├── .gitignore ├── README.md ├── bin └── goosecon.js ├── example ├── models │ └── person.js ├── package.json ├── seed.js └── services │ └── spoilMe.js ├── package.json ├── screenshot.png └── test ├── index.js ├── models └── person.js └── modules └── service.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .gooseconrc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # goosecon 2 | 3 | goosecon is a Mongoose REPL Console that auto-loads mongoose models and additional app-specific modules (services/helpers). Inspired by rails console. 4 | 5 | ![](https://github.com/mnort9/goosecon/blob/master/screenshot.png) 6 | 7 | ### Installation 8 | 9 | Install globally: 10 | ```sh 11 | $ npm install -g goosecon 12 | ``` 13 | 14 | ### Usage 15 | 16 | Run `goosecon` in your app directory: 17 | 18 | ```sh 19 | $ cd app-directory 20 | $ goosecon 21 | ``` 22 | 23 | Perform a mongoose query with any of your models 24 | 25 | ```sh 26 | > Person.findOne({name: 'Matt'}).populate('pets') 27 | ``` 28 | 29 | ### Options 30 | 31 | The only required option is the MongoDB url: 32 | 33 | ```sh 34 | $ goosecon --mongo-db 'mongodb://localhost/db-name' 35 | ``` 36 | 37 | By default, goosecon will search through the working directory for a `models` directory and attempt to load any mongoose models. 38 | 39 | You can also specify a `models` directory: 40 | 41 | ```sh 42 | $ goosecon --models-dir './path/to/models' 43 | ``` 44 | 45 | ### .gooseconrc 46 | 47 | As an alernative to the command line options, you can create a `.gooseconrc` configuration file and place it in your app directory. 48 | 49 | ```json 50 | { 51 | "mongoDb": "mongodb://localhost/db-name", 52 | "modelsDir": "./path/to/models", 53 | "mongooseDir": "./node_modules/mongoose", 54 | "modules": [ 55 | "./path/to/services", 56 | "./path/to/helpers" 57 | ] 58 | } 59 | ``` 60 | 61 | Other modules/services/helpers can be loaded with the `modules` option. 62 | 63 | ### Note: Fix query stalling error 64 | 65 | If your mongoose queries seem to stall out, you may need to tell goosecon to use your app's mongoose package 66 | 67 | ```sh 68 | $ goosecon --mongoose-dir './node_modules/mongoose' 69 | ``` 70 | -------------------------------------------------------------------------------- /bin/goosecon.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var repl = require('repl'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var vm = require('vm'); 7 | var util = require('util'); 8 | var rc = require('rc'); 9 | var _ = require('underscore'); 10 | var chalk = require('chalk'); 11 | 12 | var argv = require('yargs') 13 | .usage('Usage: $0 [options]') 14 | .option('mongo-db', { 15 | describe: 'MongoDB URL' 16 | }) 17 | .option('models-dir', { 18 | describe: 'Path to models directory' 19 | }) 20 | .option('mongoose-dir', { 21 | describe: 'Path to mongoose' 22 | }) 23 | .help('h') 24 | .alias('h', 'help') 25 | .argv; 26 | 27 | // Override options 28 | var config = rc('goosecon', {}, argv); 29 | 30 | var mongoosePath = config.mongooseDir ? path.normalize(path.resolve(process.cwd(), config.mongooseDir)) : 'mongoose'; 31 | var mongoose = require(mongoosePath); 32 | var replServer; 33 | 34 | mongoose.connect(config.mongoDb); 35 | 36 | mongoose.connection.on('error', function(err) { 37 | console.error('Unable to connect to db ' + config.mongoDb + '\n' + err); 38 | process.exit(1); 39 | }); 40 | 41 | function startReplServer () { 42 | replServer = repl.start({ 43 | eval: function(cmd, context, filename, cb) { 44 | var result; 45 | 46 | try { 47 | result = vm.runInContext(cmd, context, filename); 48 | } 49 | catch(err) { return cb(err); } 50 | 51 | // instanceof doesn't work when mongoose version < 4 52 | if (_.isObject(result) && (result instanceof mongoose.Query || result.constructor.name === 'Query')) { 53 | result.exec(function(err, doc) { 54 | if (err) return cb(err); 55 | return cb(null, doc); 56 | }); 57 | } else { 58 | cb(null, result); 59 | } 60 | }, 61 | writer: function(val) { 62 | if (!val) return ''; 63 | 64 | try { 65 | val = JSON.parse(JSON.stringify(val)); 66 | console.log(util.inspect(val, { colors: true, depth: 7 })); 67 | return ''; 68 | } 69 | catch(err) { console.error('Unable to stringify JSON'); } 70 | 71 | console.log(val); 72 | return ''; // Prevents 'undefined' output 73 | } 74 | }); 75 | } 76 | 77 | function loadModules() { 78 | var moduleNames = []; 79 | var files = _.compact(_.flatten([findModels(), findModules()])); 80 | 81 | // Add models to repl context 82 | files.forEach(function(file) { 83 | var module; 84 | try { module = require(file); } 85 | catch(err) { return; } 86 | 87 | // Get module name and capitalize first letter 88 | var moduleName = path.basename(file, '.js'); 89 | moduleName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1); 90 | 91 | replServer.context[moduleName] = module; 92 | moduleNames.push(moduleName); 93 | }); 94 | 95 | if (moduleNames) { 96 | console.log(chalk.underline('\n' + moduleNames.length + ' Modules Loaded:')); 97 | console.log(chalk.red(moduleNames.join(', ')) + '\n'); 98 | replServer.displayPrompt(); 99 | } 100 | } 101 | 102 | function findModels() { 103 | var modelsPath = config.modelsDir; 104 | var files; 105 | 106 | if (!modelsPath) { 107 | var cwdFiles = fs.readdirSync(process.cwd()); 108 | 109 | // Search working directory for models directory 110 | if (_.contains(cwdFiles, 'models')) 111 | modelsPath = path.resolve(process.cwd() + '/' + 'models'); 112 | else 113 | return; 114 | } 115 | 116 | files = fs.readdirSync(modelsPath); 117 | files = files.map(function(file) { return path.resolve(modelsPath + '/' + file); }); 118 | 119 | return files; 120 | } 121 | 122 | function findModules() { 123 | if (!config.modules) return; 124 | 125 | // Convert command line argument to array 126 | var modulesDirectories = typeof config.modules === 'string' ? [config.modules] : config.modules; 127 | 128 | var modules = modulesDirectories.map(function(modulesDirPath) { 129 | var files = fs.readdirSync(modulesDirPath); 130 | return files.map(function(file) { return path.resolve(modulesDirPath + '/' + file); }); 131 | }); 132 | 133 | return modules; 134 | } 135 | 136 | function displayIntroText() { 137 | var logo = '\n' + 138 | '██████╗ ██████╗ ██████╗ ███████╗███████╗ ██████╗ ██████╗ ███╗ ██╗\n' + 139 | '██╔════╝ ██╔═══██╗██╔═══██╗██╔════╝██╔════╝██╔════╝██╔═══██╗████╗ ██║\n' + 140 | '██║ ███╗██║ ██║██║ ██║███████╗█████╗ ██║ ██║ ██║██╔██╗ ██║\n' + 141 | '██║ ██║██║ ██║██║ ██║╚════██║██╔══╝ ██║ ██║ ██║██║╚██╗██║\n' + 142 | '╚██████╔╝╚██████╔╝╚██████╔╝███████║███████╗╚██████╗╚██████╔╝██║ ╚████║\n' + 143 | '╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝\n'; 144 | console.log(chalk.red(logo)); 145 | console.log('Loading Modules...'); 146 | } 147 | 148 | 149 | displayIntroText(); 150 | startReplServer(); 151 | loadModules(); -------------------------------------------------------------------------------- /example/models/person.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | 3 | var schema = new mongoose.Schema({ 4 | name: String, 5 | address: String, 6 | city: String, 7 | state: String 8 | }); 9 | 10 | module.exports = mongoose.model('Person', schema); -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "mongoose": "^4.1.2" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /example/seed.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Person = require('./models/person'); 3 | 4 | mongoose.connect('mongodb://localhost/goosecon-example'); 5 | 6 | Person.create({ 7 | name: 'Kim Kardashian', 8 | address: '1 Bayshore Blvd', 9 | city: 'Tampa', 10 | state: 'FL' 11 | }, function(err) { 12 | if (err) throw err; 13 | process.exit(); 14 | }); 15 | 16 | -------------------------------------------------------------------------------- /example/services/spoilMe.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | shop: function() { 3 | return 'Done. Bought everything and handed it to you.'; 4 | } 5 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goosecon", 3 | "version": "0.2.2", 4 | "description": "Mongoose Console that auto-loads models and other app modules", 5 | "scripts": { 6 | "test": "mocha" 7 | }, 8 | "license": "ISC", 9 | "dependencies": { 10 | "chalk": "^1.1.0", 11 | "mongoose": "^4.1.2", 12 | "rc": "^1.1.0", 13 | "underscore": "^1.8.3", 14 | "yargs": "^3.18.0" 15 | }, 16 | "devDependencies": { 17 | "chai": "^3.2.0", 18 | "mocha": "^2.2.5" 19 | }, 20 | "bin": { 21 | "goosecon": "bin/goosecon.js" 22 | }, 23 | "engines": { 24 | "node": ">=0.10.x" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/mnort9/goosecon.git" 29 | }, 30 | "preferGlobal": true 31 | } 32 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnort9/goosecon/0a8ce8449d1101794ef251b93095d7ef883eb0df/screenshot.png -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var spawn = require('child_process').spawn; 2 | var expect = require('chai').expect; 3 | var path = require('path'); 4 | var chalk = require('chalk'); 5 | 6 | describe('goosecon', function() { 7 | var replServer; 8 | var filePath = path.resolve(__dirname + '/../bin/goosecon.js'); 9 | 10 | before(function() { 11 | replServer = spawn('node', [filePath, '--mongo-db', 'mongodb://localhost/test', '--models-dir', './test/models', '--modules', './test/modules']); 12 | 13 | var result = ''; 14 | var err = ''; 15 | 16 | replServer.stdout.on('data', function(data) { 17 | result += data; 18 | // console.log(result); 19 | }); 20 | replServer.stderr.on('data', function(data) { 21 | err += data; 22 | // console.log(err); 23 | }); 24 | }); 25 | 26 | after(function() { 27 | replServer.kill(); 28 | }); 29 | 30 | it('Throws error if unable to connect to db', function(done) { 31 | var cp = spawn('node', [filePath, '--mongo-db', ' ']); 32 | 33 | var err = ''; 34 | cp.stderr.on('data', function(data) { err += data; }); 35 | cp.on('exit', function (code) { 36 | expect(err).contains('Unable to connect to db'); 37 | expect(code).to.equal(1); 38 | done(); 39 | }); 40 | }); 41 | 42 | it('Loads models into context', function(done) { 43 | test('Person.name', function(err, result) { 44 | expect(err).to.be.empty; 45 | expect(result).to.equal('model'); 46 | done(); 47 | }); 48 | }); 49 | 50 | it('Loads modules into context', function(done) { 51 | test('typeof Service', function(err, result) { 52 | expect(err).to.be.empty; 53 | expect(result).to.equal('object'); 54 | done(); 55 | }); 56 | }); 57 | 58 | function test(cmd, cb) { 59 | var result = ''; 60 | var err = ''; 61 | var timer; 62 | 63 | replServer.stdout.on('data', function(data) { result += data; }); 64 | replServer.stderr.on('data', function(data) { err += data; }); 65 | 66 | replServer.stdin.write(cmd + '\n'); 67 | 68 | function finish() { 69 | if (result.length === 0 && err.length === 0) { 70 | timer = setTimeout(finish, 500); 71 | return; 72 | } 73 | 74 | replServer.stdout.removeAllListeners('data'); 75 | replServer.stderr.removeAllListeners('data'); 76 | return cb(err, clean(result)); 77 | } 78 | timer = setTimeout(finish, 500); 79 | } 80 | 81 | // Removes ANSI colors and extracts output between quotes 82 | function clean(string) { 83 | string = chalk.stripColor(string); 84 | var substringMatches = string.match(/'([^']+)'/); 85 | 86 | if (substringMatches.length > 1) 87 | string = substringMatches[1]; 88 | 89 | return string; 90 | } 91 | }); -------------------------------------------------------------------------------- /test/models/person.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose') 2 | var Schema = mongoose.Schema; 3 | 4 | var personSchema = new Schema({ 5 | name: String 6 | }); 7 | 8 | module.exports = mongoose.model('Person', personSchema); -------------------------------------------------------------------------------- /test/modules/service.js: -------------------------------------------------------------------------------- 1 | module.exports.hello = function() { 2 | console.log('Hello World'); 3 | }; --------------------------------------------------------------------------------