├── .gitignore ├── .travis.yml ├── History.md ├── Readme.md ├── examples ├── replpad.js └── simple.js ├── package.json ├── replify.js └── test └── replify.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .DS_Store 3 | .idea 4 | nohup.out 5 | npm-debug.log 6 | logs 7 | node_modules 8 | pids 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | - 0.8 5 | - 0.9 6 | - 0.10 7 | - 0.11 -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.2.0 / 2014-02-20 3 | ================== 4 | 5 | * Removed hard coded /tmp in favour of real OS temp. (@remy) 6 | * Update docs and examples. 7 | 8 | 1.1.4 / 2013-08-28 9 | ================== 10 | 11 | * Return `replServer` instance. 12 | 13 | 1.1.3 / 2013-08-14 14 | ================== 15 | 16 | * Fixed 'ctx not defined'. (@thlorenz) 17 | * Expose `useColors` REPL configuration. (@thlorenz) 18 | * `socket.end` should be bound to the `socket` object. (@kitcambridge) 19 | * `getConnections` was not in v0.8, switch to checking for `listen`. 20 | 21 | 1.1.2 / 2013-08-12 22 | ================== 23 | 24 | * Fixed bad reference. 25 | 26 | 1.1.1 / 2013-08-12 27 | ================== 28 | 29 | * Fixed references to repl options. 30 | 31 | 1.1.0 / 2013-08-12 32 | ================== 33 | 34 | * Add js formatting to Readme code blocks. (@timoxley) 35 | * Add support for custom REPL `start` functions to support `replpad`, et al. (@thlorenz) 36 | * Clean-up and organize options. 37 | * Consistent formatting and variable naming. 38 | 39 | 1.0.2 / 2013-01-03 40 | ================== 41 | 42 | * Initial tests. 43 | 44 | 1.0.1 / 2012-11-17 45 | ================== 46 | 47 | * Fixed missing logger default. (@raynos) 48 | 49 | 1.0.0 / 2012-09-28 50 | ================== 51 | 52 | * Release replify. 53 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Replify 2 | 3 | Easily add a REPL to your Node.js app. 4 | 5 | 6 | ## Status 7 | 8 | [![Travis Build Status](https://secure.travis-ci.org/dshaw/replify.png)](http://travis-ci.org/dshaw/replify) 9 | 10 | ## Install 11 | 12 | npm install replify 13 | 14 | ## Usage 15 | 16 | ```js 17 | var replify = require('replify') 18 | , app = require('http').createServer() 19 | 20 | replify('realtime-101', app) 21 | ``` 22 | 23 | Advanced options. 24 | 25 | ```js 26 | replify({ name: 'realtime-101', path: '/dshaw/repl' }, app, { 'other_context': io }) 27 | ``` 28 | 29 | ## replify(options, app, [contexts]) 30 | 31 | ### `options` 32 | 33 | `name` or `options` object. 34 | 35 | - `name` [String] - Name for the REPL domain socket. Default: **replify**. You are going to want to assign a value for this. **Note:** Due to the fact that Node no longer cleans up domain socket files behind itself, `replify` automatically paves over the existing file when it starts. 36 | - `path` [String] - Default: **/tmp/repl**. The REPL will be located at `{path}/{name}{extension}`. 37 | - `extension` [String] - Default: **.sock**. 38 | - `logger` [Object] - Default: **console**. 39 | - `start` [Function] - Default: **require('repl').start**. Useful for custom repls like [replpad](https://github.com/thlorenz/replpad). 40 | - `app` [Object] - Alternative to using the `app` parameter. 41 | - `contexts` [Object] - Alternative to using the `contexts` parameter. 42 | 43 | ### `app` 44 | 45 | Primary context. Exposed as: 46 | 47 | realtime-101> app 48 | 49 | ### `contexts` 50 | 51 | Additional contexts exposed under the name of the key. 52 | 53 | ```js 54 | replify('realtime-101', app, { 'io': io }) 55 | ``` 56 | 57 | ## Connect to the REPL 58 | 59 | ### NETCAT (nc) 60 | 61 | $ nc -U /tmp/repl/realtime-101.sock 62 | 63 | ### SOCAT 64 | 65 | $ socat READLINE /tmp/repl/realtime-101.sock 66 | 67 | ### repl-client (rc) 68 | 69 | Node repl client with history scrollback and tab completion. Learn more about [`repl-client (rc)`](https://github.com/dshaw/repl-client). Install with `npm install repl-client -g`. 70 | 71 | $ rc /tmp/repl/realtime-101.sock 72 | 73 | ### Windows 74 | 75 | Recommended to use `repl-client`. Note that Windows pipes don't live on disk, and the path to the socket is not *quite* the same as *nix environments. 76 | 77 | $ rc \\.\pipe\tmp-repl\realtime-101.sock 78 | 79 | ## License 80 | 81 | (The MIT License) 82 | 83 | Copyright (c) 2012-2014 Daniel D. Shaw, http://dshaw.com 84 | 85 | Permission is hereby granted, free of charge, to any person obtaining 86 | a copy of this software and associated documentation files (the 87 | 'Software'), to deal in the Software without restriction, including 88 | without limitation the rights to use, copy, modify, merge, publish, 89 | distribute, sublicense, and/or sell copies of the Software, and to 90 | permit persons to whom the Software is furnished to do so, subject to 91 | the following conditions: 92 | 93 | The above copyright notice and this permission notice shall be 94 | included in all copies or substantial portions of the Software. 95 | 96 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 97 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 98 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 99 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 100 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 101 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 102 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 103 | -------------------------------------------------------------------------------- /examples/replpad.js: -------------------------------------------------------------------------------- 1 | var replify = require('../') 2 | , app = require('http').createServer() 3 | , replpad 4 | 5 | try { 6 | replpad = require('replpad'); 7 | } catch (e) { 8 | console.error('For this example you need to `npm install replpad` first.') 9 | process.exit(1) 10 | } 11 | 12 | replify({ name: 'replpad-101', start: replpad }, app) 13 | 14 | app.on('request', function onRequest(req, res) { 15 | res.writeHead(200, {'Content-Type': 'text/plain'}) 16 | res.end('Hello, replpad!\n') 17 | }) 18 | 19 | app.on('listening', function onListening() { 20 | console.log('listening') 21 | }) 22 | 23 | app.listen(8080) 24 | -------------------------------------------------------------------------------- /examples/simple.js: -------------------------------------------------------------------------------- 1 | var replify = require('../') 2 | , app = require('http').createServer() 3 | 4 | replify('realtime-101', app) 5 | 6 | app.on('request', function onRequest(req, res) { 7 | res.writeHead(200, {'Content-Type': 'text/plain'}) 8 | res.end('Hello, replify!\n') 9 | }) 10 | 11 | app.listen(Number(process.argv[2]) || 8080) 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "replify", 3 | "version": "1.2.0", 4 | "description": "Easily add a REPL to your Node.js app.", 5 | "keywords": [ 6 | "repl", 7 | "observability" 8 | ], 9 | "author": { 10 | "name": "Daniel D. Shaw", 11 | "email": "dshaw@dshaw.com", 12 | "url": "http://dshaw.com" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/dshaw/replify.git" 17 | }, 18 | "bugs": { 19 | "url": "http://github.com/dshaw/replify/issues" 20 | }, 21 | "license": "MIT", 22 | "main": "replify.js", 23 | "scripts": { 24 | "test": "./node_modules/.bin/tap test/*.js" 25 | }, 26 | "directories": { 27 | "examples": "examples", 28 | "test": "test" 29 | }, 30 | "dependencies": {}, 31 | "devDependencies": { 32 | "tap": "~0.4.4" 33 | }, 34 | "engines": { 35 | "node": ">= 0.6.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /replify.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * replify 3 | * Copyright(c) 2012-2013 Daniel D. Shaw, http://dshaw.com 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies 9 | */ 10 | 11 | var fs = require('fs') 12 | , net = require('net') 13 | , path = require('path') 14 | , repl = require('repl') 15 | 16 | /** 17 | * Exports - replify 18 | */ 19 | function cleanPipeName(str) { 20 | if (process.platform === 'win32') { 21 | str = str.replace(/^\//, ''); 22 | str = str.replace(/\//g, '-'); 23 | return '\\\\.\\pipe\\'+str; 24 | } else { 25 | return str; 26 | } 27 | } 28 | 29 | module.exports = function replify (options, app, contexts) { 30 | options = (options && options.name) ? options : { name: options } 31 | 32 | options.app || (options.app = app) 33 | options.columns || (options.columns = 132) 34 | options.contexts || (options.contexts = (typeof contexts === 'object') ? contexts : {}) 35 | options.extension || (options.extension = '.sock') 36 | options.logger || (options.logger = console) 37 | options.name || (options.name = 'replify') 38 | options.path || (options.path = '/tmp/repl') 39 | options.start || (options.start = repl.start) 40 | options.hasOwnProperty('useColors') || (options.useColors = true) 41 | 42 | options.replPath = cleanPipeName(options.path + path.sep + options.name + options.extension) 43 | 44 | var logger = options.logger 45 | , replServer = net.createServer() 46 | 47 | replServer.on('connection', function onRequest(socket) { 48 | var rep = null 49 | , replOptions = { 50 | prompt: options.name + '> ' 51 | , input: socket 52 | , output: socket 53 | , terminal: true 54 | , useGlobal: false 55 | , useColors: options.useColors 56 | } 57 | 58 | // Set screen width. Especially useful for autocomplete. 59 | // Since we expose the socket context, we can view 60 | // You can modify this locally in your repl with `socket.columns`. 61 | socket.columns = options.columns 62 | 63 | // start the repl instance 64 | if (typeof fs.exists === 'undefined') { // We're in node v0.6. Start legacy repl. 65 | 66 | logger.warn('starting legacy repl') 67 | rep = repl.start(replOptions.prompt, socket) 68 | 69 | } else { 70 | 71 | rep = options.start(replOptions) 72 | rep.on('exit', socket.end.bind(socket)) 73 | rep.on('error', function (err) { 74 | logger.error('repl error', err) 75 | }) 76 | 77 | } 78 | 79 | // expose the socket itself to the repl 80 | rep.context.replify = options 81 | 82 | // expose the socket itself to the repl 83 | rep.context.socket = socket 84 | 85 | if (options.app) { 86 | rep.context.app = options.app 87 | } 88 | 89 | Object.keys(options.contexts).forEach(function (key) { 90 | if (rep.context[key]) { 91 | // don't pave over existing contexts 92 | logger.warn('unable to register context: ' + key) 93 | } else { 94 | rep.context[key] = options.contexts[key] 95 | } 96 | }) 97 | }) 98 | 99 | replServer.on('error', function (err) { 100 | logger.error('repl server error', err) 101 | }) 102 | 103 | var start = replServer.listen.bind(replServer, options.replPath) 104 | 105 | // with windows, pipes are different, so we don't actually need to create 106 | // anything and we go ahead and listen right away 107 | if (process.platform === 'win32') { 108 | start() 109 | } else { 110 | fs.mkdir(options.path, function (err) { 111 | if (err && err.code !== 'EEXIST') { 112 | return logger.error('error making repl directory: ' + options.path, err) 113 | } 114 | 115 | fs.unlink(options.replPath, function () { 116 | // NOTE: Intentionally not listening for any errors. 117 | replServer.listen(options.replPath) 118 | }) 119 | }) 120 | } 121 | 122 | return replServer 123 | } 124 | -------------------------------------------------------------------------------- /test/replify.test.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | , http = require('http') 3 | , net = require('net') 4 | , replify = require('../') 5 | , tap = require('tap') 6 | , test = tap.test 7 | 8 | /** 9 | * Support 10 | */ 11 | 12 | function connect(name) { 13 | return net.connect({ path: '/tmp/repl/' + name + '.sock' }) 14 | } 15 | 16 | function sendMsg(socket, msg, cb) { 17 | var data = '' 18 | socket.on('data', function (buf) { data += buf.toString() }) 19 | socket.on('end', function () { cb(data) }) 20 | 21 | socket.write(msg); 22 | socket.end(); 23 | } 24 | 25 | /** 26 | * Cleanup 27 | */ 28 | 29 | tap.on('end', function () { 30 | // cleanup 31 | try { 32 | fs.unlinkSync('/tmp/repl/net-test.sock') 33 | } catch (err) {} 34 | 35 | process.exit() 36 | }) 37 | 38 | /** 39 | * Tests 40 | */ 41 | 42 | test('replify', function (t) { 43 | 44 | var app = http.createServer() 45 | t.on('end', app.close.bind(app)) 46 | 47 | app.listen(9999, function onListening () { 48 | // need to give it a couple ticks to setup 49 | setTimeout(function () { 50 | t.ok(fs.statSync('/tmp/repl/net-test.sock'), 'repl file exists') 51 | 52 | var conn = net.connect('/tmp/repl/net-test.sock') 53 | conn.resume() 54 | 55 | conn.on('connect', conn.end.bind(conn, '.exit\n')) 56 | conn.on('close', t.end.bind(t)) 57 | }, 250) 58 | }) 59 | 60 | replify('net-test', app) 61 | }) 62 | 63 | test('replify has app in context', function (t) { 64 | 65 | var app = http.createServer() 66 | t.on('end', app.close.bind(app)) 67 | 68 | app.listen(9999, function onListening () { 69 | setTimeout(function () { 70 | var socket = connect('net-test') 71 | socket.on('connect', function () { 72 | sendMsg(socket, 'app.listen\n', function (res) { 73 | t.similar(res, /app.listen\r\n\[Function\]/, 'can access app.listen property') 74 | t.end() 75 | }) 76 | }) 77 | }, 250) 78 | }) 79 | replify({ name: 'net-test', useColors: false }, app) 80 | }) 81 | 82 | test('replify accepts custom context as last param', function (t) { 83 | 84 | var app = http.createServer() 85 | t.on('end', app.close.bind(app)) 86 | 87 | app.listen(9999, function onListening () { 88 | setTimeout(function () { 89 | var socket = connect('net-test') 90 | socket.on('connect', function () { 91 | sendMsg(socket, 'node\n', function (res) { 92 | t.ok(/up/.test(res), 'can access properties in custom context') 93 | t.end() 94 | }) 95 | }) 96 | }, 250) 97 | }) 98 | replify({ name: 'net-test', usecolors: false }, app, { node: 'up' }) 99 | }) 100 | 101 | test('replify accepts custom context as options property', function (t) { 102 | 103 | var app = http.createServer() 104 | t.on('end', app.close.bind(app)) 105 | 106 | app.listen(9999, function onListening () { 107 | setTimeout(function () { 108 | var socket = connect('net-test') 109 | socket.on('connect', function () { 110 | sendMsg(socket, 'node\n', function (res) { 111 | t.ok(/up/.test(res), 'can access properties in custom context') 112 | t.end() 113 | }) 114 | }) 115 | }, 250) 116 | }) 117 | replify({ name: 'net-test', usecolors: false, contexts: { node: 'up' } }, app) 118 | }) 119 | 120 | test('replify exposes net server', function (t) { 121 | 122 | var TcpServer = net.Server 123 | , app = http.createServer() 124 | , replServer = replify({ name: 'net-test', usecolors: false }, app) 125 | 126 | t.on('end', app.close.bind(app)) 127 | 128 | t.isa(replServer, TcpServer, 'returns a TCP server') 129 | 130 | app.listen(9999, function onAppListening () { 131 | replServer.on('listening', function onReplServerListening () { 132 | replServer.close(function onClose() { 133 | var socket = connect('net-test') 134 | socket.on('error', function (err) { 135 | t.isa(err, Error, 'socket connection should fail') 136 | t.equal(err.code, 'ENOENT', 'cannot connect to closed replServer') 137 | t.end() 138 | }) 139 | }) 140 | }) 141 | }) 142 | }) 143 | --------------------------------------------------------------------------------