Merge remote-tracking branch 'szwacz/master' into update-upstream

# Conflicts:
#	.gitignore
#	.travis.yml
#	LICENSE
#	README.md
#	app/app.html
#	app/background.js
#	app/images/osx/dmg-icon.icns
#	app/package.json
#	app/vendor/electron_boilerplate/window_state.js
#	appveyor.yml
#	build/icon.icns
#	build/icon.ico
#	build/icons/512x512.png
#	package.json
#	resources/linux/app.desktop
#	resources/osx/Info.plist
#	resources/windows/installer.nsi
#	src/hello_world/hello_world.js
#	tasks/build/build.js
#	tasks/rebuild_native_modules.js
#	tasks/release/linux.js
#	tasks/release/osx.js
#	tasks/release/windows.js
#	tasks/start.js
#	tasks/utils.js
This commit is contained in:
Rodrigo Nascimento 2017-01-06 15:40:41 -02:00
commit feff7c2782
No known key found for this signature in database
GPG key ID: 2C85B3AFE75D23F9
58 changed files with 683 additions and 5896 deletions

15
.gitignore vendored
View file

@ -1,9 +1,20 @@
node_modules
*.log
.DS_Store
Thumbs.db
*.log
*.autogenerated
# ignore everything in 'app' folder what had been generated from 'src' folder
/app/stylesheets
/app/app.js
/app/background.js
/app/env.json
/app/**/*.map
/dist
/coverage
/app/spec.js
/build/
/releases/
/tmp/

View file

@ -1,59 +1,24 @@
matrix:
include:
- os: linux
dist: trusty
sudo: required
language: node_js
node_js:
- '6'
script:
- npm run release
deploy:
provider: releases
api_key:
secure: DHzFJ9JGNAdeTfTmr2vLJFajTL0XHEYG1OKeD4DQhxB1mGDVLF2FX2T0PrRoqD1S084acnEKN1nElMn/NWc6eNPVksqQlC+8kVtYsX6EMOoiT8YnsQW7TbAsBEp30a/cuNWLfUFEmwsXN6zQQMgyF5vBoMjsycxDXq9MF5AQ9Y5BXM0Dky/v5fBmKyoVl76G/L6RkXM4SaTpthwfFmYlrAC9ctbCgA337qS3SmNLjh/EzdvgAkI9mKxewBizp1/T8hzagcOXs+UoDm/avhbZTTxfxf03PcBHs6/YwlVGKWnsIKD+BP0N7kt4q7QjXmetx5UDvKiYk269lyapqgJgF9QhODjuxufK8HD9dkOfDsG2kC0VR0BK7fHgkFCEItp6rvWwvvSDKVJN7ogT9V8XzQjeYTWwc03B58zu0f9hS4x5OMrZrs3z+NsZ/Qr+yaEjmJAiazkJPOpfPyQB0EllnPY+uugzo9iUMLmK99aYTgK0ZKOOHtJh0ziv6aQgKK6L8i9k7A4t99SfLlaKPCNITx4FH8TbLts6Oxm87iNhTQPafpOZUGUeuHtJywK1Rj+CfYffqIpZahG8jRkfllqMoLZ0QmBXE+CD7aPkTN3MF1Tj38Dbe3HjOGl8oJfS2VKH8dH6p6e9BBTXvU/bOKNCb8FBjNT2SY8UYAxnlDmzJDA=
file:
- "$TRAVIS_BUILD_DIR/releases/rocketchat-$TRAVIS_TAG-linux-x64.deb"
- "$TRAVIS_BUILD_DIR/releases/rocketchat-$TRAVIS_TAG-linux.x86_64.rpm"
skip_cleanup: true
on:
tags: true
all_branches: true
- os: osx
osx_image: xcode7.3.1
language: generic
node_js:
- '6'
script:
- echo $CERTIFICATE_OSX_P12_DEVELOPER_ID_APPLICATION | base64 --decode > CERTIFICATE_OSX_P12_DEVELOPER_ID_APPLICATION.p12
- security create-keychain -p KEYCHAIN_PASSWORD build.keychain
- security default-keychain -s build.keychain
- security unlock-keychain -p KEYCHAIN_PASSWORD build.keychain
- security import CERTIFICATE_OSX_P12_DEVELOPER_ID_APPLICATION.p12 -k build.keychain -P '' -T /usr/bin/codesign
- security find-identity -v
- npm run release
deploy:
provider: releases
api_key:
secure: DHzFJ9JGNAdeTfTmr2vLJFajTL0XHEYG1OKeD4DQhxB1mGDVLF2FX2T0PrRoqD1S084acnEKN1nElMn/NWc6eNPVksqQlC+8kVtYsX6EMOoiT8YnsQW7TbAsBEp30a/cuNWLfUFEmwsXN6zQQMgyF5vBoMjsycxDXq9MF5AQ9Y5BXM0Dky/v5fBmKyoVl76G/L6RkXM4SaTpthwfFmYlrAC9ctbCgA337qS3SmNLjh/EzdvgAkI9mKxewBizp1/T8hzagcOXs+UoDm/avhbZTTxfxf03PcBHs6/YwlVGKWnsIKD+BP0N7kt4q7QjXmetx5UDvKiYk269lyapqgJgF9QhODjuxufK8HD9dkOfDsG2kC0VR0BK7fHgkFCEItp6rvWwvvSDKVJN7ogT9V8XzQjeYTWwc03B58zu0f9hS4x5OMrZrs3z+NsZ/Qr+yaEjmJAiazkJPOpfPyQB0EllnPY+uugzo9iUMLmK99aYTgK0ZKOOHtJh0ziv6aQgKK6L8i9k7A4t99SfLlaKPCNITx4FH8TbLts6Oxm87iNhTQPafpOZUGUeuHtJywK1Rj+CfYffqIpZahG8jRkfllqMoLZ0QmBXE+CD7aPkTN3MF1Tj38Dbe3HjOGl8oJfS2VKH8dH6p6e9BBTXvU/bOKNCb8FBjNT2SY8UYAxnlDmzJDA=
file:
- "$TRAVIS_BUILD_DIR/releases/rocketchat-$TRAVIS_TAG-darwin-x64.dmg"
skip_cleanup: true
on:
tags: true
all_branches: true
git:
depth: 1
addons:
apt:
packages:
- rpm
os:
- linux
- osx
branches:
only:
- master
env:
- NODE_VERSION="6.3.0"
before_script:
- rm -rf releases
- travis_retry npm install
- |
perl -pe "s/(\"version\": )\"([0-9.]+)\"/ q{\"version\": \"${TRAVIS_TAG:-0.0.0}\"} /ge" -i app/package.json
- |
perl -pe "s/(\"build\": )\"([0-9.]+)\"/ q{\"build\": \"${TRAVIS_BUILD_NUMBER:-0}\"} /ge" -i app/package.json
after_script:
- ls releases/
- chmod +x ./scripts/travis-build.sh
script: ./scripts/travis-build.sh
cache:
directories:
- node_modules
notifications:
email:
on_success: never
on_failure: change

View file

@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

176
README.md
View file

@ -1,111 +1,127 @@
Rocket.Chat.Electron
==============
# electron-boilerplate [![Build Status](https://travis-ci.org/szwacz/electron-boilerplate.svg?branch=master)](https://travis-ci.org/szwacz/electron-boilerplate) [![Build status](https://ci.appveyor.com/api/projects/status/s9htc1k5ojkn08fr?svg=true)](https://ci.appveyor.com/project/szwacz/electron-boilerplate)
All of Rocket.Chat's Desktop Apps - for Windows, Mac OSX, and Linux are based on the [Electron platform from GitHub](https://github.com/electron/electron). This is the source code base for all desktop apps.
A minimalistic yet comprehensive boilerplate application for [Electron runtime](http://electron.atom.io). Tested on macOS, Windows and Linux.
### IMPORTANT
This project does not impose on you any framework (like Angular or React). It tries to give you only the 'electron' part of technology stack so you can pick your favorite technologies to build the actual app.
Please join the community server channel for Rocket.Chat Electron app users for feedback, interactions, and important notification regarding this code:
# Quick start
https://demo.rocket.chat/channel/desktopclient
The only development dependency of this project is [Node.js](https://nodejs.org), so just make sure you have it installed.
Then type few commands known to every Node developer...
```
git clone https://github.com/szwacz/electron-boilerplate.git
cd electron-boilerplate
npm install
npm start
```
... and boom! You have a running desktop application on your screen.
# Structure of the project
The application is split between two main folders...
`src` - this folder is intended for files which need to be transpiled or compiled (files which can't be used directly by Electron).
`app` - contains all static assets (put here images, css, html etc.) which don't need any pre-processing.
The build process compiles all stuff from the `src` folder and puts it into the `app` folder, so after the build has finished, your `app` folder contains the full, runnable application.
Treat `src` and `app` folders like two halves of one bigger thing.
The drawback of this design is that `app` folder contains some files which should be git-ignored and some which shouldn't (see `.gitignore` file). But thanks to this two-folders split development builds are much (much!) faster.
# Development
#### Installation
```
npm install
```
It will also download Electron runtime, and install dependencies for second `package.json` file inside `app` folder.
Debian users need to make sure they have the package `libxss-dev` installed.
#### Starting the app
## Starting the app
```
npm start
```
# Structure of the project
## Upgrading Electron version
There are **two** `package.json` files:
The version of Electron runtime your app is using is declared in `package.json`:
```json
"devDependencies": {
"electron": "1.4.7"
}
```
Side note: [Electron authors advise](http://electron.atom.io/docs/tutorial/electron-versioning/) to use fixed version here.
#### 1. For development
Sits at root of the application. Just used for development dependencies. **This file is not distributed with real application!**
## The build pipeline
Build process is founded upon [gulp](https://github.com/gulpjs/gulp) task runner and [rollup](https://github.com/rollup/rollup) bundler. There are two entry files for your code: `src/background.js` and `src/app.js`. Rollup will follow all `import` statements starting from those files and compile code of the whole dependency tree into one `.js` file for each entry point.
#### 2. For your application
Sits on path: `app/package.json`. This is **real** manifest of your application. App dependencies declared here.
You can [add as many more entry points as you like](https://github.com/szwacz/electron-boilerplate/blob/master/tasks/build_app.js#L16) (e.g. to split your app into modules).
### Project's folders
By the way, [rollup has a lot of plugins](https://github.com/rollup/rollup/wiki/Plugins). You can add them in [this file](https://github.com/szwacz/electron-boilerplate/blob/master/tasks/bundle.js).
- `app` - code of your application goes here.
- `config` - place where you can declare environment specific stuff for your app.
- `build` - in this folder lands built, runnable application.
- `releases` - ready for distribution installers will land here.
- `resources` - resources needed for particular operating system.
- `tasks` - build and development environment scripts.
## Adding npm modules to your app
Remember to respect the split between `dependencies` and `devDependencies` in `package.json` file. Only modules listed in `dependencies` will be included into distributable app.
Side note: If the module you want to use in your app is a native one (not pure JavaScript but compiled C code or something) you should first run `npm install name_of_npm_module --save` and then `npm run postinstall` to rebuild the module for Electron. This needs to be done only once when you're first time installing the module. Later on postinstall script will fire automatically with every `npm install`.
## Working with modules
Thanks to [rollup](https://github.com/rollup/rollup) you can (and should) use ES6 modules for all code in `src` folder. But because ES6 modules still aren't natively supported you can't use them in the `app` folder.
Use ES6 syntax in the `src` folder like this:
```js
import myStuff from './my_lib/my_stuff';
```
But use CommonJS syntax in `app` folder. So the code from above should look as follows:
```js
var myStuff = require('./my_lib/my_stuff');
```
# Testing
## Unit tests
```
npm test
```
Using [electron-mocha](https://github.com/jprichardson/electron-mocha) test runner with the [chai](http://chaijs.com/api/assert/) assertion library. This task searches for all files in `src` directory which respect pattern `*.spec.js`.
## End to end tests
```
npm run e2e
```
Using [mocha](https://mochajs.org/) test runner and [spectron](http://electron.atom.io/spectron/). This task searches for all files in `e2e` directory which respect pattern `*.e2e.js`.
## Code coverage
```
npm run coverage
```
Using [istanbul](http://gotwarlost.github.io/istanbul/) code coverage tool.
You can set the reporter(s) by setting `ISTANBUL_REPORTERS` environment variable (defaults to `text-summary` and `html`). The report directory can be set with `ISTANBUL_REPORT_DIR` (defaults to `coverage`).
## Continuous integration
Electron [can be plugged](https://github.com/atom/electron/blob/master/docs/tutorial/testing-on-headless-ci.md) into CI systems. Here two CIs are preconfigured for you. [Travis CI](https://travis-ci.org/) tests on macOS and Linux, [App Veyor](https://www.appveyor.com) tests on Windows.
# Making a release
**Note:** There are various icon and bitmap files in `resources` directory. Those are used in installers and are intended to be replaced by your own graphics.
To package your app into an installer use command:
To make ready for distribution installer use command:
```
npm run release
```
It will start the packaging process for operating system you are running this command on. Ready for distribution file will be outputted to `releases` directory.
You can create Windows installer only when running on Windows, the same is true for Linux and OSX. So to generate all three installers you need all three operating systems.
It will start the packaging process for operating system you are running this command on. Ready for distribution file will be outputted to `dist` directory.
## Mac only
You can create Windows installer only when running on Windows, the same is true for Linux and macOS. So to generate all three installers you need all three operating systems.
#### App signing
All packaging actions are handled by [electron-builder](https://github.com/electron-userland/electron-builder). It has a lot of [customization options](https://github.com/electron-userland/electron-builder/wiki/Options), which you can declare under ["build" key in package.json file](https://github.com/szwacz/electron-boilerplate/blob/master/package.json#L2).
The Mac release supports [code signing](https://developer.apple.com/library/mac/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html). To sign the `.app` in the release image, include the certificate ID in the command as so,
```shell
npm run release -- --sign DX85ENM22A
```
# License
#### Mac App Store
You should install the Electron build for MAS
```
export npm_config_platform=mas
rm -rf node_modules
npm install
```
To sign your app for Mac App Store
```shell
npm run release -- --mas --mas-sign "3rd Party Mac Developer Application: Company Name (APPIDENTITY)" --mas-installer-sign "3rd Party Mac Developer Installer: Company Name (APPIDENTITY)"
```
Or edit the `app/package.json`, remove the `//` from `//codeSignIdentitiy` and update the values with your sign indentities
```json
"//codeSignIdentitiy": {
"dmg": "Developer ID Application: Company Name (APPIDENTITY)",
"MAS": "3rd Party Mac Developer Application: Company Name (APPIDENTITY)",
"MASInstaller": "3rd Party Mac Developer Installer: Company Name (APPIDENTITY)"
}
```
You can change the application category too
```json
"LSApplicationCategoryType": "public.app-category.productivity"
```
If you insert your indentities in the package.json you can compile for MAS like
```
npm run release -- --mas
```
## Windows only
#### Installer
The installer is built using [NSIS](http://nsis.sourceforge.net). You have to install NSIS version 3.0, and add its folder to PATH in Environment Variables, so it is reachable to scripts in this project. For example, `C:\Program Files (x86)\NSIS`.
#### 32-bit build on 64-bit Windows
There are still a lot of 32-bit Windows installations in use. If you want to support those systems and have 64-bit OS make sure you've installed 32-bit (instead of 64-bit) Node version. There are [versions managers](https://github.com/coreybutler/nvm-windows) if you feel the need for both architectures on the same machine.
Released under the MIT license.

View file

@ -1,15 +0,0 @@
// Simple module exposes environment variables to rest of the code.
import jetpack from 'fs-jetpack';
var app;
if (process.type === 'renderer') {
app = require('electron').remote.app;
} else {
app = require('electron').app;
}
var appDir = jetpack.cwd(app.getAppPath());
var manifest = appDir.read('package.json', 'json');
export default manifest.env;

View file

@ -8,6 +8,10 @@
var Menu = remote.Menu;
var MenuItem = remote.MenuItem;
var isAnyTextSelected = function () {
return window.getSelection().toString() !== '';
};
var cut = new MenuItem({
label: "Cut",
click: function () {
@ -29,21 +33,27 @@
}
});
var textMenu = new Menu();
textMenu.append(cut);
textMenu.append(copy);
textMenu.append(paste);
var normalMenu = new Menu();
normalMenu.append(copy);
var textEditingMenu = new Menu();
textEditingMenu.append(cut);
textEditingMenu.append(copy);
textEditingMenu.append(paste);
document.addEventListener('contextmenu', function (e) {
switch (e.target.nodeName) {
case 'TEXTAREA':
case 'INPUT':
e.preventDefault();
textMenu.popup(remote.getCurrentWindow());
textEditingMenu.popup(remote.getCurrentWindow());
break;
default:
if (isAnyTextSelected()) {
e.preventDefault();
normalMenu.popup(remote.getCurrentWindow());
}
}
}, false);
}());

View file

@ -35,10 +35,10 @@
} else if (element.parentElement) {
checkDomElement(element.parentElement);
}
}
};
checkDomElement(e.target);
}
};
document.addEventListener('click', supportExternalLinks, false);
}());

Binary file not shown.

View file

@ -1,29 +0,0 @@
{
"name": "rocketchat",
"productName": "Rocket.Chat+",
"identifier": "chat.rocket",
"description": "Rocket.Chat Native Cross-Platform Desktop Application via Electron.",
"version": "2.0.2",
"build": "1",
"author": "Rocket.Chat Support <support@rocket.chat>",
"copyright": "© 2016, Rocket.Chat",
"main": "background.js",
"dependencies": {
"@paulcbetts/system-idle-time": "^1.0.4",
"ffi": "^2.2.0",
"electron-notification-shim": "^1.1.0",
"electron-toaster": "^2.0.2",
"fs-jetpack": "^0.10.5",
"spellchecker": "3.3.1"
},
"packageNameTemplate": "{{name}}-{{version}}-{{platform}}-{{arch}}",
"codeSignIdentity": {
"dmg": "Developer ID Application: Konecty Informatica Ltda (DX85ENM22A)",
"MAS": "3rd Party Mac Developer Application: Konecty Informatica Ltda (DX85ENM22A)",
"MASInstaller": "3rd Party Mac Developer Installer: Konecty Informatica Ltda (DX85ENM22A)"
},
"LSApplicationCategoryType": "public.app-category.productivity",
"scripts": {
"postinstall": "node ../tasks/rebuild_native_modules"
}
}

View file

@ -1,16 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Jasmine Spec Runner</title>
<link rel="stylesheet" type="text/css" href="vendor/jasmine/jasmine.css">
<script src="vendor/jasmine/jasmine.js"></script>
<script src="vendor/jasmine/jasmine-html.js"></script>
<script src="vendor/jasmine/boot.js"></script>
<script src="spec.js"></script>
</head>
<body>
</body>
</html>

View file

@ -1,31 +0,0 @@
import { app, Menu, BrowserWindow } from 'electron';
var setDevMenu = function () {
var devMenu = Menu.buildFromTemplate([{
label: 'Development',
submenu: [{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click: function () {
BrowserWindow.getFocusedWindow().webContents.reloadIgnoringCache();
}
},{
label: 'Toggle DevTools',
accelerator: 'Alt+CmdOrCtrl+I',
click: function () {
BrowserWindow.getFocusedWindow().toggleDevTools();
}
},{
label: 'Quit',
accelerator: 'CmdOrCtrl+Q',
click: function () {
app.quit();
}
}]
}]);
Menu.setApplicationMenu(devMenu);
};
export default {
setDevMenu: setDevMenu,
}

View file

@ -1,52 +0,0 @@
// Simple module to help you remember the size and position of windows.
// Can be used for more than one window, just construct many
// instances of it and give each different name.
import { app } from 'electron';
import jetpack from 'fs-jetpack';
export default function (name, defaults) {
var userDataDir = jetpack.cwd(app.getPath('userData'));
var stateStoreFile = 'window-state-' + name +'.json';
var state = {
width: defaults.width,
height: defaults.height
};
try {
var loadedState = userDataDir.read(stateStoreFile, 'json');
if (loadedState != null) {
state = loadedState;
}
} catch (err) {
// For some reason json can't be read.
// No worries, we have defaults.
}
var saveState = function (win) {
if (!win.isMaximized() && !win.isMinimized() && win.isVisible()) {
var position = win.getPosition();
var size = win.getSize();
state.x = position[0];
state.y = position[1];
state.width = size[0];
state.height = size[1];
}
state.isMaximized = win.isMaximized();
state.isMinimized = win.isMinimized();
state.isHidden = !win.isMinimized() && !win.isVisible();
userDataDir.write(stateStoreFile, state, { atomic: true });
};
return {
get x() { return state.x; },
get y() { return state.y; },
get width() { return state.width; },
get height() { return state.height; },
get isMaximized() { return state.isMaximized; },
get isMinimized() { return state.isMinimized; },
get isHidden() { return state.isHidden; },
saveState: saveState
};
}

View file

@ -1,130 +0,0 @@
/**
Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project.
If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms.
The location of `boot.js` can be specified and/or overridden in `jasmine.yml`.
[jasmine-gem]: http://github.com/pivotal/jasmine-gem
*/
(function() {
/**
* ## Require &amp; Instantiate
*
* Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
*/
window.jasmine = jasmineRequire.core(jasmineRequire);
/**
* Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
*/
jasmineRequire.html(jasmine);
/**
* Create the Jasmine environment. This is used to run all specs in a project.
*/
var env = jasmine.getEnv();
/**
* ## The Global Interface
*
* Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
*/
var jasmineInterface = jasmineRequire.interface(jasmine, env);
/**
* Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
*/
extend(window, jasmineInterface);
/**
* ## Runner Parameters
*
* More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
*/
var queryString = new jasmine.QueryString({
getWindowLocation: function() { return window.location; }
});
var catchingExceptions = queryString.getParam("catch");
env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions);
var throwingExpectationFailures = queryString.getParam("throwFailures");
env.throwOnExpectationFailure(throwingExpectationFailures);
var random = queryString.getParam("random");
env.randomizeTests(random);
var seed = queryString.getParam("seed");
if (seed) {
env.seed(seed);
}
/**
* ## Reporters
* The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
*/
var htmlReporter = new jasmine.HtmlReporter({
env: env,
onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); },
onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); },
onRandomClick: function() { queryString.navigateWithNewParam("random", !env.randomTests()); },
addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); },
getContainer: function() { return document.body; },
createElement: function() { return document.createElement.apply(document, arguments); },
createTextNode: function() { return document.createTextNode.apply(document, arguments); },
timer: new jasmine.Timer()
});
/**
* The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
*/
env.addReporter(jasmineInterface.jsApiReporter);
env.addReporter(htmlReporter);
/**
* Filter which specs will be run by matching the start of the full name against the `spec` query param.
*/
var specFilter = new jasmine.HtmlSpecFilter({
filterString: function() { return queryString.getParam("spec"); }
});
env.specFilter = function(spec) {
return specFilter.matches(spec.getFullName());
};
/**
* Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack.
*/
window.setTimeout = window.setTimeout;
window.setInterval = window.setInterval;
window.clearTimeout = window.clearTimeout;
window.clearInterval = window.clearInterval;
/**
* ## Execution
*
* Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
*/
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
htmlReporter.initialize();
env.execute();
};
/**
* Helper function for readability above.
*/
function extend(destination, source) {
for (var property in source) destination[property] = source[property];
return destination;
}
}());

View file

@ -1,473 +0,0 @@
/*
Copyright (c) 2008-2015 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
jasmineRequire.html = function(j$) {
j$.ResultsNode = jasmineRequire.ResultsNode();
j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
j$.QueryString = jasmineRequire.QueryString();
j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
};
jasmineRequire.HtmlReporter = function(j$) {
var noopTimer = {
start: function() {},
elapsed: function() { return 0; }
};
function HtmlReporter(options) {
var env = options.env || {},
getContainer = options.getContainer,
createElement = options.createElement,
createTextNode = options.createTextNode,
onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {},
onThrowExpectationsClick = options.onThrowExpectationsClick || function() {},
onRandomClick = options.onRandomClick || function() {},
addToExistingQueryString = options.addToExistingQueryString || defaultQueryString,
timer = options.timer || noopTimer,
results = [],
specsExecuted = 0,
failureCount = 0,
pendingSpecCount = 0,
htmlReporterMain,
symbols,
failedSuites = [];
this.initialize = function() {
clearPrior();
htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'},
createDom('div', {className: 'jasmine-banner'},
createDom('a', {className: 'jasmine-title', href: 'http://jasmine.github.io/', target: '_blank'}),
createDom('span', {className: 'jasmine-version'}, j$.version)
),
createDom('ul', {className: 'jasmine-symbol-summary'}),
createDom('div', {className: 'jasmine-alert'}),
createDom('div', {className: 'jasmine-results'},
createDom('div', {className: 'jasmine-failures'})
)
);
getContainer().appendChild(htmlReporterMain);
};
var totalSpecsDefined;
this.jasmineStarted = function(options) {
totalSpecsDefined = options.totalSpecsDefined || 0;
timer.start();
};
var summary = createDom('div', {className: 'jasmine-summary'});
var topResults = new j$.ResultsNode({}, '', null),
currentParent = topResults;
this.suiteStarted = function(result) {
currentParent.addChild(result, 'suite');
currentParent = currentParent.last();
};
this.suiteDone = function(result) {
if (result.status == 'failed') {
failedSuites.push(result);
}
if (currentParent == topResults) {
return;
}
currentParent = currentParent.parent;
};
this.specStarted = function(result) {
currentParent.addChild(result, 'spec');
};
var failures = [];
this.specDone = function(result) {
if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') {
console.error('Spec \'' + result.fullName + '\' has no expectations.');
}
if (result.status != 'disabled') {
specsExecuted++;
}
if (!symbols){
symbols = find('.jasmine-symbol-summary');
}
symbols.appendChild(createDom('li', {
className: noExpectations(result) ? 'jasmine-empty' : 'jasmine-' + result.status,
id: 'spec_' + result.id,
title: result.fullName
}
));
if (result.status == 'failed') {
failureCount++;
var failure =
createDom('div', {className: 'jasmine-spec-detail jasmine-failed'},
createDom('div', {className: 'jasmine-description'},
createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName)
),
createDom('div', {className: 'jasmine-messages'})
);
var messages = failure.childNodes[1];
for (var i = 0; i < result.failedExpectations.length; i++) {
var expectation = result.failedExpectations[i];
messages.appendChild(createDom('div', {className: 'jasmine-result-message'}, expectation.message));
messages.appendChild(createDom('div', {className: 'jasmine-stack-trace'}, expectation.stack));
}
failures.push(failure);
}
if (result.status == 'pending') {
pendingSpecCount++;
}
};
this.jasmineDone = function(doneResult) {
var banner = find('.jasmine-banner');
var alert = find('.jasmine-alert');
var order = doneResult && doneResult.order;
alert.appendChild(createDom('span', {className: 'jasmine-duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
banner.appendChild(
createDom('div', { className: 'jasmine-run-options' },
createDom('span', { className: 'jasmine-trigger' }, 'Options'),
createDom('div', { className: 'jasmine-payload' },
createDom('div', { className: 'jasmine-exceptions' },
createDom('input', {
className: 'jasmine-raise',
id: 'jasmine-raise-exceptions',
type: 'checkbox'
}),
createDom('label', { className: 'jasmine-label', 'for': 'jasmine-raise-exceptions' }, 'raise exceptions')),
createDom('div', { className: 'jasmine-throw-failures' },
createDom('input', {
className: 'jasmine-throw',
id: 'jasmine-throw-failures',
type: 'checkbox'
}),
createDom('label', { className: 'jasmine-label', 'for': 'jasmine-throw-failures' }, 'stop spec on expectation failure')),
createDom('div', { className: 'jasmine-random-order' },
createDom('input', {
className: 'jasmine-random',
id: 'jasmine-random-order',
type: 'checkbox'
}),
createDom('label', { className: 'jasmine-label', 'for': 'jasmine-random-order' }, 'run tests in random order'))
)
));
var raiseCheckbox = find('#jasmine-raise-exceptions');
raiseCheckbox.checked = !env.catchingExceptions();
raiseCheckbox.onclick = onRaiseExceptionsClick;
var throwCheckbox = find('#jasmine-throw-failures');
throwCheckbox.checked = env.throwingExpectationFailures();
throwCheckbox.onclick = onThrowExpectationsClick;
var randomCheckbox = find('#jasmine-random-order');
randomCheckbox.checked = env.randomTests();
randomCheckbox.onclick = onRandomClick;
var optionsMenu = find('.jasmine-run-options'),
optionsTrigger = optionsMenu.querySelector('.jasmine-trigger'),
optionsPayload = optionsMenu.querySelector('.jasmine-payload'),
isOpen = /\bjasmine-open\b/;
optionsTrigger.onclick = function() {
if (isOpen.test(optionsPayload.className)) {
optionsPayload.className = optionsPayload.className.replace(isOpen, '');
} else {
optionsPayload.className += ' jasmine-open';
}
};
if (specsExecuted < totalSpecsDefined) {
var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all';
alert.appendChild(
createDom('span', {className: 'jasmine-bar jasmine-skipped'},
createDom('a', {href: '?', title: 'Run all specs'}, skippedMessage)
)
);
}
var statusBarMessage = '';
var statusBarClassName = 'jasmine-bar ';
if (totalSpecsDefined > 0) {
statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount);
if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); }
statusBarClassName += (failureCount > 0) ? 'jasmine-failed' : 'jasmine-passed';
} else {
statusBarClassName += 'jasmine-skipped';
statusBarMessage += 'No specs found';
}
var seedBar;
if (order && order.random) {
seedBar = createDom('span', {className: 'jasmine-seed-bar'},
', randomized with seed ',
createDom('a', {title: 'randomized with seed ' + order.seed, href: seedHref(order.seed)}, order.seed)
);
}
alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage, seedBar));
for(i = 0; i < failedSuites.length; i++) {
var failedSuite = failedSuites[i];
for(var j = 0; j < failedSuite.failedExpectations.length; j++) {
var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message;
var errorBarClassName = 'jasmine-bar jasmine-errored';
alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage));
}
}
var results = find('.jasmine-results');
results.appendChild(summary);
summaryList(topResults, summary);
function summaryList(resultsTree, domParent) {
var specListNode;
for (var i = 0; i < resultsTree.children.length; i++) {
var resultNode = resultsTree.children[i];
if (resultNode.type == 'suite') {
var suiteListNode = createDom('ul', {className: 'jasmine-suite', id: 'suite-' + resultNode.result.id},
createDom('li', {className: 'jasmine-suite-detail'},
createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description)
)
);
summaryList(resultNode, suiteListNode);
domParent.appendChild(suiteListNode);
}
if (resultNode.type == 'spec') {
if (domParent.getAttribute('class') != 'jasmine-specs') {
specListNode = createDom('ul', {className: 'jasmine-specs'});
domParent.appendChild(specListNode);
}
var specDescription = resultNode.result.description;
if(noExpectations(resultNode.result)) {
specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
}
if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') {
specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
}
specListNode.appendChild(
createDom('li', {
className: 'jasmine-' + resultNode.result.status,
id: 'spec-' + resultNode.result.id
},
createDom('a', {href: specHref(resultNode.result)}, specDescription)
)
);
}
}
}
if (failures.length) {
alert.appendChild(
createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-spec-list'},
createDom('span', {}, 'Spec List | '),
createDom('a', {className: 'jasmine-failures-menu', href: '#'}, 'Failures')));
alert.appendChild(
createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-failure-list'},
createDom('a', {className: 'jasmine-spec-list-menu', href: '#'}, 'Spec List'),
createDom('span', {}, ' | Failures ')));
find('.jasmine-failures-menu').onclick = function() {
setMenuModeTo('jasmine-failure-list');
};
find('.jasmine-spec-list-menu').onclick = function() {
setMenuModeTo('jasmine-spec-list');
};
setMenuModeTo('jasmine-failure-list');
var failureNode = find('.jasmine-failures');
for (var i = 0; i < failures.length; i++) {
failureNode.appendChild(failures[i]);
}
}
};
return this;
function find(selector) {
return getContainer().querySelector('.jasmine_html-reporter ' + selector);
}
function clearPrior() {
// return the reporter
var oldReporter = find('');
if(oldReporter) {
getContainer().removeChild(oldReporter);
}
}
function createDom(type, attrs, childrenVarArgs) {
var el = createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(createTextNode(child));
} else {
if (child) {
el.appendChild(child);
}
}
}
for (var attr in attrs) {
if (attr == 'className') {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
}
function pluralize(singular, count) {
var word = (count == 1 ? singular : singular + 's');
return '' + count + ' ' + word;
}
function specHref(result) {
return addToExistingQueryString('spec', result.fullName);
}
function seedHref(seed) {
return addToExistingQueryString('seed', seed);
}
function defaultQueryString(key, value) {
return '?' + key + '=' + value;
}
function setMenuModeTo(mode) {
htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode);
}
function noExpectations(result) {
return (result.failedExpectations.length + result.passedExpectations.length) === 0 &&
result.status === 'passed';
}
}
return HtmlReporter;
};
jasmineRequire.HtmlSpecFilter = function() {
function HtmlSpecFilter(options) {
var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
var filterPattern = new RegExp(filterString);
this.matches = function(specName) {
return filterPattern.test(specName);
};
}
return HtmlSpecFilter;
};
jasmineRequire.ResultsNode = function() {
function ResultsNode(result, type, parent) {
this.result = result;
this.type = type;
this.parent = parent;
this.children = [];
this.addChild = function(result, type) {
this.children.push(new ResultsNode(result, type, this));
};
this.last = function() {
return this.children[this.children.length - 1];
};
}
return ResultsNode;
};
jasmineRequire.QueryString = function() {
function QueryString(options) {
this.navigateWithNewParam = function(key, value) {
options.getWindowLocation().search = this.fullStringWithNewParam(key, value);
};
this.fullStringWithNewParam = function(key, value) {
var paramMap = queryStringToParamMap();
paramMap[key] = value;
return toQueryString(paramMap);
};
this.getParam = function(key) {
return queryStringToParamMap()[key];
};
return this;
function toQueryString(paramMap) {
var qStrPairs = [];
for (var prop in paramMap) {
qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop]));
}
return '?' + qStrPairs.join('&');
}
function queryStringToParamMap() {
var paramStr = options.getWindowLocation().search.substring(1),
params = [],
paramMap = {};
if (paramStr.length > 0) {
params = paramStr.split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
var value = decodeURIComponent(p[1]);
if (value === 'true' || value === 'false') {
value = JSON.parse(value);
}
paramMap[decodeURIComponent(p[0])] = value;
}
}
return paramMap;
}
}
return QueryString;
};

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1,34 +1,26 @@
platform:
- x64
build: off
os: unstable
branches:
only:
- master
skip_tags: true
environment:
nodejs_version: "6.3.0"
cache:
- node_modules
- '%USERPROFILE%\.electron'
- node_modules -> package.json
install:
- ps: Install-Product node 6 x64
- choco install nsis -version 3.01.0.20160420
- SET PATH=C:/Program Files (x86)/NSIS;%PATH%
- ps: Install-Product node $env:nodejs_version
- npm install npm
- .\node_modules\.bin\npm install
build_script:
- ps: >-
if (-not (Test-Path env:APPVEYOR_REPO_TAG_NAME)) { $env:APPVEYOR_REPO_TAG_NAME = '0.0.0' }
(gc app\package.json) -replace '"version": "[0-9.]+"', ('"version": "'+$env:APPVEYOR_REPO_TAG_NAME+'"') | Set-Content app\package.json
- npm run release
artifacts:
- path: 'releases\*.exe'
deploy:
- provider: GitHub
artifact: /.*\.exe/
auth_token:
secure: rqUx+ZsMc/HqozfHhXVgjG9rd0Iqyg+cVNakbk7EXXEsQvYC/E77jMgJutWlbI6V
draft: false
prerelease: false
on:
appveyor_repo_tag: true # deploy on tag push only
test: off
test_script:
- node --version
- .\node_modules\.bin\npm --version
- .\node_modules\.bin\npm test
- .\node_modules\.bin\npm run e2e

BIN
build/icon.icns Normal file

Binary file not shown.

BIN
build/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

BIN
build/icons/512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

14
e2e/hello_world.e2e.js Normal file
View file

@ -0,0 +1,14 @@
import { expect } from 'chai';
import testUtils from './utils';
describe('application launch', function () {
beforeEach(testUtils.beforeEach);
afterEach(testUtils.afterEach);
it('shows hello world text on screen after launch', function () {
return this.app.client.getText('#greet').then(function (text) {
expect(text).to.equal('Hello World!');
});
});
});

25
e2e/utils.js Normal file
View file

@ -0,0 +1,25 @@
import electron from 'electron';
import { Application } from 'spectron';
var beforeEach = function () {
this.timeout(10000);
this.app = new Application({
path: electron,
args: ['.'],
startTimeout: 10000,
waitTimeout: 10000,
});
return this.app.start();
};
var afterEach = function () {
this.timeout(10000);
if (this.app && this.app.isRunning()) {
return this.app.stop();
}
};
export default {
beforeEach: beforeEach,
afterEach: afterEach,
};

View file

@ -1,4 +1,5 @@
'use strict';
require('./tasks/build/build');
require('./tasks/release/release');
require('./tasks/build_app');
require('./tasks/build_tests');
require('./tasks/start');

View file

@ -1,37 +1,57 @@
{
"devDependencies": {
"asar": "^0.12.3",
"electron": "1.4.13",
"electron-rebuild": "^1.2.1",
"fs-jetpack": "^0.10.5",
"gulp": "^3.9.1",
"gulp-less": "^3.1.0",
"gulp-util": "^3.0.7",
"q": "^1.4.1",
"rollup": "^0.38.0",
"tree-kill": "^1.1.0",
"yargs": "^6.5.0"
},
"optionalDependencies": {
"appdmg": "^0.4.5",
"rcedit": "^0.7.0"
},
"scripts": {
"postinstall": "cd app && npm install",
"build": "gulp build",
"release": "gulp release --env=production",
"start": "node ./tasks/start",
"test": "node ./tasks/start --env=test",
"install-native": "node ./tasks/install_native_module",
"build-asar": "gulp build:asar --env=production"
},
"name": "rocketchat",
"productName": "Rocket.Chat+",
"description": "Rocket.Chat Native Cross-Platform Desktop Application via Electron.",
"version": "2.0.2",
"author": "Rocket.Chat Support <support@rocket.chat>",
"copyright": "© 2016, Rocket.Chat",
"homepage": "http://example.com",
"license": "MIT",
"main": "app/background.js",
"repository": {
"type": "git",
"url": "https://github.com/RocketChat/Rocket.Chat.Electron.git"
},
"license": "MIT",
"build": {
"appId": "chat.rocket",
"files": [
"app/**/*",
"node_modules/**/*",
"package.json"
]
},
"scripts": {
"postinstall": "install-app-deps",
"build": "gulp build",
"prerelease": "gulp build --env=production",
"release": "build",
"start": "gulp start",
"pretest": "gulp build-unit --env=test",
"test": "electron-mocha app/specs.js.autogenerated --renderer --require source-map-support/register",
"coverage": "npm test -- -R scripts/istanbul-reporter",
"pree2e": "gulp build-e2e --env=test",
"e2e": "mocha app/e2e.js.autogenerated --require source-map-support/register"
},
"dependencies": {
"electron-notification-shim": "^1.1.0",
"electron-toaster": "^2.0.2"
"fs-jetpack": "^0.10.2"
},
"devDependencies": {
"chai": "^3.5.0",
"electron": "^1.4.7",
"electron-builder": "^8.6.0",
"electron-mocha": "^3.0.0",
"gulp": "^3.9.0",
"gulp-batch": "^1.0.5",
"gulp-less": "^3.0.3",
"gulp-plumber": "^1.1.0",
"gulp-util": "^3.0.6",
"gulp-watch": "^4.3.5",
"istanbul": "^0.4.3",
"minimist": "^1.2.0",
"mocha": "^3.0.2",
"rollup": "^0.36.3",
"rollup-plugin-istanbul": "^1.1.0",
"source-map-support": "^0.4.2",
"spectron": "^3.3.0"
}
}

View file

@ -1,7 +0,0 @@
Package: {{name}}
Version: {{version}}
Maintainer: {{author}}
Priority: optional
Architecture: amd64
Installed-Size: {{size}}
Description: {{description}}

View file

@ -1,11 +0,0 @@
[Desktop Entry]
Version=1.0
Type=Application
Encoding=UTF-8
Name={{productName}}
Comment={{description}}
Exec=/opt/{{name}}/{{name}}
Path=/opt/{{name}}/
Icon=/opt/{{name}}/icon.png
Terminal=false
Categories=Network;InstantMessaging;Chat;

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>{{productName}}</string>
<key>CFBundleExecutable</key>
<string>{{productName}}</string>
<key>CFBundleIconFile</key>
<string>icon.icns</string>
<key>CFBundleIdentifier</key>
<string>{{identifier}}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>{{productName}}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleVersion</key>
<string>{{build}}</string>
<key>CFBundleVersionString</key>
<string>{{build}}</string>
<key>CFBundleShortVersionString</key>
<string>{{version}}</string>
<key>CFBundleGetInfoString</key>
<string>{{version}}</string>
<key>LSMinimumSystemVersion</key>
<string>10.8.0</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>AtomApplication</string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>{{copyright}}</string>
<key>LSApplicationCategoryType</key>
<string>{{LSApplicationCategoryType}}</string>
</dict>
</plist>

View file

@ -1,10 +0,0 @@
{
"title": "{{productName}}",
"icon": "{{dmgIcon}}",
"background": "{{dmgBackground}}",
"icon-size": 128,
"contents": [
{ "x": 410, "y": 220, "type": "link", "path": "/Applications" },
{ "x": 130, "y": 220, "type": "file", "path": "{{appPath}}" }
]
}

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>{{productName}} Helper EH</string>
<key>CFBundleExecutable</key>
<string>{{productName}} Helper EH</string>
<key>CFBundleIdentifier</key>
<string>{{identifier}}.helper.EH</string>
<key>CFBundleName</key>
<string>{{productName}} Helper EH</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>DTSDKName</key>
<string>macosx</string>
<key>LSUIElement</key>
<true/>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>
</plist>

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>{{productName}} Helper NP</string>
<key>CFBundleExecutable</key>
<string>{{productName}} Helper NP</string>
<key>CFBundleIdentifier</key>
<string>{{identifier}}.helper.NP</string>
<key>CFBundleName</key>
<string>{{productName}} Helper NP</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>DTSDKName</key>
<string>macosx</string>
<key>LSUIElement</key>
<true/>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>
</plist>

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>{{identifier}}.helper</string>
<key>CFBundleName</key>
<string>{{productName}} Helper</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>DTSDKName</key>
<string>macosx</string>
<key>LSUIElement</key>
<true/>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>
</plist>

View file

@ -1,339 +0,0 @@
; NSIS packaging/install script
; Docs: http://nsis.sourceforge.net/Docs/Contents.html
; --------------------------------
; Includes
; --------------------------------
!include LogicLib.nsh
!include nsDialogs.nsh
!include FileFunc.nsh
!insertmacro GetParameters
; --------------------------------
; Variables
; --------------------------------
!define dest "{{dest}}"
!define src "{{src}}"
!define name "{{name}}"
!define productName "{{productName}}"
!define author "{{author}}"
!define version "{{version}}"
!define icon "{{icon}}"
!define setupIcon "{{setupIcon}}"
!define banner "{{banner}}"
!define exec "{{productName}}.exe"
!define regkey "Software\${productName}"
!define uninstkey "Software\Microsoft\Windows\CurrentVersion\Uninstall\${productName}"
!define uninstaller "uninstall.exe"
; --------------------------------
; Installation
; --------------------------------
Unicode true
SetCompressor /SOLID lzma
Name "${productName}"
Icon "${setupIcon}"
OutFile "${dest}"
InstallDir "$PROGRAMFILES\${productName}"
InstallDirRegKey HKLM "${regkey}" ""
RequestExecutionLevel admin
CRCCheck on
SilentInstall normal
XPStyle on
ShowInstDetails nevershow
AutoCloseWindow false
WindowIcon off
Caption "${productName} Setup"
; Don't add sub-captions to title bar
SubCaption 3 " "
SubCaption 4 " "
; --------------------------------
; Page layout
; --------------------------------
Page custom welcome
Page components
Page directory
Page instfiles
Page custom finish finishEnd
; --------------------------------
; Control variables
; --------------------------------
Var Image
Var ImageHandle
Var LaunchAppCheckbox
Var LaunchAppCheckbox_State
; --------------------------------
; Installer init
; --------------------------------
Function .onInit
; --------------------------------
; Checking previously installed version
; --------------------------------
ReadRegStr $R0 HKLM \
"Software\Microsoft\Windows\CurrentVersion\Uninstall\${productName}" \
"UninstallString"
StrCmp $R0 "" done
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \
"${productName} is already installed. $\n$\nClick `OK` to remove the \
previous version or `Cancel` to cancel this upgrade." \
IDOK uninst
Abort
; --------------------------------
; Run the uninstaller
; --------------------------------
uninst:
ClearErrors
ExecWait '$R0 _?=$INSTDIR' ;Do not copy the uninstaller to a temp file
IfErrors no_remove_uninstaller done
;You can either use Delete /REBOOTOK in the uninstaller or add some code
;here to remove the uninstaller. Use a registry key to check
;whether the user has chosen to uninstall. If you are using an uninstaller
;components page, make sure all sections are uninstalled.
no_remove_uninstaller:
done:
; --------------------------------
; End of uninstaller
; --------------------------------
; Set installer to silent if /silent switch was provided
${GetParameters} $R0
${GetOptionsS} $R0 "/silent" $0
IfErrors +2 0
SetSilent silent
ClearErrors
; Extract banner image for welcome page
InitPluginsDir
ReserveFile "${banner}"
File /oname=$PLUGINSDIR\banner.bmp "${banner}"
; Check if the application is currently running, show message if it is
retryInstallation:
FindWindow $0 "Chrome_WidgetWin_1" "${productName}"
StrCmp $0 0 notRunning
MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION "${productName} is currently running. Please close the application to continue." /SD IDCANCEL IDRETRY retryInstallation
Abort
notRunning:
FunctionEnd
; --------------------------------
; Welcome page [custom]
; --------------------------------
Function welcome
nsDialogs::Create 1018
${NSD_CreateLabel} 185 1u 210 100% "Welcome to ${productName} version ${version} installer.$\r$\n$\r$\nClick next to continue."
${NSD_CreateBitmap} 0 0 170 210 ""
Pop $Image
${NSD_SetImage} $Image $PLUGINSDIR\banner.bmp $ImageHandle
nsDialogs::Show
${NSD_FreeImage} $ImageHandle
FunctionEnd
; --------------------------------
; Installation sections
; --------------------------------
Section "${productName} Client"
; Make this section a requirement
SectionIn RO
WriteRegStr HKLM "${regkey}" "Install_Dir" "$INSTDIR"
WriteRegStr HKLM "${uninstkey}" "DisplayName" "${productName}"
WriteRegStr HKLM "${uninstkey}" "DisplayIcon" '"$INSTDIR\icon.ico"'
WriteRegStr HKLM "${uninstkey}" "UninstallString" '"$INSTDIR\${uninstaller}"'
WriteRegStr HKLM "${uninstkey}" "Publisher" "${author}"
WriteRegStr HKLM "${uninstkey}" "DisplayVersion" "${version}"
; Remove all application files copied by previous installation
RMDir /r "$INSTDIR"
SetOutPath $INSTDIR
; Include all files from /build directory
File /r "${src}\*"
WriteUninstaller "${uninstaller}"
SectionEnd
Section "Desktop shortcut"
; Create desktop shortcut
CreateShortCut "$DESKTOP\${productName}.lnk" "$INSTDIR\${exec}" "" "$INSTDIR\icon.ico"
SectionEnd
Section "Autostart Entry"
; Create autostart entry
CreateShortCut "$SMSTARTUP\${productName}.lnk" "$INSTDIR\${exec}" "" "$INSTDIR\icon.ico"
SectionEnd
Section "Start Menu Entry"
; Create start menu entry
SetShellVarContext all
CreateShortCut "$SMPROGRAMS\${productName}.lnk" "$INSTDIR\${exec}" "" "$INSTDIR\icon.ico"
SectionEnd
; --------------------------------
; Finish page [custom]
; --------------------------------
Function finish
nsDialogs::Create 1018
${NSD_CreateLabel} 185 1u 210 30u "${productName} installation successfully finished."
${NSD_CreateCheckbox} 185 35u 100% 10u "Launch ${productName}"
Pop $LaunchAppCheckbox
${NSD_SetState} $LaunchAppCheckbox ${BST_CHECKED}
${NSD_CreateBitmap} 0 0 170 210 ""
Pop $Image
${NSD_SetImage} $Image $PLUGINSDIR\banner.bmp $ImageHandle
nsDialogs::Show
${NSD_FreeImage} $ImageHandle
FunctionEnd
Function finishEnd
; Save checkbox state on installer leave
${NSD_GetState} $LaunchAppCheckbox $LaunchAppCheckbox_State
; Launch the app, if the box is checked
${If} $LaunchAppCheckbox_State == ${BST_CHECKED}
Exec "$INSTDIR\${exec}"
${EndIf}
FunctionEnd
; --------------------------------
; Uninstaller
; --------------------------------
ShowUninstDetails nevershow
UninstallCaption "Uninstall ${productName}"
UninstallText "Don't like ${productName} anymore? Hit uninstall button."
UninstallIcon "${icon}"
; --------------------------------
; Page layout
; --------------------------------
UninstPage custom un.confirm un.confirmOnLeave
UninstPage instfiles
; --------------------------------
; Control variables
; --------------------------------
Var RemoveAppDataCheckbox
Var RemoveAppDataCheckbox_State
; --------------------------------
; Uninstaller init
; --------------------------------
Function un.onInit
; Check if the application is currently running, show message if it is
retryUninstall:
FindWindow $0 "Chrome_WidgetWin_1" "${productName}"
StrCmp $0 0 notRunning
MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION "${productName} is currently running. Please close the application to continue." /SD IDCANCEL IDRETRY retryUninstall
Abort
notRunning:
FunctionEnd
; --------------------------------
; Confirm page [custom]
; --------------------------------
Function un.confirm
nsDialogs::Create 1018
${NSD_CreateLabel} 1u 1u 100% 24u "If you really want to remove ${productName} from your computer press uninstall button."
${NSD_CreateCheckbox} 1u 35u 100% 10u "Remove also my ${productName} personal data"
Pop $RemoveAppDataCheckbox
nsDialogs::Show
FunctionEnd
Function un.confirmOnLeave
; Save checkbox state on page leave
${NSD_GetState} $RemoveAppDataCheckbox $RemoveAppDataCheckbox_State
FunctionEnd
; --------------------------------
; Uninstallation sections
; --------------------------------
Section "Uninstall"
; Remove registry entries
DeleteRegKey HKLM "${uninstkey}"
DeleteRegKey HKLM "${regkey}"
; Remove desktop shortcut
Delete "$DESKTOP\${productName}.lnk"
; Remove autostart entry
Delete "$SMSTARTUP\${productName}.lnk"
; Remove start menu entry
SetShellVarContext all
Delete "$SMPROGRAMS\${productName}.lnk"
; Remove whole directory from installation directory
RMDir /r "$INSTDIR"
; Remove also appData directory generated by your app if user checked this option
${If} $RemoveAppDataCheckbox_State == ${BST_CHECKED}
RMDir /r "$APPDATA\${productName}"
${EndIf}
SectionEnd

View file

@ -0,0 +1,26 @@
var istanbul = require('istanbul');
module.exports = function (runner, options) {
mocha.reporters.Base.call(this, runner);
var reporterOpts = { dir: 'coverage' },
reporters = ['text-summary', 'html'];
options = options || {};
if (options.reporters) reporters = options.reporters.split(',');
if (process.env.ISTANBUL_REPORTERS) reporters = process.env.ISTANBUL_REPORTERS.split(',');
if (options.reportDir) reporterOpts.dir = options.reportDir;
if (process.env.ISTANBUL_REPORT_DIR) reporterOpts.dir = process.env.ISTANBUL_REPORT_DIR;
runner.on('end', function(){
var cov = global.__coverage__ || {},
collector = new istanbul.Collector();
collector.add(cov);
reporters.forEach(function(reporter) {
istanbul.Report.create(reporter, reporterOpts).writeReport(collector, true);
});
});
};

18
scripts/travis-build.sh Executable file
View file

@ -0,0 +1,18 @@
#!/usr/bin/env bash
git clone https://github.com/creationix/nvm.git /tmp/.nvm
source /tmp/.nvm/nvm.sh
nvm install "$NODE_VERSION"
nvm use --delete-prefix "$NODE_VERSION"
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start
sleep 3
fi
node --version
npm --version
npm install
npm test & npm run e2e

56
src/background.js Normal file
View file

@ -0,0 +1,56 @@
// This is main process of Electron, started as first thing when your
// app starts. This script is running through entire life of your application.
// It doesn't have any windows which you can see on screen, but we can open
// window from here.
import path from 'path';
import url from 'url';
import { app, Menu } from 'electron';
import { devMenuTemplate } from './menu/dev_menu_template';
import { editMenuTemplate } from './menu/edit_menu_template';
import createWindow from './helpers/window';
// Special module holding environment variables which you declared
// in config/env_xxx.json file.
import env from './env';
var mainWindow;
var setApplicationMenu = function () {
var menus = [editMenuTemplate];
if (env.name !== 'production') {
menus.push(devMenuTemplate);
}
Menu.setApplicationMenu(Menu.buildFromTemplate(menus));
};
// Save userData in separate folders for each environment.
// Thanks to this you can use production and development versions of the app
// on same machine like those are two separate apps.
if (env.name !== 'production') {
var userDataPath = app.getPath('userData');
app.setPath('userData', userDataPath + ' (' + env.name + ')');
}
app.on('ready', function () {
setApplicationMenu();
var mainWindow = createWindow('main', {
width: 1000,
height: 600
});
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'app.html'),
protocol: 'file:',
slashes: true
}));
if (env.name === 'development') {
mainWindow.openDevTools();
}
});
app.on('window-all-closed', function () {
app.quit();
});

8
src/env.js Normal file
View file

@ -0,0 +1,8 @@
// Simple wrapper exposing environment variables to rest of the code.
import jetpack from 'fs-jetpack';
// The variables have been written to `env.json` by the build process.
var env = jetpack.cwd(__dirname).read('env.json', 'json');
export default env;

View file

@ -0,0 +1,7 @@
export var greet = function () {
return 'Hello World!';
};
export var bye = function () {
return 'See ya!';
};

View file

@ -0,0 +1,20 @@
import { expect } from 'chai';
import { greet, bye } from './hello_world';
import env from '../env';
describe("hello world", function () {
it("greets", function () {
expect(greet()).to.equal('Hello World!');
});
it("says goodbye", function () {
expect(bye()).to.equal('See ya!');
});
it("should load test environment variables", function () {
expect(env.name).to.equal('test');
expect(env.description).to.equal('Add here any environment specific stuff you like.');
});
});

83
src/helpers/window.js Normal file
View file

@ -0,0 +1,83 @@
// This helper remembers the size and position of your windows (and restores
// them in that place after app relaunch).
// Can be used for more than one window, just construct many
// instances of it and give each different name.
import { app, BrowserWindow, screen } from 'electron';
import jetpack from 'fs-jetpack';
export default function (name, options) {
var userDataDir = jetpack.cwd(app.getPath('userData'));
var stateStoreFile = 'window-state-' + name +'.json';
var defaultSize = {
width: options.width,
height: options.height
};
var state = {};
var win;
var restore = function () {
var restoredState = {};
try {
restoredState = userDataDir.read(stateStoreFile, 'json');
} catch (err) {
// For some reason json can't be read (might be corrupted).
// No worries, we have defaults.
}
return Object.assign({}, defaultSize, restoredState);
};
var getCurrentPosition = function () {
var position = win.getPosition();
var size = win.getSize();
return {
x: position[0],
y: position[1],
width: size[0],
height: size[1]
};
};
var windowWithinBounds = function (windowState, bounds) {
return windowState.x >= bounds.x &&
windowState.y >= bounds.y &&
windowState.x + windowState.width <= bounds.x + bounds.width &&
windowState.y + windowState.height <= bounds.y + bounds.height;
};
var resetToDefaults = function (windowState) {
var bounds = screen.getPrimaryDisplay().bounds;
return Object.assign({}, defaultSize, {
x: (bounds.width - defaultSize.width) / 2,
y: (bounds.height - defaultSize.height) / 2
});
};
var ensureVisibleOnSomeDisplay = function (windowState) {
var visible = screen.getAllDisplays().some(function (display) {
return windowWithinBounds(windowState, display.bounds);
});
if (!visible) {
// Window is partially or fully not visible now.
// Reset it to safe defaults.
return resetToDefaults(windowState);
}
return windowState;
};
var saveState = function () {
if (!win.isMinimized() && !win.isMaximized()) {
Object.assign(state, getCurrentPosition());
}
userDataDir.write(stateStoreFile, state, { atomic: true });
};
state = ensureVisibleOnSomeDisplay(restore());
win = new BrowserWindow(Object.assign({}, options, state));
win.on('close', saveState);
return win;
}

View file

@ -0,0 +1,24 @@
import { app, BrowserWindow } from 'electron';
export var devMenuTemplate = {
label: 'Development',
submenu: [{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click: function () {
BrowserWindow.getFocusedWindow().webContents.reloadIgnoringCache();
}
},{
label: 'Toggle DevTools',
accelerator: 'Alt+CmdOrCtrl+I',
click: function () {
BrowserWindow.getFocusedWindow().toggleDevTools();
}
},{
label: 'Quit',
accelerator: 'CmdOrCtrl+Q',
click: function () {
app.quit();
}
}]
};

View file

@ -0,0 +1,12 @@
export var editMenuTemplate = {
label: 'Edit',
submenu: [
{ label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:" },
{ label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" },
{ type: "separator" },
{ label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" },
{ label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" },
{ label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" },
{ label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:" }
]
};

View file

@ -1,136 +0,0 @@
'use strict';
var pathUtil = require('path');
var Q = require('q');
var gulp = require('gulp');
var less = require('gulp-less');
var jetpack = require('fs-jetpack');
var asar = require('asar');
var bundle = require('./bundle');
var generateSpecImportsFile = require('./generate_spec_imports');
var utils = require('../utils');
var projectDir = jetpack;
var srcDir = projectDir.cwd('./app');
var destDir = projectDir.cwd('./build');
var paths = {
copyFromAppDir: [
'./branding/**',
'./scripts/**',
'./lib/**',
'./spec.js',
'./app.html',
'./node_modules/**',
'./vendor/**',
'./images/**',
'./icons/**',
'./stylesheets/**/*.css',
'./fonts/**',
'./**/*.html',
'./**/*.+(jpg|png|svg)'
],
};
// -------------------------------------
// Tasks
// -------------------------------------
gulp.task('clean', function () {
return destDir.dirAsync('.', { empty: true });
});
var copyTask = function () {
return projectDir.copyAsync('app', destDir.path(), {
overwrite: true,
matching: paths.copyFromAppDir
});
};
gulp.task('copy', ['clean'], copyTask);
gulp.task('copy-watch', copyTask);
var bundleApplication = function () {
return Q.all([
bundle(srcDir.path('background.js'), destDir.path('background.js')),
bundle(srcDir.path('servers.js'), destDir.path('servers.js')),
bundle(srcDir.path('certificate.js'), destDir.path('certificate.js')),
bundle(srcDir.path('app.js'), destDir.path('app.js')),
]);
};
var bundleSpecs = function () {
return generateSpecImportsFile().then(function (specEntryPointPath) {
return Q.all([
bundle(srcDir.path('background.js'), destDir.path('background.js')),
bundle(specEntryPointPath, destDir.path('spec.js')),
]);
});
};
var bundleTask = function () {
if (utils.getEnvName() === 'test') {
return bundleSpecs();
}
return bundleApplication();
};
gulp.task('bundle', ['clean'], bundleTask);
gulp.task('bundle-watch', bundleTask);
var lessTask = function () {
return gulp.src('app/stylesheets/main.less')
.pipe(less())
.pipe(gulp.dest(destDir.path('stylesheets')));
};
gulp.task('less', ['clean'], lessTask);
gulp.task('less-watch', lessTask);
gulp.task('finalize', ['clean'], function () {
var manifest = srcDir.read('package.json', 'json');
// Add "dev" or "test" suffix to name, so Electron will write all data
// like cookies and localStorage in separate places for each environment.
switch (utils.getEnvName()) {
case 'development':
manifest.name += '-dev';
manifest.productName += ' Dev';
break;
case 'test':
manifest.name += '-test';
manifest.productName += ' Test';
break;
}
// Copy environment variables to package.json file for easy use
// in the running application. This is not official way of doing
// things, but also isn't prohibited ;)
manifest.env = projectDir.read('config/env_' + utils.getEnvName() + '.json', 'json');
destDir.write('package.json', manifest);
});
gulp.task('watch', function () {
gulp.watch('app/**/*.js', ['bundle-watch']);
gulp.watch(paths.copyFromAppDir, { cwd: 'app' }, ['copy-watch']);
gulp.watch('app/**/*.less', ['less-watch']);
});
var buildAsar = function () {
var deferred = Q.defer();
asar.createPackage(destDir.cwd(), projectDir.path('app.asar'), function () {
deferred.resolve();
});
deferred.resolve();
return deferred.promise;
};
gulp.task('build:asar', ['build'], buildAsar);
gulp.task('build', ['bundle', 'less', 'copy', 'finalize']);

View file

@ -1,52 +0,0 @@
'use strict';
var pathUtil = require('path');
var jetpack = require('fs-jetpack');
var rollup = require('rollup');
var Q = require('q');
var nodeBuiltInModules = ['assert', 'buffer', 'child_process', 'cluster',
'console', 'constants', 'crypto', 'dgram', 'dns', 'domain', 'events',
'fs', 'http', 'https', 'module', 'net', 'os', 'path', 'process', 'punycode',
'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'timers',
'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib'];
var electronBuiltInModules = ['electron'];
var npmModulesUsedInApp = function () {
var appManifest = require('../../app/package.json');
return Object.keys(appManifest.dependencies);
};
var generateExternalModulesList = function () {
return [].concat(nodeBuiltInModules, electronBuiltInModules, npmModulesUsedInApp());
};
module.exports = function (src, dest) {
var deferred = Q.defer();
rollup.rollup({
entry: src,
external: generateExternalModulesList(),
}).then(function (bundle) {
var jsFile = pathUtil.basename(dest);
var result = bundle.generate({
format: 'cjs',
sourceMap: true,
sourceMapFile: jsFile,
});
// Wrap code in self invoking function so the variables don't
// pollute the global namespace.
var isolatedCode = '(function () {' + result.code + '\n}());';
return Q.all([
jetpack.writeAsync(dest, isolatedCode + '\n//# sourceMappingURL=' + jsFile + '.map'),
jetpack.writeAsync(dest + '.map', result.map.toString()),
]);
}).then(function () {
deferred.resolve();
}).catch(function (err) {
console.error('Build: Error during rollup', err.stack);
});
return deferred.promise;
};

View file

@ -1,28 +0,0 @@
// Spec files are scattered through the whole project. Here we're searching
// for them and generate one entry file which will run all the tests.
'use strict';
var jetpack = require('fs-jetpack');
var srcDir = jetpack.cwd('app');
var fileName = 'spec.js';
var fileBanner = "// This file is generated automatically.\n"
+ "// All your modifications to it will be lost (so don't do it).\n";
var whatToInclude = [
'*.spec.js',
'!node_modules/**',
];
module.exports = function () {
return srcDir.findAsync('.', { matching: whatToInclude }, 'relativePath')
.then(function (specPaths) {
var fileContent = specPaths.map(function (path) {
return 'import "' + path + '";';
}).join('\n');
return srcDir.writeAsync(fileName, fileBanner + fileContent);
})
.then(function () {
return srcDir.path(fileName);
});
};

53
tasks/build_app.js Normal file
View file

@ -0,0 +1,53 @@
'use strict';
var gulp = require('gulp');
var less = require('gulp-less');
var watch = require('gulp-watch');
var batch = require('gulp-batch');
var plumber = require('gulp-plumber');
var jetpack = require('fs-jetpack');
var bundle = require('./bundle');
var utils = require('./utils');
var projectDir = jetpack;
var srcDir = jetpack.cwd('./src');
var destDir = jetpack.cwd('./app');
gulp.task('bundle', function () {
return Promise.all([
bundle(srcDir.path('background.js'), destDir.path('background.js')),
bundle(srcDir.path('app.js'), destDir.path('app.js')),
]);
});
gulp.task('less', function () {
return gulp.src(srcDir.path('stylesheets/main.less'))
.pipe(plumber())
.pipe(less())
.pipe(gulp.dest(destDir.path('stylesheets')));
});
gulp.task('environment', function () {
var configFile = 'config/env_' + utils.getEnvName() + '.json';
projectDir.copy(configFile, destDir.path('env.json'), { overwrite: true });
});
gulp.task('watch', function () {
var beepOnError = function (done) {
return function (err) {
if (err) {
utils.beepSound();
}
done(err);
};
};
watch('src/**/*.js', batch(function (events, done) {
gulp.start('bundle', beepOnError(done));
}));
watch('src/**/*.less', batch(function (events, done) {
gulp.start('less', beepOnError(done));
}));
});
gulp.task('build', ['bundle', 'less', 'environment']);

51
tasks/build_tests.js Normal file
View file

@ -0,0 +1,51 @@
'use strict';
var gulp = require('gulp');
var jetpack = require('fs-jetpack');
var bundle = require('./bundle');
var istanbul = require('rollup-plugin-istanbul');
// Spec files are scattered through the whole project. Here we're searching
// for them and generate one entry file which will run all the tests.
var generateEntryFile = function (dir, destFileName, filePattern) {
var fileBanner = "// This file is generated automatically.\n"
+ "// All modifications will be lost.\n";
return dir.findAsync('.', { matching: filePattern })
.then(function (specPaths) {
var fileContent = specPaths.map(function (path) {
return 'import "./' + path.replace(/\\/g, '/') + '";';
}).join('\n');
return dir.writeAsync(destFileName, fileBanner + fileContent);
})
.then(function () {
return dir.path(destFileName);
});
};
gulp.task('build-unit', ['environment'], function () {
var srcDir = jetpack.cwd('src');
var destDir = jetpack.cwd('app');
return generateEntryFile(srcDir, 'specs.js.autogenerated', '*.spec.js')
.then(function (entryFilePath) {
return bundle(entryFilePath, destDir.path('specs.js.autogenerated'), {
rollupPlugins: [
istanbul({
exclude: ['**/*.spec.js', '**/specs.js.autogenerated'],
sourceMap: true
})
]
});
});
});
gulp.task('build-e2e', ['build'], function () {
var srcDir = jetpack.cwd('e2e');
var destDir = jetpack.cwd('app');
return generateEntryFile(srcDir, 'e2e.js.autogenerated', '*.e2e.js')
.then(function (entryFilePath) {
return bundle(entryFilePath, destDir.path('e2e.js.autogenerated'));
});
});

53
tasks/bundle.js Normal file
View file

@ -0,0 +1,53 @@
'use strict';
var path = require('path');
var jetpack = require('fs-jetpack');
var rollup = require('rollup').rollup;
var nodeBuiltInModules = ['assert', 'buffer', 'child_process', 'cluster',
'console', 'constants', 'crypto', 'dgram', 'dns', 'domain', 'events',
'fs', 'http', 'https', 'module', 'net', 'os', 'path', 'process', 'punycode',
'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'timers',
'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib'];
var electronBuiltInModules = ['electron'];
var generateExternalModulesList = function () {
var appManifest = jetpack.read('./package.json', 'json');
return [].concat(
nodeBuiltInModules,
electronBuiltInModules,
Object.keys(appManifest.dependencies),
Object.keys(appManifest.devDependencies)
);
};
var cached = {};
module.exports = function (src, dest, opts) {
opts = opts || {};
opts.rollupPlugins = opts.rollupPlugins || [];
return rollup({
entry: src,
external: generateExternalModulesList(),
cache: cached[src],
plugins: opts.rollupPlugins,
})
.then(function (bundle) {
cached[src] = bundle;
var jsFile = path.basename(dest);
var result = bundle.generate({
format: 'cjs',
sourceMap: true,
sourceMapFile: jsFile,
});
// Wrap code in self invoking function so the variables don't
// pollute the global namespace.
var isolatedCode = '(function () {' + result.code + '\n}());';
return Promise.all([
jetpack.writeAsync(dest, isolatedCode + '\n//# sourceMappingURL=' + jsFile + '.map'),
jetpack.writeAsync(dest + '.map', result.map.toString()),
]);
});
};

View file

@ -1,95 +0,0 @@
// Install native module from npm and compile it for Electron.
// Usage: npm run install-native -- name_of_native_module
'use strict';
var childProcess = require('child_process');
var Q = require('q');
var appDir = require('fs-jetpack').cwd(__dirname, '../app');
var argv = require('yargs').argv;
var utils = require('./utils');
var ensureElectronRebuildInstalled = function () {
var deferred = Q.defer();
try {
// If require is successful it means module is already installed.
require('electron-rebuild');
deferred.resolve();
} catch (err) {
childProcess.spawn(utils.spawnablePath('npm'), [
'install', '--save-dev', 'electron-rebuild'
], {
stdio: 'inherit'
})
.on('error', deferred.reject)
.on('close', deferred.resolve);
}
return deferred.promise;
};
var ensurePostinstallRunsElectronRebuild = function () {
var postinstallScript = 'node ../tasks/rebuild_native_modules';
var appManifest = appDir.read('package.json', 'json');
if (typeof appManifest.scripts === 'undefined') {
appManifest.scripts = {};
}
// Let's do it 100% bulletproof and check if programmer didn't
// pust some custom stuff into postinstall script already.
if (typeof appManifest.scripts.postinstall === 'undefined') {
appManifest.scripts.postinstall = postinstallScript;
appDir.write('package.json', appManifest);
} else if (appManifest.scripts.postinstall.indexOf(postinstallScript) === -1) {
appManifest.scripts.postinstall += ' && ' + postinstallScript;
appDir.write('package.json', appManifest);
}
return Q();
};
var installNativeModule = function () {
var deferred = Q.defer();
var moduleName = argv._[0];
if (!moduleName) {
deferred.reject('Module name not specified! Correct usage is "npm run install-native -- name_of_native_module" (remember about space after "--").');
} else {
childProcess.spawn(utils.spawnablePath('npm'), [
'install', '--save', moduleName
], {
cwd: appDir.cwd(),
stdio: 'inherit'
})
.on('error', deferred.reject)
.on('close', deferred.resolve);
}
return deferred.promise;
};
var runRebuild = function () {
var deferred = Q.defer();
childProcess.spawn(utils.spawnablePath('npm'), [
'run', 'postinstall'
], {
cwd: appDir.cwd(),
stdio: 'inherit'
})
.on('error', deferred.reject)
.on('close', deferred.resolve);
return deferred.promise;
};
ensureElectronRebuildInstalled()
.then(ensurePostinstallRunsElectronRebuild)
.then(installNativeModule)
.then(runRebuild)
.catch(function (err) {
console.error(err);
});

View file

@ -1,33 +0,0 @@
// Rebuilds native node modules for Electron.
// More: https://github.com/atom/electron/blob/master/docs/tutorial/using-native-node-modules.md
'use strict';
var path = require('path');
var Q = require('q');
var electron = require('electron');
var electronPackage = require('electron/package.json');
var rebuild = require('electron-rebuild');
var pathToElectronNativeModules = path.join(__dirname, '../app/node_modules');
rebuild.shouldRebuildNativeModules(electron)
.then(function (shouldBuild) {
if (!shouldBuild) {
return true;
}
console.log('Rebuilding native modules for Electron...');
return rebuild.installNodeHeaders(electronPackage.version)
.then(function () {
return rebuild.rebuildNativeModules(electronPackage.version, pathToElectronNativeModules);
});
})
.then(function () {
console.log('Rebuilding complete.');
})
.catch(function (err) {
console.error("Rebuilding error!");
console.error(err);
});

View file

@ -1,160 +0,0 @@
'use strict';
var Q = require('q');
var gulpUtil = require('gulp-util');
var childProcess = require('child_process');
var jetpack = require('fs-jetpack');
var asar = require('asar');
var utils = require('../utils');
var projectDir;
var releasesDir;
var packName;
var packDir;
var tmpDir;
var readyAppDir;
var manifest;
var init = function () {
projectDir = jetpack;
tmpDir = projectDir.dir('./tmp', { empty: true });
releasesDir = projectDir.dir('./releases');
manifest = projectDir.read('app/package.json', 'json');
packName = utils.getReleasePackageName(manifest);
packDir = tmpDir.dir(packName);
readyAppDir = packDir.cwd('opt', manifest.name);
return new Q();
};
var copyRuntime = function () {
return projectDir.copyAsync('node_modules/electron/dist', readyAppDir.path(), { overwrite: true });
};
var packageBuiltApp = function () {
var deferred = Q.defer();
asar.createPackage(projectDir.path('build'), readyAppDir.path('resources/app.asar'), function () {
deferred.resolve();
});
return deferred.promise;
};
var finalize = function () {
// Create .desktop file from the template
var desktop = projectDir.read('resources/linux/app.desktop');
desktop = utils.replace(desktop, {
name: manifest.name,
productName: manifest.productName,
description: manifest.description,
version: manifest.version,
author: manifest.author
});
packDir.write('usr/share/applications/' + manifest.name + '.desktop', desktop);
// Copy icon
projectDir.copy('app/images/linux/icon.png', readyAppDir.path('icon.png'));
// Copy dictionaries
projectDir.copy('dictionaries', readyAppDir.path('resources/dictionaries'));
return new Q();
};
var renameApp = function () {
return readyAppDir.renameAsync('electron', manifest.name);
};
var packToDebFile = function () {
var deferred = Q.defer();
var debFileName = packName + '.deb';
var debPath = releasesDir.path(debFileName);
gulpUtil.log('Creating DEB package... (' + debFileName + ')');
// Counting size of the app in KiB
var appSize = Math.round(readyAppDir.inspectTree('.').size / 1024);
// Preparing debian control file
var control = projectDir.read('resources/linux/DEBIAN/control');
control = utils.replace(control, {
name: manifest.name,
description: manifest.description,
version: manifest.version,
author: manifest.author,
size: appSize
});
packDir.write('DEBIAN/control', control);
// Build the package...
childProcess.exec('fakeroot dpkg-deb -Zxz --build ' + packDir.path().replace(/\s/g, '\\ ') + ' ' + debPath.replace(/\s/g, '\\ '),
function (error, stdout, stderr) {
if (error || stderr) {
console.log('ERROR while building DEB package:');
console.log(error);
console.log(stderr);
} else {
gulpUtil.log('DEB package ready!', debPath);
}
deferred.resolve();
});
return deferred.promise;
};
var packToRpmFile = function () {
var deferred = Q.defer();
var rpmFileName = packName + '.rpm';
var rpmPath = releasesDir.path(rpmFileName);
gulpUtil.log('Creating RPM package... (' + rpmFileName + ')');
// Preparing RPM Spec file
var spec = projectDir.read('resources/linux/RHEL/app.spec');
spec = utils.replace(spec, {
name: manifest.name,
description: manifest.description,
version: process.env.TRAVIS_TAG || process.env.APPVEYOR_REPO_TAG_NAME || manifest.version,
author: manifest.author
});
tmpDir.write('SPECS/app.spec', spec);
// Build the package...
childProcess.exec('fakeroot rpmbuild --quiet -D "_topdir `pwd`/tmp" -D "_builddir ' + packDir.path() + '" -bb ' + tmpDir.path('SPECS/app.spec').replace(/\s/g, '\\ '),
function (error, stdout, stderr) {
if (error || stderr) {
console.log('ERROR while building RPM package:');
console.log(error);
console.log(stderr);
} else {
// Copy to release directory
tmpDir.copy('RPMS/x86_64', releasesDir.path(), {
matching: '*.rpm',
overwrite: true
});
gulpUtil.log('RPM package ready!', rpmPath);
}
deferred.resolve();
});
return deferred.promise;
};
var cleanClutter = function () {
return tmpDir.removeAsync('.');
};
module.exports = function () {
return init()
.then(copyRuntime)
.then(packageBuiltApp)
.then(finalize)
.then(renameApp)
.then(packToDebFile)
.then(packToRpmFile)
.then(cleanClutter)
.catch(console.error);
};

View file

@ -1,196 +0,0 @@
'use strict';
var Q = require('q');
var gulpUtil = require('gulp-util');
var jetpack = require('fs-jetpack');
var asar = require('asar');
var utils = require('../utils');
var child_process = require('child_process');
var projectDir;
var releasesDir;
var tmpDir;
var finalAppDir;
var manifest;
var init = function () {
projectDir = jetpack;
tmpDir = projectDir.dir('./tmp', { empty: true });
releasesDir = projectDir.dir('./releases');
manifest = projectDir.read('app/package.json', 'json');
finalAppDir = tmpDir.cwd(manifest.productName + '.app');
return new Q();
};
var copyRuntime = function () {
return projectDir.copyAsync('node_modules/electron/dist/Electron.app', finalAppDir.path());
};
var cleanupRuntime = function () {
finalAppDir.remove('Contents/Resources/default_app');
finalAppDir.remove('Contents/Resources/atom.icns');
return new Q();
};
var packageBuiltApp = function () {
var deferred = Q.defer();
asar.createPackage(projectDir.path('build'), finalAppDir.path('Contents/Resources/app.asar'), function () {
deferred.resolve();
});
return deferred.promise;
};
var finalize = function () {
// Prepare main Info.plist
var info = projectDir.read('resources/osx/Info.plist');
info = utils.replace(info, {
productName: manifest.productName,
identifier: manifest.identifier,
version: manifest.version,
build: manifest.build,
copyright: manifest.copyright,
LSApplicationCategoryType: manifest.LSApplicationCategoryType
});
finalAppDir.write('Contents/Info.plist', info);
// Prepare Info.plist of Helper apps
[' EH', ' NP', ''].forEach(function (helper_suffix) {
info = projectDir.read('resources/osx/helper_apps/Info' + helper_suffix + '.plist');
info = utils.replace(info, {
productName: manifest.productName,
identifier: manifest.identifier
});
finalAppDir.write('Contents/Frameworks/Electron Helper' + helper_suffix + '.app/Contents/Info.plist', info);
});
// Copy icon
projectDir.copy('app/images/osx/icon.icns', finalAppDir.path('Contents/Resources/icon.icns'));
// Copy dictionaries
projectDir.copy('dictionaries', finalAppDir.path('Contents/Resources/dictionaries'));
return new Q();
};
var renameApp = function () {
// Rename helpers
[' Helper EH', ' Helper NP', ' Helper'].forEach(function (helper_suffix) {
finalAppDir.rename('Contents/Frameworks/Electron' + helper_suffix + '.app/Contents/MacOS/Electron' + helper_suffix, manifest.productName + helper_suffix );
finalAppDir.rename('Contents/Frameworks/Electron' + helper_suffix + '.app', manifest.productName + helper_suffix + '.app');
});
// Rename application
finalAppDir.rename('Contents/MacOS/Electron', manifest.productName);
return new Q();
};
var signApp = function () {
var identity = utils.getSigningId(manifest);
var MASIdentity = utils.getMASSigningId(manifest);
var MASInstallerIdentity = utils.getMASInstallerSigningId(manifest);
if (utils.releaseForMAS()) {
if (!MASIdentity || !MASInstallerIdentity) {
gulpUtil.log('--mas-sign and --mas-installer-sign are required to release for Mac App Store!');
process.exit(0);
}
var cmds = [
'codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist -v "' + finalAppDir.path() + '/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib"',
'codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist -v "' + finalAppDir.path() + '/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libnode.dylib"',
'codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist -v "' + finalAppDir.path() + '/Contents/Frameworks/Electron Framework.framework/Versions/A"',
'codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist -v "' + finalAppDir.path() + '/Contents/Frameworks/' + manifest.productName + ' Helper.app/"',
'codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist -v "' + finalAppDir.path() + '/Contents/Frameworks/' + manifest.productName + ' Helper EH.app/"',
'codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist -v "' + finalAppDir.path() + '/Contents/Frameworks/' + manifest.productName + ' Helper NP.app/"'
];
if (finalAppDir.exists('Contents/Frameworks/Squirrel.framework/Versions/A')) {
// # Signing a non-MAS build.
cmds.push('codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist "' + finalAppDir.path() + '/Contents/Frameworks/Mantle.framework/Versions/A"');
cmds.push('codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist "' + finalAppDir.path() + '/Contents/Frameworks/ReactiveCocoa.framework/Versions/A"');
cmds.push('codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist "' + finalAppDir.path() + '/Contents/Frameworks/Squirrel.framework/Versions/A"');
}
cmds.push('codesign -f -s "' + MASIdentity + '" --entitlements resources/osx/parent.plist -v "' + finalAppDir.path() + '"');
cmds.push('productbuild --component "' + finalAppDir.path() + '" /Applications --sign "' + MASInstallerIdentity + '" "' + releasesDir.path(manifest.productName + '.pkg') + '"');
var result = new Q();
cmds.forEach(function (cmd) {
result = result.then(function(result) {
gulpUtil.log('Signing with:', cmd);
return Q.nfcall(child_process.exec, cmd);
});
});
result = result.then(function(result) {
return new Q();
});
return result;
} else if (identity) {
var cmd = 'codesign --deep --force --sign "' + identity + '" "' + finalAppDir.path() + '"';
gulpUtil.log('Signing with:', cmd);
return Q.nfcall(child_process.exec, cmd);
} else {
return new Q();
}
};
var packToDmgFile = function () {
if (utils.releaseForMAS()) {
return new Q();
}
var deferred = Q.defer();
var appdmg = require('appdmg');
var dmgName = utils.getReleasePackageName(manifest) + '.dmg';
// Prepare appdmg config
var dmgManifest = projectDir.read('resources/osx/appdmg.json');
dmgManifest = utils.replace(dmgManifest, {
productName: manifest.productName,
appPath: finalAppDir.path(),
dmgIcon: projectDir.path('app/images/osx/dmg-icon.icns'),
dmgBackground: projectDir.path('app/images/osx/dmg-background.png')
});
tmpDir.write('appdmg.json', dmgManifest);
// Delete DMG file with this name if already exists
releasesDir.remove(dmgName);
gulpUtil.log('Packaging to DMG file... (' + dmgName + ')');
var readyDmgPath = releasesDir.path(dmgName);
appdmg({
source: tmpDir.path('appdmg.json'),
target: readyDmgPath
})
.on('error', function (err) {
console.error(err);
})
.on('finish', function () {
gulpUtil.log('DMG file ready!', readyDmgPath);
deferred.resolve();
});
return deferred.promise;
};
var cleanClutter = function () {
return tmpDir.removeAsync('.');
};
module.exports = function () {
return init()
.then(copyRuntime)
.then(cleanupRuntime)
.then(packageBuiltApp)
.then(finalize)
.then(renameApp)
.then(signApp)
.then(packToDmgFile)
.then(cleanClutter)
.catch(console.error);
};

View file

@ -1,14 +0,0 @@
'use strict';
var gulp = require('gulp');
var utils = require('../utils');
var releaseForOs = {
osx: require('./osx'),
linux: require('./linux'),
windows: require('./windows'),
};
gulp.task('release', ['build'], function () {
return releaseForOs[utils.os()]();
});

View file

@ -1,133 +0,0 @@
'use strict';
var Q = require('q');
var gulpUtil = require('gulp-util');
var childProcess = require('child_process');
var jetpack = require('fs-jetpack');
var asar = require('asar');
var utils = require('../utils');
var projectDir;
var tmpDir;
var releasesDir;
var readyAppDir;
var manifest;
var init = function () {
projectDir = jetpack;
tmpDir = projectDir.dir('./tmp', { empty: true });
releasesDir = projectDir.dir('./releases');
manifest = projectDir.read('app/package.json', 'json');
readyAppDir = tmpDir.cwd(manifest.name);
return new Q();
};
var copyRuntime = function () {
return projectDir.copyAsync('node_modules/electron/dist', readyAppDir.path(), { overwrite: true });
};
var cleanupRuntime = function () {
return readyAppDir.removeAsync('resources/default_app');
};
var packageBuiltApp = function () {
var deferred = Q.defer();
asar.createPackage(projectDir.path('build'), readyAppDir.path('resources/app.asar'), function () {
deferred.resolve();
});
return deferred.promise;
};
var finalize = function () {
var deferred = Q.defer();
projectDir.copy('app/images/windows/icon.ico', readyAppDir.path('icon.ico'));
// Replace Electron icon for your own.
var rcedit = require('rcedit');
rcedit(readyAppDir.path('electron.exe'), {
'icon': projectDir.path('app/images/windows/icon.ico'),
'version-string': {
'ProductName': manifest.productName,
'FileDescription': manifest.description,
'ProductVersion': manifest.version,
'CompanyName': manifest.author, // it might be better to add another field to package.json for this
'LegalCopyright': manifest.copyright,
'OriginalFilename': manifest.productName + '.exe'
}
}, function (err) {
if (!err) {
deferred.resolve();
}
});
return deferred.promise;
};
var renameApp = function () {
return readyAppDir.renameAsync('electron.exe', manifest.productName + '.exe');
};
var createInstaller = function () {
var deferred = Q.defer();
var finalPackageName = utils.getReleasePackageName(manifest) + '.exe';
var installScript = projectDir.read('resources/windows/installer.nsi');
installScript = utils.replace(installScript, {
name: manifest.name,
productName: manifest.productName,
author: manifest.author,
version: manifest.version,
src: readyAppDir.path(),
dest: releasesDir.path(finalPackageName),
icon: readyAppDir.path('icon.ico'),
setupIcon: projectDir.path('app/images/windows/setup-icon.ico'),
banner: projectDir.path('app/images/windows/setup-banner.bmp'),
});
tmpDir.write('installer.nsi', installScript);
gulpUtil.log('Building installer with NSIS... (' + finalPackageName + ')');
// Remove destination file if already exists.
releasesDir.remove(finalPackageName);
// Note: NSIS have to be added to PATH (environment variables).
var nsis = childProcess.spawn('makensis', [
tmpDir.path('installer.nsi')
], {
stdio: 'inherit'
});
nsis.on('error', function (err) {
if (err.message === 'spawn makensis ENOENT') {
throw 'Can\'t find NSIS. Are you sure you\'ve installed it and added to PATH environment variable?';
} else {
throw err;
}
});
nsis.on('close', function () {
gulpUtil.log('Installer ready!', releasesDir.path(finalPackageName));
deferred.resolve();
});
return deferred.promise;
};
var cleanClutter = function () {
return tmpDir.removeAsync('.');
};
module.exports = function () {
return init()
.then(copyRuntime)
.then(cleanupRuntime)
.then(packageBuiltApp)
.then(finalize)
.then(renameApp)
.then(createInstaller)
.then(cleanClutter)
.catch(console.error);
};

View file

@ -1,64 +1,15 @@
'use strict';
var Q = require('q');
var electron = require('electron');
var pathUtil = require('path');
var childProcess = require('child_process');
var kill = require('tree-kill');
var utils = require('./utils');
var watch;
var electron = require('electron');
var gulp = require('gulp');
var gulpPath = pathUtil.resolve('./node_modules/.bin/gulp');
var runBuild = function () {
var deferred = Q.defer();
var build = childProcess.spawn(utils.spawnablePath(gulpPath), [
'build',
'--env=' + utils.getEnvName(),
'--color'
], {
gulp.task('start', ['build', 'watch'], function () {
childProcess.spawn(electron, ['.'], {
stdio: 'inherit'
});
build.on('close', function (code) {
deferred.resolve();
});
return deferred.promise;
};
var runGulpWatch = function () {
watch = childProcess.spawn(utils.spawnablePath(gulpPath), [
'watch',
'--env=' + utils.getEnvName(),
'--color'
], {
stdio: 'inherit'
});
watch.on('close', function (code) {
// Gulp watch exits when error occured during build.
// Just respawn it then.
runGulpWatch();
});
};
var runApp = function () {
var app = childProcess.spawn(electron, ['./build'], {
stdio: 'inherit'
});
app.on('close', function (code) {
})
.on('close', function () {
// User closed the app. Kill the host process.
kill(watch.pid, 'SIGKILL', function () {
process.exit();
});
process.exit();
});
};
runBuild()
.then(function () {
runGulpWatch();
runApp();
});

View file

@ -1,63 +1,11 @@
'use strict';
var argv = require('yargs').argv;
var os = require('os');
var argv = require('minimist')(process.argv);
module.exports.os = function () {
switch (os.platform()) {
case 'darwin':
return 'osx';
case 'linux':
return 'linux';
case 'win32':
return 'windows';
}
return 'unsupported';
};
module.exports.replace = function (str, patterns) {
Object.keys(patterns).forEach(function (pattern) {
var matcher = new RegExp('{{' + pattern + '}}', 'g');
str = str.replace(matcher, patterns[pattern]);
});
return str;
};
module.exports.getReleasePackageName = function(manifest) {
return module.exports.replace(manifest.packageNameTemplate, {
name: manifest.name,
version: process.env.TRAVIS_TAG || process.env.APPVEYOR_REPO_TAG_NAME || manifest.version,
build: process.env.TRAVIS_BUILD_NUMBER || process.env.APPVEYOR_BUILD_NUMBER || manifest.build,
productName: manifest.productName,
platform: process.platform,
arch: process.arch
});
}
module.exports.getEnvName = function () {
exports.getEnvName = function () {
return argv.env || 'development';
};
module.exports.getSigningId = function (manifest) {
return argv.sign || (manifest.codeSignIdentity ? manifest.codeSignIdentity.dmg : undefined);
};
module.exports.getMASSigningId = function (manifest) {
return argv['mas-sign'] || (manifest.codeSignIdentity ? manifest.codeSignIdentity.MAS : undefined);
};
module.exports.getMASInstallerSigningId = function (manifest) {
return argv['mas-installer-sign'] || (manifest.codeSignIdentity ? manifest.codeSignIdentity.MASInstaller : undefined);
};
module.exports.releaseForMAS = function () {
return !!argv.mas;
};
// Fixes https://github.com/nodejs/node-v0.x-archive/issues/2318
module.exports.spawnablePath = function (path) {
if (process.platform === 'win32') {
return path + '.cmd';
}
return path;
exports.beepSound = function () {
process.stdout.write('\u0007');
};