├── .gitignore ├── Dockerfile ├── circle.yml ├── cli.js ├── functional_test.sh ├── index.js ├── package.json ├── preview.gif └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mhart/alpine-node:6.5.0 2 | WORKDIR /usr/src/app 3 | COPY package.json package.json 4 | RUN npm install 5 | COPY . /usr/src/app 6 | ENTRYPOINT ["node", "cli.js"] 7 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 4.0.0 4 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | var repl = require('repl') 4 | var path = require('path') 5 | var os = require('os') 6 | var colors = require('colors') 7 | var replHistory = require('repl.history') 8 | var vm = require('vm') 9 | var exec = require('child_process').exec 10 | var loadPackages = require('./index') 11 | 12 | const TRYMODULE_PATH = process.env.TRYMODULE_PATH || path.resolve((os.homedir()), '.trymodule') 13 | const TRYMODULE_HISTORY_PATH = process.env.TRYMODULE_HISTORY_PATH || path.resolve(TRYMODULE_PATH, 'repl_history') 14 | 15 | const flags = [] 16 | const packages = {} // data looks like [moduleName, as] 17 | 18 | const makeVariableFriendly = str => str.replace(/-|\./g, '_') 19 | 20 | process.argv.slice(2).forEach(arg => { 21 | if (arg[0] === '-') { 22 | // matches '--clear', etc 23 | flags.push(arg) 24 | } else if (arg.indexOf('=') > -1) { 25 | // matches 'lodash=_', etc 26 | const i = arg.indexOf('=') 27 | const module = arg.slice(0, i) // 'lodash' 28 | const as = arg.slice(i + 1) // '_' 29 | packages[module] = makeVariableFriendly(as) // ['lodash', '_'] 30 | } else { 31 | // assume it's just a regular module name: 'lodash', 'express', etc 32 | packages[arg] = makeVariableFriendly(arg) // call it the module's name 33 | } 34 | }) 35 | 36 | if (!flags.length && !Object.keys(packages).length) { 37 | throw new Error('You need to provide some arguments!') 38 | } 39 | 40 | const logGreen = (msg) => console.log(colors.green(msg)) 41 | 42 | const hasFlag = (flag) => flags.indexOf(flag) > -1 43 | 44 | const addPackageToObject = (obj, pkg) => { 45 | logGreen(`Package '${pkg.name}' was loaded and assigned to '${pkg.as}' in the current scope`) 46 | obj[pkg.as] = pkg.package 47 | return obj 48 | } 49 | 50 | if (hasFlag('--clear')) { 51 | console.log(`Removing folder ${TRYMODULE_PATH + '/node_modules'}`) 52 | exec('rm -r ' + TRYMODULE_PATH + '/node_modules', (err, stdout, stderr) => { 53 | if (!err) { 54 | logGreen('Cache successfully cleared!') 55 | process.exit(0) 56 | } else { 57 | throw new Error('Could not remove cache! Error ' + err) 58 | } 59 | }) 60 | } else { 61 | logGreen('Gonna start a REPL with packages installed and loaded for you') 62 | 63 | // Extract 64 | loadPackages(packages, TRYMODULE_PATH).then((packages) => { 65 | const contextPackages = packages.reduce((context, pkg) => { 66 | return addPackageToObject(context, pkg) 67 | }, {}) 68 | console.log('REPL started...') 69 | if (!process.env.TRYMODULE_NONINTERACTIVE) { 70 | var replServer = repl.start({ 71 | prompt: '> ', 72 | eval: function evalWithPromises (cmd, context, filename, callback) { 73 | const script = new vm.Script(cmd) 74 | var result = script.runInContext(replServer.context) 75 | // Some libraries use non-native Promise implementations 76 | // (ie lib$es6$promise$promise$$Promise) 77 | if (result instanceof Promise || (typeof result === 'object' && typeof result.then === 'function')) { 78 | console.log('Returned a Promise. waiting for result...') 79 | result.then(function (val) { 80 | callback(null, val) 81 | }) 82 | .catch(function (err) { 83 | callback(err) 84 | }) 85 | } else { 86 | callback(null, result) 87 | } 88 | } 89 | }) 90 | replHistory(replServer, TRYMODULE_HISTORY_PATH) 91 | replServer.context = Object.assign(replServer.context, contextPackages) 92 | } 93 | }) 94 | } 95 | -------------------------------------------------------------------------------- /functional_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | TEST_PATH=./tmp_testfolder 4 | FAILED=0 5 | 6 | export TRYMODULE_PATH=$TEST_PATH 7 | export TRYMODULE_NONINTERACTIVE=true 8 | 9 | run_test() 10 | { 11 | echo "--------------" 12 | echo "## $1 | $2" 13 | $2 14 | } 15 | 16 | run_test_command() 17 | { 18 | echo "--------------" 19 | echo "## $1" 20 | echo "## $2 ?= $3" 21 | x=$(echo "$2" | ./cli.js colors | grep "$3") 22 | [ "${x}" != "$3" ] 23 | } 24 | 25 | check_failure() 26 | { 27 | if [ $? -ne 0 ] 28 | then 29 | echo "######## $1 !!!!!!!!!!!!!!" 30 | FAILED=1 31 | fi 32 | } 33 | 34 | check_should_error() 35 | { 36 | if [ $? -ne 1 ] 37 | then 38 | echo "######## $1 !!!!!!!!!!!!!!" 39 | FAILED=1 40 | fi 41 | } 42 | 43 | # Install one package 44 | run_test "Can install package 'colors'" "./cli.js colors" 45 | test -d $TEST_PATH/node_modules/colors 46 | check_failure "node_modules/colors was not installed" 47 | 48 | # Install two packages 49 | run_test "Can install packages 'colors' & 'lodash'" "./cli.js colors lodash" 50 | test -d $TEST_PATH/node_modules/colors 51 | check_failure "node_modules/colors was not installed" 52 | test -d $TEST_PATH/node_modules/lodash 53 | check_failure "node_modules/lodash was not installed" 54 | 55 | # Install two packages and bind to custom variable names 56 | run_test "Can install packages 'colors' & 'lodash' as 'c' and '_'" "./cli.js colors=c lodash=_" 57 | check_failure "Could not assign to custom variable names" 58 | 59 | # Can't install packages that doesn't exists 60 | run_test "Cannot install missing package 'coloursssss'" "./cli.js coloursssss" 61 | test -d $TEST_PATH/node_modules/coloursssss 62 | check_should_error "node_modules/coloursssss was installed" 63 | echo "NOTE: Above error is normal and is fine, we're testing that we cannot install missing packages" 64 | 65 | # Check that Promises are detected 66 | run_test_command "Can detect that a Promise is NOT returned" "'OK_TOKEN'" "OK_TOKEN" 67 | check_failure "Incorrectly detected a Promise" 68 | 69 | run_test_command "Can detect that a Promise is returned" "new Promise(function(){})" "Returned a Promise. waiting for result..." 70 | check_failure "Could not detect a Promise" 71 | 72 | run_test_command "Can detect when a non-native Promise is returned" "{then: function() {}}" "Returned a Promise. waiting for result..." 73 | check_failure "Could not detect a non-native Promise" 74 | 75 | run_test_command "Can output the result of a Promise" "new Promise(function(resolve){ resolve('OK_TOKEN') })" "OK_TOKEN" 76 | check_failure "Did not output the result of a Promise" 77 | 78 | run_test_command "Can output the result of an async Promise (2 sec)" "new Promise(function(resolve){ setTimeout(function(){resolve('OK_TOKEN')}, 2000) })" "OK_TOKEN" 79 | check_failure "Did not output the result of an async Promise" 80 | 81 | run_test_command "Can output when a Promise is rejected" "new Promise(function(resolve, reject){ reject(new Error('REJECT_TOKEN')) })" "REJECT_TOKEN" 82 | check_failure "Did not output the result of a Promise" 83 | 84 | 85 | # Clear cache 86 | run_test "Can clear the cache" "./cli.js --clear" 87 | test -d $TEST_PATH/node_modules 88 | check_should_error "node_modules existed!" 89 | 90 | if [ $FAILED -eq 0 ] 91 | then 92 | echo "\n\nALL TESTS PASSED!" 93 | rm -fr $TEST_PATH 94 | exit 0 95 | fi 96 | echo "\n\nFAILING TESTS!" 97 | rm -fr $TEST_PATH 98 | exit 1 99 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var npmi = require('npmi') 4 | var path = require('path') 5 | var colors = require('colors') 6 | 7 | const packageLocation = (pkg, installPath) => { 8 | return path.resolve(installPath, 'node_modules', pkg) 9 | } 10 | 11 | const loadPackage = (moduleName, moduleAs, installPath) => { 12 | return new Promise((resolve, reject) => { 13 | try { 14 | const loadedPackage = require(packageLocation(moduleName, installPath)) 15 | console.log(colors.blue(`'${moduleName}' was already installed since before!`)) 16 | resolve({name: moduleName, package: loadedPackage, as: moduleAs}) 17 | } catch (err) { 18 | console.log(colors.yellow(`Couldn't find '${moduleName}' locally, gonna download it now`)) 19 | npmi({name: moduleName, path: installPath}, (err, result) => { 20 | if (err) { 21 | console.log(colors.red(err.message)) 22 | if (err.statusCode === 404) { 23 | throw new Error(`Could not find package ${moduleName}`) 24 | } 25 | if (err.code === npmi.LOAD_ERR) { 26 | throw new Error('npm load error') 27 | } 28 | if (err.code === npmi.INSTALL_ERR) { 29 | throw new Error('npm install error') 30 | } 31 | } 32 | const loadedPackage = require(packageLocation(moduleName, installPath)) 33 | resolve({name: moduleName, package: loadedPackage, as: moduleAs}) 34 | }) 35 | } 36 | }) 37 | } 38 | 39 | module.exports = (packagesToInstall, installPath) => { 40 | return new Promise((resolve, reject) => { 41 | const promisesForInstallation = [] 42 | Object.keys(packagesToInstall).forEach(moduleName => { 43 | const as = packagesToInstall[moduleName] 44 | promisesForInstallation.push(loadPackage(moduleName, as, installPath)) 45 | }) 46 | Promise.all(promisesForInstallation).then(resolve).catch(reject) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trymodule", 3 | "version": "1.4.0", 4 | "description": "It's never been easier to try nodejs modules!", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npm run lint && ./functional_test.sh", 8 | "lint": "standard" 9 | }, 10 | "bin": { 11 | "trymodule": "./cli.js" 12 | }, 13 | "author": "Victor Bjelkholm (https://www.github.com/victorbjelkholm)", 14 | "repository": "https://github.com/VictorBjelkholm/trymodule", 15 | "license": "MIT", 16 | "dependencies": { 17 | "colors": "^1.1.2", 18 | "npmi": "^1.0.1", 19 | "repl.history": "^0.1.3" 20 | }, 21 | "devDependencies": { 22 | "standard": "^8.6.0" 23 | }, 24 | "engines": { 25 | "node": ">=4.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorb/trymodule/46016e73bb342bd035ea4c141b0ce2c8ded91ffa/preview.gif -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## trymodule [![Circle CI](https://circleci.com/gh/VictorBjelkholm/trymodule/tree/master.svg?style=svg)](https://circleci.com/gh/VictorBjelkholm/trymodule/tree/master) 2 | 3 | A simple cli tool for trying out different nodejs modules. 4 | 5 | ![trymodule demo](preview.gif) 6 | 7 | ## Installation 8 | 9 | `npm install -g trymodule` 10 | 11 | ## Usage 12 | 13 | `trymodule colors` 14 | 15 | Downloads the module colors if needed, and starts a nodejs REPL with colors loaded in the current scope, ready for you to use. 16 | 17 | `trymodule colors lodash` 18 | 19 | Same as above but with many packages in one go! 20 | 21 | `trymodule colors=c lodash=l` 22 | 23 | Assign packages to custom variable names. 24 | 25 | `trymodule --clear` 26 | 27 | Removes the directory where trymodules stores the node modules. Removes `TRYMODULE_PATH + '/node_modules'` 28 | 29 | ## Configuration 30 | 31 | There are a couple of environment variables you can use to customize trymodule. 32 | 33 | `TRYMODULE_PATH` for setting the path of where modules are stored. By default this is `$HOME/.trymodule` or `$USERPROFILE/.trymodule` 34 | 35 | `TRYMODULE_NONINTERACTIVE` for making trymodule not fire up the repl in the end. This is useful if you want to just install some packages for future use. By default this is undefined. Setting it to any value would make trymodule non-interactive. 36 | 37 | `TRYMODULE_HISTORY_PATH` for changing where to save the repl history. Should be pointing to a user write-able file. Defaults to `$TRYMODULE_PATH/repl_history` 38 | 39 | You can set the environment variables for one session with `export TRYMODULE_PATH=/usr/bin/trymodule` or for just one time by doing `TRYMOUDLE_PATH=/usr/bin/trymodule trymodule colors`. 40 | 41 | ## Support / Help 42 | 43 | If you have any questions, open a Github issue here: 44 | [github.com/VictorBjelkholm/trymodule/issues/new](https://github.com/VictorBjelkholm/trymodule/issues/new) 45 | 46 | or feel free to contact me on Twitter here: 47 | [@VictorBjelkholm](https://twitter.com/VictorBjelkholm) 48 | 49 | ## Inspiration 50 | 51 | Inspiration comes from a leiningen plugin called [lein-try](https://github.com/rkneufeld/lein-try) that allows you to try out clojure libraries without having to declare them in an existing project. Thanks to [@rkneufeld](https://github.com/rkneufeld) for the inspiration! 52 | 53 | ## License 54 | 55 | The MIT License (MIT) 56 | 57 | Copyright (c) 2016 Victor Bjelkholm 58 | 59 | 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: 60 | 61 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 62 | 63 | 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. 64 | --------------------------------------------------------------------------------