├── .gitignore ├── wrapper ├── package.json ├── index.js └── lib │ └── common.js ├── index.js ├── test.js ├── lib ├── api.BrowserWindow.js ├── api.Base.js ├── shell.js ├── api.js └── common.js ├── LICENSE-ATOM_SHELL ├── LICENSE ├── package.json ├── LICENSE-CHROMIUM ├── README.md └── script └── bootstrap.js /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | node_modules 3 | .*.swp 4 | -------------------------------------------------------------------------------- /wrapper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wrapper", 3 | "productName": "Node Shell's Atom Shell wrapper app", 4 | "version": "0.1.1", 5 | "main": "index.js", 6 | "dependencies": { 7 | "async": "0.7.x", 8 | "dnode": "1.2.x" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-shell: index.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-07-18 spolu Creation 10 | */ 11 | "use strict" 12 | 13 | module.exports = require('./lib/shell.js').shell({}).spawn_shell; 14 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var common = require('./lib/common.js'); 2 | 3 | require('./index.js')(function(err, api) { 4 | var win = new api.BrowserWindow({}).loadUrl('http://google.com'); 5 | }); 6 | 7 | // SAFETY NET (kills the process and the spawns) 8 | process.on('uncaughtException', function (err) { 9 | common.fatal(err); 10 | }); 11 | 12 | var sig_handler = function() { 13 | common.exit(0); 14 | }; 15 | process.on('SIGHUP', sig_handler); 16 | process.on('SIGINT', sig_handler); 17 | process.on('SIGQUIT', sig_handler); 18 | process.on('SIGABRT', sig_handler); 19 | process.on('SIGTERM', sig_handler); 20 | -------------------------------------------------------------------------------- /lib/api.BrowserWindow.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-shell: api.BrowserWindow.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-07-18 spolu Creation 10 | */ 11 | "use strict" 12 | 13 | var util = require('util'); 14 | 15 | function BrowserWindow(api, options) { 16 | require('./api.Base.js').Base.call(this, api, 'BrowserWindow', [options]); 17 | }; 18 | 19 | util.inherits(BrowserWindow, require('./api.Base.js').Base); 20 | 21 | BrowserWindow.prototype.loadUrl = function loadUrl(url) { 22 | this.call('loadUrl', [url], function() {}); 23 | }; 24 | 25 | BrowserWindow.prototype.show = function show(url) { 26 | this.call('show', [], function() {}); 27 | }; 28 | 29 | exports.BrowserWindow = BrowserWindow; 30 | 31 | -------------------------------------------------------------------------------- /LICENSE-ATOM_SHELL: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Stanislas Polu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/api.Base.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-shell: api.Base.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-07-18 spolu Creation 10 | */ 11 | "use strict" 12 | 13 | var common = require('./common.js'); 14 | 15 | var events = require('events'); 16 | var util = require('util'); 17 | 18 | function Base(api, type, args) { 19 | events.EventEmitter.call(this); 20 | 21 | this.api = api; 22 | this.type = type; 23 | this.object_id = null; 24 | 25 | var that = this; 26 | this.api.create(type, this, args, function(err, object_id) { 27 | common.log.out('Base ' + type + ' [' + object_id + ']'); 28 | that.object_id = object_id; 29 | that.emit('_ready'); 30 | }); 31 | }; 32 | 33 | util.inherits(Base, events.EventEmitter); 34 | 35 | Base.prototype.pre = function pre(cb_) { 36 | var that = this; 37 | if(!that.object_id) { 38 | that.on('_ready', function() { 39 | return cb_(); 40 | }); 41 | } 42 | else { 43 | return cb_(); 44 | } 45 | }; 46 | 47 | Base.prototype.call = function call(method, args, cb_) { 48 | var that = this; 49 | that.pre(function() { 50 | that.api.call(that.object_id, method, args, cb_); 51 | }); 52 | }; 53 | 54 | exports.Base = Base; 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-shell", 3 | "description": "Atom Shell bindings for nodeJS", 4 | "main": "./index.js", 5 | "author": { 6 | "name": "Stanislas Polu", 7 | "email": "polu.stanislas@gmail.com", 8 | "url": "http://github.com/spolu" 9 | }, 10 | "version": "0.1.5", 11 | "dependencies": { 12 | "async": "0.7.x", 13 | "mkdirp": "0.3.x", 14 | "fs-extra": "0.8.x", 15 | "optimist": "0.6.x", 16 | "request": "2.36.x", 17 | "dnode": "1.2.x" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "http://github.com/spolu/node-shell.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/spolu/node-shell/issues" 25 | }, 26 | "licenses": [ 27 | { 28 | "type": "MIT", 29 | "url": "https://github.com/spolu/node-shell/raw/master/LICENSE" 30 | } 31 | ], 32 | "ATOM_SHELL_VERSION": "v0.13.3", 33 | "ATOM_SHELL_BASE_URL": "https://github.com/atom/atom-shell/releases/download/", 34 | "ATOM_SHELL_RELEASE_PLATFORMS": { 35 | "darwin": { 36 | "x64": "darwin-x64" 37 | }, 38 | "linux": { 39 | "ia32": "linux-ia32", 40 | "x64": "linux-x64" 41 | }, 42 | "win32": { 43 | "ia32": "win32-ia32", 44 | "x64": "win32-ia32" 45 | } 46 | }, 47 | "scripts": { 48 | "postinstall": "node script/bootstrap.js" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE-CHROMIUM: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above 10 | // copyright notice, this list of conditions and the following disclaimer 11 | // in the documentation and/or other materials provided with the 12 | // distribution. 13 | // * Neither the name of Google Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /wrapper/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-shell: [wrapper] index.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-07-18 spolu Creation 10 | */ 11 | "use strict" 12 | 13 | var common = require('./lib/common.js'); 14 | var async = require('async'); 15 | 16 | var path = require('path'); 17 | var dnode = require('dnode'); 18 | var net = require('net'); 19 | 20 | /* atom-shell API */ 21 | var app = require('app'); 22 | var Menu = require('menu'); 23 | var MenuItem = require('menu-item'); 24 | var BrowserWindow = require('browser-window'); 25 | 26 | 27 | 28 | var atom_sock = process.argv[1]; 29 | var node_sock = process.argv[2]; 30 | 31 | 32 | var registry = {}; 33 | var salt = Date.now().toString(); 34 | var count = 0; 35 | 36 | var next_id = function() { 37 | return salt + '-' + (++count); 38 | }; 39 | 40 | 41 | var server = net.createServer(function(c) { 42 | var rpc_srv = dnode({ 43 | create: function(type, args, cb_) { 44 | if(type === 'BrowserWindow') { 45 | var object_id = next_id(); 46 | registry[object_id] = { 47 | type: type, 48 | object: new BrowserWindow(args[0]) 49 | }; 50 | return cb_(null, object_id); 51 | } 52 | }, 53 | call: function(object_id, method, args, cb_) { 54 | if(registry[object_id]) { 55 | registry[object_id].object[method].apply(registry[object_id].object, args); 56 | return cb_(); 57 | } 58 | } 59 | }, { weak: false }); 60 | c.pipe(rpc_srv).pipe(c); 61 | }); 62 | 63 | server.listen(atom_sock); 64 | 65 | 66 | app.on('ready', function() { 67 | 68 | var d = dnode({}, { weak: false }); 69 | d.on('remote', function (remote) { 70 | common.log.out('node-shell > wrapper [' + process.pid + ']'); 71 | remote.handshake(); 72 | /* 73 | remote.loopback('foo', function(err, s) { 74 | common.log.out('loopback replied : ' + s); 75 | //d.end(); 76 | }); 77 | */ 78 | }); 79 | 80 | var c = net.connect(node_sock); 81 | c.pipe(d).pipe(c); 82 | }); 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-shell 2 | ========== 3 | 4 | `node-shell` lets you create cross-platform native GUI apps based on [atom-shell](https://github.com/atom/atom-shell), 5 | straight out of the box from NodeJS, with no dependency or build phase required. 6 | 7 | ### Try it yourself right now 8 | 9 | ``` 10 | $ npm install node-shell 11 | $ node 12 | > require('node-shell')(function(err, api) { 13 | var win = new api.BrowserWindow({}).loadUrl('http://google.com'); 14 | }); 15 | ``` 16 | This works out of the box on `Windows`, `OSX` and `Linux` with vanilla nodeJS installations. 17 | 18 | ![ScreenShot](http://i.imgur.com/4pRqXwQ.png) 19 | 20 | ### Implementation details 21 | 22 | When `npm install` is invoked, `node-shell` downloads a packaged release of `atom-shell` 23 | for the current platform and replaces the default app with a wrapper that exposes the 24 | `atom-shell` API through (json)RPC over UNIX sockets. 25 | 26 | Using the `node-shell` library, node apps can create rich native apps powered by `atom-shell`, 27 | without the need to build or rebuild anything (including native addons). 28 | 29 | When spawning a new shell from your module, the `node-shell` library spawns an `atom-shell` 30 | running the wrapper app and opens an RPC channel with it to let you interact with the API. 31 | 32 | ``` 33 | +-------------------------+ +-----------------------+ 34 | | node | | atom-shell | 35 | +-------------------------- +-----------------------+ 36 | +--------+ +------------+ +---------+ +----------+ 37 | | | +-+ node-shell | <--------> | wrapper +--+ atom | 38 | | | | +------------+ RPC | app | | api | 39 | | your +-+ +---------+ +----+-----+ 40 | | app.js | | +------------+ +--------+---+ 41 | | | +-+ other deps | | | 42 | | | +------------+ +---+--+ +---+--+ 43 | | | ... | winA | | winB | ... 44 | +--------+ +------+ +------+ 45 | ``` 46 | -------------------------------------------------------------------------------- /lib/shell.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-shell: shell.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-07-18 spolu Creation 10 | */ 11 | "use strict" 12 | 13 | var common = require('./common.js'); 14 | 15 | var async = require('async'); 16 | var path = require('path'); 17 | var os = require('os'); 18 | 19 | 20 | // ## shell 21 | // 22 | // Main node-shell entry point. See `spawn_shell` method. 23 | // 24 | // ``` 25 | // @spec {} 26 | // @inherits {} 27 | // ``` 28 | var shell = function(spec, my) { 29 | var _super = {}; 30 | my = my || {}; 31 | spec = spec || {}; 32 | 33 | my.ATOM_SHELL_PATH = path.join(__dirname, '..', 'vendor', 'atom-shell'); 34 | my.ATOM_SHELL_EXEC = { 35 | 'linux': path.join(my.ATOM_SHELL_PATH, 'atom'), 36 | 'win32': path.join(my.ATOM_SHELL_PATH, 'atom'), 37 | 'darwin': path.join(my.ATOM_SHELL_PATH, 38 | 'Atom.app', 'Contents', 'MacOS', 'Atom'), 39 | } 40 | 41 | // 42 | // #### _public_ 43 | // 44 | var spawn_shell; /* spawn(cb_); */ 45 | 46 | // 47 | // #### _private_ 48 | // 49 | 50 | // 51 | // #### _that_ 52 | // 53 | var that = {}; 54 | 55 | /****************************************************************************/ 56 | /* PRIVATE HELPERS */ 57 | /****************************************************************************/ 58 | 59 | /****************************************************************************/ 60 | /* PUBLIC METHODS */ 61 | /****************************************************************************/ 62 | // ### spawn_shell 63 | // 64 | // Spawns a new `atom-shell` instace and returns an associated `api` object 65 | // to access its browser-side API. The `api` object communicates with the 66 | // shell process using unix domain socket. 67 | // 68 | // This method can be called multiple times to spawn different instances. 69 | // ``` 70 | // @cb_ {function(err, api)} 71 | // ``` 72 | spawn_shell = function(cb_) { 73 | if(!my.ATOM_SHELL_EXEC[os.platform()]) { 74 | return cb_(common.err('Platform not supported: ' + 75 | os.platform(), 76 | 'shell:platform_not_supported')); 77 | } 78 | 79 | require('./api.js').api({}).init(function(err, api) { 80 | var p = require('child_process').spawn( 81 | my.ATOM_SHELL_EXEC[os.platform()], 82 | [api.atom_sock(), api.node_sock()], { stdio: 'inherit' }); 83 | 84 | api.set_process(p); 85 | api.on('ready', function() { 86 | return cb_(null, api); 87 | }); 88 | }); 89 | }; 90 | 91 | common.method(that, 'spawn_shell', spawn_shell, _super); 92 | 93 | return that; 94 | }; 95 | 96 | exports.shell = shell; 97 | -------------------------------------------------------------------------------- /lib/api.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-shell: api.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-07-18 spolu Creation 10 | */ 11 | "use strict" 12 | 13 | var common = require('./common.js'); 14 | 15 | var dnode = require('dnode'); 16 | var async = require('async'); 17 | var path = require('path'); 18 | var os = require('os'); 19 | var net = require('net'); 20 | var events = require('events'); 21 | var util = require('util'); 22 | 23 | 24 | // ## api 25 | // 26 | // Object in charge of RPC with the wrapper process as well as exposing the 27 | // api components. 28 | // 29 | // ``` 30 | // @spec {} 31 | // @inherits {} 32 | // ``` 33 | var api = function(spec, my) { 34 | var _super = {}; 35 | my = my || {}; 36 | spec = spec || {}; 37 | 38 | my.salt = Date.now().toString(); 39 | my.count = 0; 40 | 41 | // 42 | // #### _public_ 43 | // 44 | var next_id; /* next_id(); */ 45 | 46 | var create; /* create(type, object, args, cb_); */ 47 | var call; /* create(object_id, methods, args, cb_); */ 48 | 49 | var init; /* init(cb_); */ 50 | var handshake; /* handshake(child, cb_); */ 51 | 52 | 53 | // 54 | // #### _private_ 55 | // 56 | 57 | // 58 | // #### _that_ 59 | // 60 | var that = new events.EventEmitter(); 61 | 62 | /****************************************************************************/ 63 | /* PRIVATE HELPERS */ 64 | /****************************************************************************/ 65 | // ### handshake 66 | // 67 | // RPC called when the wrapper connects to my.rpc_srv 68 | handshake = function() { 69 | my.rpc_cli = dnode({}, { weak: false }); 70 | my.rpc_cli.on('remote', function (remote) { 71 | my.remote = remote; 72 | common.log.out('node-shell < wrapper'); 73 | that.emit('ready'); 74 | }); 75 | 76 | var c = net.connect(my.atom_sock); 77 | c.pipe(my.rpc_cli).pipe(c); 78 | }; 79 | 80 | 81 | /****************************************************************************/ 82 | /* PUBLIC METHODS */ 83 | /****************************************************************************/ 84 | // ### next_id 85 | // 86 | // Returns a new unique id 87 | next_id = function() { 88 | return my.salt + '-' + (++my.count); 89 | }; 90 | 91 | // ### create 92 | // 93 | // RPC to create a remote API object 94 | // ``` 95 | // @type {string} the object type 96 | // @obj {object} the local object 97 | // @args {array} the array of constructors arguments 98 | // @cb_ {function(err)} 99 | // ``` 100 | create = function(type, object, args, cb_) { 101 | common.log.out('CREATE: ' + type + ' [' + args.length + ']'); 102 | my.remote.create(type, args, cb_); 103 | }; 104 | 105 | // ### call 106 | // 107 | // RPC to call a remote API object method 108 | // ``` 109 | // @object_id {string} the object_id 110 | // @method {string} the method name 111 | // @args {array} the arguments array 112 | // @cb_ {function(err, ...)} 113 | // ``` 114 | call = function(object_id, method, args, cb_) { 115 | common.log.out('CALL: ' + object_id + ' ' + method + 116 | ' [' + args.length + ']'); 117 | my.remote.call(object_id, method, args, cb_); 118 | } 119 | 120 | 121 | // ### init 122 | // 123 | // Initializes the API and opens the JSON RPC channel 124 | // ``` 125 | // @cb_ {function(err, api)} 126 | // ``` 127 | init = function(cb_) { 128 | var now = Date.now(); 129 | 130 | if (os.platform() === 'win32') { 131 | my.node_sock = '\\\\.\\pipe\\' + next_id() + '-node.sock'; 132 | my.atom_sock = '\\\\.\\pipe\\' + next_id() + '-atom.sock'; 133 | } 134 | else { 135 | my.node_sock = path.join(os.tmpdir(), 136 | 'node-shell.' + next_id() + '-node.sock'); 137 | my.atom_sock = path.join(os.tmpdir(), 138 | 'node-shell.' + next_id() + '-atom.sock'); 139 | } 140 | 141 | my.server = net.createServer(function(c) { 142 | my.rpc_srv = dnode({ 143 | handshake: handshake, 144 | loopback: function(s, cb_) { 145 | return cb_(null, s); 146 | } 147 | }, { weak: false }); 148 | c.pipe(my.rpc_srv).pipe(c); 149 | }); 150 | 151 | /* BrowserWindow */ 152 | var BrowserWindow = require('./api.BrowserWindow.js').BrowserWindow; 153 | that.BrowserWindow = function(options) { 154 | BrowserWindow.call(this, that, options); 155 | }; 156 | util.inherits(that.BrowserWindow, BrowserWindow); 157 | 158 | my.server.listen(my.node_sock); 159 | return cb_(null, that); 160 | }; 161 | 162 | common.getter(that, 'atom_sock', my, 'atom_sock'); 163 | common.getter(that, 'node_sock', my, 'node_sock'); 164 | 165 | common.getter(that, 'process', my, 'process'); 166 | common.setter(that, 'process', my, 'process'); 167 | 168 | common.method(that, 'next_id', next_id, _super); 169 | 170 | common.method(that, 'create', create, _super); 171 | common.method(that, 'call', call, _super); 172 | 173 | common.method(that, 'init', init, _super); 174 | common.method(that, 'handshake', handshake, _super); 175 | 176 | return that; 177 | } 178 | 179 | exports.api = api; 180 | -------------------------------------------------------------------------------- /script/bootstrap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-frame: script/bootstrap.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-07-18 spolu Creation 10 | */ 11 | "use strict" 12 | 13 | var async = require('async'); 14 | var mkdirp = require('mkdirp'); 15 | var path = require('path'); 16 | var fs = require('fs-extra'); 17 | var os = require('os'); 18 | var request = require('request'); 19 | var util = require('util'); 20 | 21 | var common = require('../lib/common.js'); 22 | 23 | /******************************************************************************/ 24 | /* CONFIGURATION */ 25 | /******************************************************************************/ 26 | var MODULE_PATH = path.join(__dirname, '..'); 27 | var VENDOR_PATH = path.join(MODULE_PATH, 'vendor'); 28 | 29 | var package_json = require(path.join(MODULE_PATH, 'package.json')); 30 | 31 | var ATOM_SHELL_BASE_URL = package_json.ATOM_SHELL_BASE_URL; 32 | var ATOM_SHELL_VERSION = package_json.ATOM_SHELL_VERSION; 33 | 34 | var ATOM_SHELL_PATH = path.join(VENDOR_PATH, 'atom-shell'); 35 | var ATOM_SHELL_VERSION_PATH = path.join(ATOM_SHELL_PATH, '.version'); 36 | 37 | var ATOM_SHELL_RELEASE_PLATFORM = package_json.ATOM_SHELL_RELEASE_PLATFORMS 38 | [os.platform()] 39 | [os.arch()]; 40 | 41 | var ATOM_RELEASE_FILENAME = 'atom-shell-' + ATOM_SHELL_VERSION + '-' + 42 | ATOM_SHELL_RELEASE_PLATFORM + '.zip'; 43 | 44 | var ATOM_RELEASE_URL = ATOM_SHELL_BASE_URL + 45 | ATOM_SHELL_VERSION + '/' + 46 | ATOM_RELEASE_FILENAME; 47 | 48 | var WRAPPER_PATH = path.join(MODULE_PATH, 'wrapper'); 49 | 50 | /******************************************************************************/ 51 | /* BOOTSTRAPPING STEPS */ 52 | /******************************************************************************/ 53 | // ### install_atom_shell 54 | // 55 | // Installs atom-shell binary distribution for the adequate platform if not 56 | // already present unless the force parameter is passed. 57 | // ``` 58 | // @force {boolean} force install of atom-shell binary distribution 59 | // ``` 60 | var install_atom_shell = function(force, cb_) { 61 | async.series([ 62 | /* Clean if force flag */ 63 | function(cb_) { 64 | if(force) { 65 | return fs.remove(ATOM_SHELL_PATH, cb_); 66 | } 67 | return cb_(); 68 | }, 69 | /* Check for the ATOM_SHELL_VERSION_PATH or remove it */ 70 | function(cb_) { 71 | fs.readFile(ATOM_SHELL_VERSION_PATH, function(err, data) { 72 | if(err && err.code !== 'ENOENT') { 73 | return cb_(err); 74 | } 75 | else if(err && err.code === 'ENOENT') { 76 | fs.remove(ATOM_SHELL_PATH, cb_); 77 | } 78 | else { 79 | if(data.toString() === ATOM_SHELL_VERSION) { 80 | /* We're done here! */ 81 | return cb_(common.err('atom-shell-' + ATOM_SHELL_VERSION + 82 | ' already installed in ' + 83 | path.join(ATOM_SHELL_PATH), 84 | 'bootstrap:already_installed')); 85 | } 86 | else { 87 | fs.remove(ATOM_SHELL_PATH, cb_); 88 | } 89 | } 90 | }); 91 | }, 92 | /* Create ATOM_SHELL_PATH */ 93 | function(cb_) { 94 | console.log('Creating: ' + ATOM_SHELL_PATH); 95 | mkdirp(ATOM_SHELL_PATH, cb_); 96 | }, 97 | /* Download ATOM_SHELL */ 98 | function(cb_) { 99 | console.log('Downloading: ' + ATOM_RELEASE_URL); 100 | 101 | var itv = setInterval(function() { 102 | process.stdout.write('.'); 103 | }, 1000); 104 | var finish = function(err) { 105 | clearInterval(itv); 106 | process.stdout.write('\n'); 107 | return cb_(err); 108 | } 109 | 110 | request(ATOM_RELEASE_URL) 111 | .on('error', finish) 112 | .on('end', finish) 113 | .pipe(fs.createWriteStream(path.join(ATOM_SHELL_PATH, 114 | ATOM_RELEASE_FILENAME))); 115 | }, 116 | /* Extract atom-shell in ATOM_SHELL_PATH */ 117 | function(cb_) { 118 | console.log('Extracting ' + path.join(ATOM_SHELL_PATH, 119 | ATOM_RELEASE_FILENAME)); 120 | var unzip = require('child_process').spawn('unzip', 121 | [ '-oqq', path.join(ATOM_SHELL_PATH, ATOM_RELEASE_FILENAME) ], { 122 | cwd: ATOM_SHELL_PATH 123 | }); 124 | unzip.stdout.on('data', function (data) { 125 | console.log('stdout: ' + data); 126 | }); 127 | unzip.stderr.on('data', function (data) { 128 | console.log('stderr: ' + data); 129 | }); 130 | unzip.on('close', function (code) { 131 | if(code !== 0) { 132 | return cb_(common.err('Extraction failed with code: ' + code, 133 | 'boostrap:failed_extraction')); 134 | } 135 | return cb_(); 136 | }); 137 | }, 138 | /* Cleaning up */ 139 | function(cb_) { 140 | console.log('Cleaning up ' + path.join(ATOM_SHELL_PATH, 141 | ATOM_RELEASE_FILENAME)); 142 | fs.remove(path.join(ATOM_SHELL_PATH, ATOM_RELEASE_FILENAME), cb_); 143 | }, 144 | /* Create ATOM_SHELL_VERSION */ 145 | function(cb_) { 146 | fs.writeFile(ATOM_SHELL_VERSION_PATH, ATOM_SHELL_VERSION, { 147 | flag: 'w' 148 | }, cb_); 149 | } 150 | ], cb_); 151 | }; 152 | 153 | // ### inject_wrapper 154 | // 155 | // Injects the node-shell wrapper app as atom-shell default app 156 | var inject_wrapper = function(cb_) { 157 | console.log('Injecting node-shell wrapper'); 158 | if(os.platform() === 'linux' || os.platform() === 'win32') { 159 | async.series([ 160 | function(cb_) { 161 | fs.remove(path.join(ATOM_SHELL_PATH, 162 | 'resources', 'default_app'), cb_); 163 | }, 164 | function(cb_) { 165 | fs.copy(WRAPPER_PATH, 166 | path.join(ATOM_SHELL_PATH, 'resources', 'default_app'), 167 | cb_); 168 | } 169 | ], cb_); 170 | } 171 | else if(os.type() === 'darwin') { 172 | async.series([ 173 | function(cb_) { 174 | fs.remove(path.join(ATOM_SHELL_PATH, 175 | 'Atom.app', 'Contents', 'Resources', 'default_app'), 176 | cb_); 177 | }, 178 | function(cb_) { 179 | fs.copy(WRAPPER_PATH, 180 | path.join(ATOM_SHELL_PATH, 181 | 'Atom.app', 'Contents', 'Resources', 'default_app'), 182 | cb_); 183 | } 184 | ], cb_); 185 | } 186 | else { 187 | return cb_(common.err('Wrapper injection not supported on: ' + 188 | os.type().toLowerCase(), 189 | 'boostrap:injection_not_supported')); 190 | } 191 | }; 192 | 193 | 194 | /******************************************************************************/ 195 | /* MAIN */ 196 | /******************************************************************************/ 197 | var argv = require('optimist') 198 | .usage('Usage: $0 [-f]') 199 | .argv; 200 | 201 | async.series([ 202 | function(cb_) { 203 | install_atom_shell(argv.f || false, function(err) { 204 | if(err && err.name === 'bootstrap:already_installed') { 205 | console.log(err.message); 206 | return cb_(); 207 | } 208 | return cb_(err); 209 | }); 210 | }, 211 | function(cb_) { 212 | inject_wrapper(cb_); 213 | } 214 | ], function(err) { 215 | if(err) { 216 | common.fatal(err); 217 | } 218 | console.log('Done!'); 219 | process.exit(0); 220 | }); 221 | 222 | -------------------------------------------------------------------------------- /lib/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * node-frame: common.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-07-18 spolu Creation 10 | */ 11 | "use strict"; 12 | 13 | var util = require('util'); 14 | var events = require('events'); 15 | var crypto = require('crypto'); 16 | 17 | /******************************************************************************/ 18 | /* GLOBAL CONFIG */ 19 | /******************************************************************************/ 20 | 21 | exports.DEBUG = false; 22 | 23 | /******************************************************************************/ 24 | /* CROCKFORD */ 25 | /******************************************************************************/ 26 | 27 | // ### method 28 | // 29 | // Adds a method to the current object denoted by that and preserves _super 30 | // implementation (see Crockford) 31 | // ``` 32 | // @that {object} object to extend 33 | // @name {string} the method name 34 | // @method {function} the method 35 | // @_super {object} parent object for functional inheritence 36 | // ``` 37 | exports.method = function(that, name, method, _super) { 38 | if(_super) { 39 | var m = that[name]; 40 | _super[name] = function() { 41 | return m.apply(that, arguments); 42 | }; 43 | } 44 | that[name] = method; 45 | }; 46 | 47 | // ### getter 48 | // 49 | // Generates a getter on obj for key 50 | // ``` 51 | // @that {object} object to extend 52 | // @name {string} the getter name 53 | // @obj {object} the object targeted by the getter 54 | // @key {string} the key to get on obj 55 | // ``` 56 | exports.getter = function(that, name, obj, prop) { 57 | var getter = function() { 58 | return obj[prop]; 59 | }; 60 | that[name] = getter; 61 | }; 62 | 63 | // ### setter 64 | // 65 | // Generates a getter on obj for key 66 | // ``` 67 | // @that {object} object to extend 68 | // @name {string} the getter name 69 | // @obj {object} the object targeted by the getter 70 | // @key {string} the key to get on obj 71 | // ``` 72 | exports.setter = function(that, name, obj, prop) { 73 | var setter = function (arg) { 74 | obj[prop] = arg; 75 | return that; 76 | }; 77 | that['set' + name.substring(0, 1).toUpperCase() + name.substring(1)] = setter; 78 | that['set' + '_' + name] = setter; 79 | }; 80 | 81 | // ### responds 82 | // 83 | // Tests whether the object responds to the given method name 84 | // ``` 85 | // @that {object} object to test 86 | // @name {string} the method/getter/setter name 87 | // ``` 88 | exports.responds = function(that, name) { 89 | return (that[name] && typeof that[name] === 'function'); 90 | }; 91 | 92 | 93 | /******************************************************************************/ 94 | /* HELPERS */ 95 | /******************************************************************************/ 96 | 97 | // #### once 98 | // 99 | // Returns a function that will call the underlying function only once 100 | // whether it is called once or multiple times 101 | // ``` 102 | // @fn {function} function to call once 103 | // ``` 104 | exports.once = function(fn) { 105 | if(fn === void 0 || fn === null || typeof fn !== 'function') 106 | throw new TypeError(); 107 | 108 | var done = false; 109 | return function() { 110 | if(!done) { 111 | args = Array.prototype.slice.call(arguments); 112 | done = true; 113 | fn.apply(null, args); 114 | } 115 | }; 116 | }; 117 | 118 | // ### remove 119 | // 120 | // Removes the element e from the Array, using the JS '===' equality 121 | // ``` 122 | // @that {array} the array to operate on 123 | // @e {object} element to remove from the array 124 | // @only_one {boolean} remove only one 125 | // ``` 126 | exports.remove = function(that, e, only_one) { 127 | "use strict"; 128 | 129 | if(that === void 0 || that === null || !Array.isArray(that)) 130 | throw new TypeError(); 131 | 132 | for(var i = that.length - 1; i >= 0; i--) { 133 | if(e === that[i]) { 134 | that.splice(i, 1); 135 | if(only_one) return; 136 | } 137 | } 138 | }; 139 | 140 | // ### clamp 141 | // 142 | // Clamp a given integer to a specified range and pad 143 | // ``` 144 | // @v {number} value 145 | // @min {number} minimum 146 | // @max {number} maximum 147 | // ``` 148 | exports.clamp = function(v, min, max) { 149 | if (v < min) 150 | return min; 151 | if (v > max) 152 | return max; 153 | return v; 154 | }; 155 | 156 | // ### lpad 157 | // 158 | // Pads a string to the provided length with ' ' or opt_ch 159 | // ``` 160 | // @str {string} string to pad 161 | // @length {number} pad to length character 162 | // @opt_ch {string} [optional] character 163 | // ``` 164 | exports.lpad = function(str, length, opt_ch) { 165 | str = String(str); 166 | opt_ch = opt_ch || ' '; 167 | 168 | while (str.length < length) 169 | str = opt_ch + str; 170 | 171 | return str; 172 | }; 173 | 174 | // ### rpad 175 | // 176 | // Pads a string to the provided length with ' ' or opt_ch 177 | // ``` 178 | // @str {string} string to pad 179 | // @length {number} pad to length character 180 | // @opt_ch {string} [optional] character 181 | // ``` 182 | exports.rpad = function(str, length, opt_ch) { 183 | str = String(str); 184 | opt_ch = opt_ch || ' '; 185 | 186 | while (str.length < length) 187 | str += opt_ch; 188 | 189 | return str.substr(0, length); 190 | }; 191 | 192 | // ### zpad 193 | // 194 | // Pads a string to the provided length with zeroes 195 | // ``` 196 | // @str {string} string to pad 197 | // @length {number} pad to length character 198 | // ``` 199 | exports.zpad = function(str, length) { 200 | return exports.lpad(str, length, '0'); 201 | }; 202 | 203 | // ### hash 204 | // 205 | // Computes a hash value from the given strings 206 | // ``` 207 | // @strings {array} an array of string used to update HMAC 208 | // @encoding {string} the encoding used [optional] (default: 'hex') 209 | // @return {string} the hash value 210 | // ``` 211 | // 212 | exports.hash = function(strings, encoding) { 213 | encoding = encoding || 'hex'; 214 | var hash = crypto.createHmac('sha1', ''); 215 | strings.forEach(function(update) { 216 | hash.update(new Buffer(update.toString())); 217 | }); 218 | return hash.digest(encoding); 219 | }; 220 | 221 | 222 | /******************************************************************************/ 223 | /* LOGGING AND ERROR REPORTING */ 224 | /******************************************************************************/ 225 | 226 | var log = function(str, debug, error) { 227 | var pre = '[' + new Date().toISOString() + '] '; 228 | //pre += (my.name ? '{' + my.name.toUpperCase() + '} ' : ''); 229 | pre += (debug ? 'DEBUG: ' : ''); 230 | str.toString().split('\n').forEach(function(line) { 231 | if(error) 232 | console.error(pre + line) 233 | else if(debug) 234 | console.log(pre + line); 235 | else 236 | console.log(pre + line); 237 | }); 238 | }; 239 | 240 | 241 | // ### log 242 | // 243 | // Logging helpers. Object based on the `log` function including 4 logging 244 | // functions: `out`, `error`, `debug`, `info` 245 | // ``` 246 | // @str {string|error} the string or error to log 247 | // ``` 248 | exports.log = { 249 | out: function(str) { 250 | log(str); 251 | }, 252 | error: function(err) { 253 | if(typeof err === 'object') { 254 | log('*********************************************', false, true); 255 | log('ERROR: ' + err.message); 256 | log('*********************************************', false, true); 257 | log(err.stack); 258 | log('---------------------------------------------', false, true); 259 | } 260 | else { 261 | log('*********************************************', false, true); 262 | log('ERROR: ' + JSON.stringify(err)); 263 | log('*********************************************', false, true); 264 | log('---------------------------------------------', false, true); 265 | } 266 | }, 267 | debug: function(str) { 268 | if(exports.DEBUG) 269 | log(str, true); 270 | }, 271 | info: function(str) { 272 | util.print(str + '\n'); 273 | } 274 | }; 275 | 276 | // ### exit 277 | // 278 | // Makes sure to kill all subprocesses 279 | // `` 280 | // @code {number} exit code 281 | // ``` 282 | exports.exit = function(code) { 283 | try { 284 | process.kill('-' + process.pid); 285 | } 286 | finally { 287 | process.exit(code); 288 | } 289 | }; 290 | 291 | // ### fatal 292 | // 293 | // Prints out the error and exits the process while killing all sub processes 294 | // ``` 295 | // @err {error} 296 | // ``` 297 | exports.fatal = function(err) { 298 | exports.log.error(err); 299 | exports.exit(1); 300 | }; 301 | 302 | // ### err 303 | // 304 | // Generates a proper error with name set 305 | // ``` 306 | // @msg {string} the error message 307 | // @name {string} the error name 308 | // ``` 309 | exports.err = function(msg, name) { 310 | var err = new Error(msg); 311 | err.name = name || 'CommonError'; 312 | return err; 313 | }; 314 | 315 | -------------------------------------------------------------------------------- /wrapper/lib/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * node-shell: [wrapper] common.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-07-18 spolu Creation 10 | */ 11 | "use strict"; 12 | 13 | var util = require('util'); 14 | var events = require('events'); 15 | var crypto = require('crypto'); 16 | 17 | /******************************************************************************/ 18 | /* GLOBAL CONFIG */ 19 | /******************************************************************************/ 20 | 21 | exports.DEBUG = false; 22 | 23 | /******************************************************************************/ 24 | /* CROCKFORD */ 25 | /******************************************************************************/ 26 | 27 | // ### method 28 | // 29 | // Adds a method to the current object denoted by that and preserves _super 30 | // implementation (see Crockford) 31 | // ``` 32 | // @that {object} object to extend 33 | // @name {string} the method name 34 | // @method {function} the method 35 | // @_super {object} parent object for functional inheritence 36 | // ``` 37 | exports.method = function(that, name, method, _super) { 38 | if(_super) { 39 | var m = that[name]; 40 | _super[name] = function() { 41 | return m.apply(that, arguments); 42 | }; 43 | } 44 | that[name] = method; 45 | }; 46 | 47 | // ### getter 48 | // 49 | // Generates a getter on obj for key 50 | // ``` 51 | // @that {object} object to extend 52 | // @name {string} the getter name 53 | // @obj {object} the object targeted by the getter 54 | // @key {string} the key to get on obj 55 | // ``` 56 | exports.getter = function(that, name, obj, prop) { 57 | var getter = function() { 58 | return obj[prop]; 59 | }; 60 | that[name] = getter; 61 | }; 62 | 63 | // ### setter 64 | // 65 | // Generates a getter on obj for key 66 | // ``` 67 | // @that {object} object to extend 68 | // @name {string} the getter name 69 | // @obj {object} the object targeted by the getter 70 | // @key {string} the key to get on obj 71 | // ``` 72 | exports.setter = function(that, name, obj, prop) { 73 | var setter = function (arg) { 74 | obj[prop] = arg; 75 | return that; 76 | }; 77 | that['set' + name.substring(0, 1).toUpperCase() + name.substring(1)] = setter; 78 | that['set' + '_' + name] = setter; 79 | }; 80 | 81 | // ### responds 82 | // 83 | // Tests whether the object responds to the given method name 84 | // ``` 85 | // @that {object} object to test 86 | // @name {string} the method/getter/setter name 87 | // ``` 88 | exports.responds = function(that, name) { 89 | return (that[name] && typeof that[name] === 'function'); 90 | }; 91 | 92 | 93 | /******************************************************************************/ 94 | /* HELPERS */ 95 | /******************************************************************************/ 96 | 97 | // #### once 98 | // 99 | // Returns a function that will call the underlying function only once 100 | // whether it is called once or multiple times 101 | // ``` 102 | // @fn {function} function to call once 103 | // ``` 104 | exports.once = function(fn) { 105 | if(fn === void 0 || fn === null || typeof fn !== 'function') 106 | throw new TypeError(); 107 | 108 | var done = false; 109 | return function() { 110 | if(!done) { 111 | args = Array.prototype.slice.call(arguments); 112 | done = true; 113 | fn.apply(null, args); 114 | } 115 | }; 116 | }; 117 | 118 | // ### remove 119 | // 120 | // Removes the element e from the Array, using the JS '===' equality 121 | // ``` 122 | // @that {array} the array to operate on 123 | // @e {object} element to remove from the array 124 | // @only_one {boolean} remove only one 125 | // ``` 126 | exports.remove = function(that, e, only_one) { 127 | "use strict"; 128 | 129 | if(that === void 0 || that === null || !Array.isArray(that)) 130 | throw new TypeError(); 131 | 132 | for(var i = that.length - 1; i >= 0; i--) { 133 | if(e === that[i]) { 134 | that.splice(i, 1); 135 | if(only_one) return; 136 | } 137 | } 138 | }; 139 | 140 | // ### clamp 141 | // 142 | // Clamp a given integer to a specified range and pad 143 | // ``` 144 | // @v {number} value 145 | // @min {number} minimum 146 | // @max {number} maximum 147 | // ``` 148 | exports.clamp = function(v, min, max) { 149 | if (v < min) 150 | return min; 151 | if (v > max) 152 | return max; 153 | return v; 154 | }; 155 | 156 | // ### lpad 157 | // 158 | // Pads a string to the provided length with ' ' or opt_ch 159 | // ``` 160 | // @str {string} string to pad 161 | // @length {number} pad to length character 162 | // @opt_ch {string} [optional] character 163 | // ``` 164 | exports.lpad = function(str, length, opt_ch) { 165 | str = String(str); 166 | opt_ch = opt_ch || ' '; 167 | 168 | while (str.length < length) 169 | str = opt_ch + str; 170 | 171 | return str; 172 | }; 173 | 174 | // ### rpad 175 | // 176 | // Pads a string to the provided length with ' ' or opt_ch 177 | // ``` 178 | // @str {string} string to pad 179 | // @length {number} pad to length character 180 | // @opt_ch {string} [optional] character 181 | // ``` 182 | exports.rpad = function(str, length, opt_ch) { 183 | str = String(str); 184 | opt_ch = opt_ch || ' '; 185 | 186 | while (str.length < length) 187 | str += opt_ch; 188 | 189 | return str.substr(0, length); 190 | }; 191 | 192 | // ### zpad 193 | // 194 | // Pads a string to the provided length with zeroes 195 | // ``` 196 | // @str {string} string to pad 197 | // @length {number} pad to length character 198 | // ``` 199 | exports.zpad = function(str, length) { 200 | return exports.lpad(str, length, '0'); 201 | }; 202 | 203 | // ### hash 204 | // 205 | // Computes a hash value from the given strings 206 | // ``` 207 | // @strings {array} an array of string used to update HMAC 208 | // @encoding {string} the encoding used [optional] (default: 'hex') 209 | // @return {string} the hash value 210 | // ``` 211 | // 212 | exports.hash = function(strings, encoding) { 213 | encoding = encoding || 'hex'; 214 | var hash = crypto.createHmac('sha1', ''); 215 | strings.forEach(function(update) { 216 | hash.update(new Buffer(update.toString())); 217 | }); 218 | return hash.digest(encoding); 219 | }; 220 | 221 | 222 | /******************************************************************************/ 223 | /* LOGGING AND ERROR REPORTING */ 224 | /******************************************************************************/ 225 | 226 | var log = function(str, debug, error) { 227 | var pre = '[' + new Date().toISOString() + '] '; 228 | //pre += (my.name ? '{' + my.name.toUpperCase() + '} ' : ''); 229 | pre += (debug ? 'DEBUG: ' : ''); 230 | str.toString().split('\n').forEach(function(line) { 231 | if(error) 232 | console.error(pre + line) 233 | else if(debug) 234 | console.log(pre + line); 235 | else 236 | console.log(pre + line); 237 | }); 238 | }; 239 | 240 | 241 | // ### log 242 | // 243 | // Logging helpers. Object based on the `log` function including 4 logging 244 | // functions: `out`, `error`, `debug`, `info` 245 | // ``` 246 | // @str {string|error} the string or error to log 247 | // ``` 248 | exports.log = { 249 | out: function(str) { 250 | log(str); 251 | }, 252 | error: function(err) { 253 | if(typeof err === 'object') { 254 | log('*********************************************', false, true); 255 | log('ERROR: ' + err.message); 256 | log('*********************************************', false, true); 257 | log(err.stack); 258 | log('---------------------------------------------', false, true); 259 | } 260 | else { 261 | log('*********************************************', false, true); 262 | log('ERROR: ' + JSON.stringify(err)); 263 | log('*********************************************', false, true); 264 | log('---------------------------------------------', false, true); 265 | } 266 | }, 267 | debug: function(str) { 268 | if(exports.DEBUG) 269 | log(str, true); 270 | }, 271 | info: function(str) { 272 | util.print(str + '\n'); 273 | } 274 | }; 275 | 276 | // ### exit 277 | // 278 | // Makes sure to kill all subprocesses 279 | // `` 280 | // @code {number} exit code 281 | // ``` 282 | exports.exit = function(code) { 283 | try { 284 | process.kill('-' + process.pid); 285 | } 286 | finally { 287 | process.exit(code); 288 | } 289 | }; 290 | 291 | // ### fatal 292 | // 293 | // Prints out the error and exits the process while killing all sub processes 294 | // ``` 295 | // @err {error} 296 | // ``` 297 | exports.fatal = function(err) { 298 | exports.log.error(err); 299 | exports.exit(1); 300 | }; 301 | 302 | // ### err 303 | // 304 | // Generates a proper error with name set 305 | // ``` 306 | // @msg {string} the error message 307 | // @name {string} the error name 308 | // ``` 309 | exports.err = function(msg, name) { 310 | var err = new Error(msg); 311 | err.name = name || 'CommonError'; 312 | return err; 313 | }; 314 | 315 | --------------------------------------------------------------------------------