├── .gitignore ├── .travis.yml ├── README.md ├── package-lock.json ├── package.json ├── renovate.json ├── src ├── Mod.ts ├── cli.ts ├── famous.ts ├── index.ts └── utils.ts ├── test └── Mod.test.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | built 2 | node_modules 3 | .vscode 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '6' 5 | - '8' 6 | - '9' 7 | - '10' 8 | 9 | 10 | script: 11 | - npm test 12 | 13 | 14 | # Trigger a push build on master/release and greenkeeper branches + PRs build on every branches 15 | # Avoid double build on PRs (See https://github.com/travis-ci/travis-ci/issues/1147) 16 | branches: 17 | only: 18 | - master 19 | - /^greenkeeper.*$/ 20 | 21 | deploy: 22 | # runs semantic-release on each 23 | - provider: script 24 | script: npm ci && npx semantic-release 25 | skip_cleanup: true 26 | on: 27 | tags: false 28 | all_branches: true 29 | node: '10' 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # npm-try 2 | 3 | 🚆 Quickly try npm packages without writing boilerplate code. 4 | 5 | [![asciicast](https://asciinema.org/a/250257.svg)](https://asciinema.org/a/250257) 6 | 7 | [![Build Status](https://travis-ci.com/luin/npm-try.svg?branch=master)](https://travis-ci.com/luin/npm-try) 8 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 9 | [![npm latest version](https://img.shields.io/npm/v/npm-try-cli/latest.svg)](https://www.npmjs.com/package/npm-try-pkg) 10 | 11 | npm-try provides a REPL interface for you to try NPM packages without writing any boilerplate code. 12 | 13 | Simply run `npm-try [packages ..]` anywhere on the shell and npm-try will show a REPL interface which has all packages installed and assigned to variables. 14 | 15 | ## Features 16 | 17 | * Super easy to use! 18 | * npm-try even defines variables for you 19 | * Top-level `await` support (requires Node.js >= 10) 20 | 21 | ## Install 22 | 23 | ```shell 24 | $ npm install -g npm-try-cli 25 | ``` 26 | 27 | ## Examples 28 | 29 | Wanna try the `capitalize` method of lodash package? 30 | 31 | ```shell 32 | $ npm-try lodash 33 | ✔ const lodash = require('lodash') 34 | > lodash.capitalize('hello world') 35 | 'Hello world' 36 | ``` 37 | 38 | Would like to try multiple packages at the same time? 39 | 40 | ```shell 41 | $ npm-try lodash underscore 42 | ✔ const lodash = require('lodash') 43 | ✔ const underscore = require('underscore') 44 | > lodash.first([1, 2, 3]) 45 | 1 46 | > underscore.first([1, 2, 3]) 47 | 1 48 | ``` 49 | 50 | A previous version? You can specify versions with `@` symbol (Missing the old days when the `pluck` still exists).: 51 | 52 | ```shell 53 | $ npm-try lodash@3 54 | ✔ const lodash = require('lodash') 55 | > lodash.pluck 56 | [Function: pluck] 57 | ``` 58 | 59 | Asynchronous operations? `await` is supported out-of-the-box. Let's try ioredis: 60 | 61 | ```shell 62 | $ npm-try ioredis 63 | ✔ const Redis = require('ioredis') 64 | > const redis = new Redis() 65 | undefined 66 | > await redis.get('foo') 67 | '123' 68 | ``` 69 | 70 | ## Create a Project 71 | REPL is not enough sometimes when you want to write more code to test with packages. npm-try offers `--out-dir`/`-o` option to create a self-contained project so you can write your test code at the drop of a hat. 72 | 73 | ```shell 74 | $ npm-try lodash -o try-lodash 75 | ✔ Installing lodash... 76 | ✔ The project created at /Users/luin/try-lodash 77 | ``` 78 | 79 | ## Limitations 80 | 81 | Testing multiple versions of the same package is not supported. The following command will only have lodash@3 provided: 82 | 83 | ```shell 84 | $ npm-try lodash@4 lodash@3 85 | ✔ const lodash = require('lodash') 86 | ✔ const lodash = require('lodash') 87 | > lodash.VERSION 88 | '3.10.1' 89 | ``` 90 | 91 | ## License 92 | 93 | MIT 94 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-try-cli", 3 | "version": "0.0.0-development", 4 | "description": "Quickly try npm packages without writing boilerplate code", 5 | "main": "index.js", 6 | "bin": { 7 | "npm-try": "built/cli.js" 8 | }, 9 | "scripts": { 10 | "prepublishOnly": "npm run build && npm test", 11 | "start": "tsc && node built/index.js", 12 | "build": "tsc", 13 | "mocha": "mocha -r ts-node/register test/**/*.test.ts", 14 | "test": "npm run lint && npm run mocha", 15 | "lint": "tslint --fix src/**/*.ts test/**/*.test.ts", 16 | "semantic-release": "semantic-release" 17 | }, 18 | "files": [ 19 | "built/", 20 | "cli.sh" 21 | ], 22 | "keywords": [ 23 | "npm", 24 | "try" 25 | ], 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/luin/npm-try.git" 29 | }, 30 | "author": "Zihua Li ", 31 | "license": "MIT", 32 | "devDependencies": { 33 | "@types/chai": "4.1.7", 34 | "@types/fs-extra": "7.0.0", 35 | "@types/meow": "5.0.0", 36 | "@types/mocha": "5.2.7", 37 | "@types/node": "12.0.5", 38 | "@types/ora": "3.2.0", 39 | "chai": "4.2.0", 40 | "mocha": "6.1.4", 41 | "ts-node": "8.2.0", 42 | "tslint": "5.17.0", 43 | "typescript": "3.5.1", 44 | "semantic-release": "15.13.12" 45 | }, 46 | "dependencies": { 47 | "chalk": "^3.0.0", 48 | "fs-extra": "^8.0.1", 49 | "lodash.assign": "^4.2.0", 50 | "meow": "^6.0.0", 51 | "ora": "^4.0.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/Mod.ts: -------------------------------------------------------------------------------- 1 | import famous from "./famous"; 2 | 3 | function getVariableNameFromPackage(packageName: string): string { 4 | packageName = packageName.toLowerCase(); 5 | if (typeof famous[packageName] === "string") { 6 | return famous[packageName]; 7 | } 8 | 9 | if (packageName[0] === "@") { 10 | packageName = packageName.slice(1); 11 | } 12 | 13 | packageName = packageName.replace(/[\.\-]js$/, ""); 14 | packageName = packageName.replace(/^js-/, ""); 15 | 16 | if (packageName.match(/^\w[\w\d]*$/)) { 17 | return packageName; 18 | } 19 | 20 | const dots = packageName.split(/[\/\.]/); 21 | if (dots.length > 0) { 22 | packageName = dots[dots.length - 1]; 23 | } 24 | const words = packageName.split("-").map((word, index) => { 25 | if (index === 0 || !word) { 26 | return word; 27 | } 28 | return word[0].toUpperCase() + word.slice(1); 29 | }); 30 | 31 | return words.join(""); 32 | } 33 | 34 | export class Mod { 35 | public readonly packageName: string; 36 | public readonly variableName: string; 37 | 38 | constructor(readonly target: string) { 39 | const versionIndex = target.lastIndexOf("@"); 40 | if (versionIndex > 0) { 41 | this.packageName = target.slice(0, versionIndex); 42 | } else { 43 | this.packageName = target; 44 | } 45 | this.variableName = getVariableNameFromPackage(this.packageName); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import chalk from "chalk"; 4 | import { spawn } from "child_process"; 5 | import { join } from "path"; 6 | import { supportReplAwait } from "./utils"; 7 | 8 | const args = [join(__dirname, "index")].concat(process.argv.slice(2)); 9 | 10 | if (supportReplAwait()) { 11 | args.unshift("--experimental-repl-await"); 12 | } else { 13 | console.log( 14 | chalk.yellow( 15 | `support for "await" is disabled (required Node.js >= 10, current ${process.version}).`, 16 | ), 17 | ); 18 | } 19 | 20 | spawn("node", args, { 21 | stdio: "inherit", 22 | }); 23 | -------------------------------------------------------------------------------- /src/famous.ts: -------------------------------------------------------------------------------- 1 | const map: { [key: string]: string } = { 2 | "ioredis": "Redis", 3 | "redis": "Redis", 4 | "js-yaml": "yaml", 5 | "io": "socket.io", 6 | "lodash": "_", 7 | }; 8 | 9 | export default map; 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { moveSync, writeFileSync } from "fs-extra"; 2 | import * as meow from "meow"; 3 | import * as ora from "ora"; 4 | import { join, resolve } from "path"; 5 | import * as repl from "repl"; 6 | import { Mod } from "./Mod"; 7 | import { createProjectDirectory, installModule } from "./utils"; 8 | 9 | const cli = meow( 10 | ` 11 | Usage 12 | $ npm-try [package ..] 13 | 14 | Options 15 | --registry, -r Specify the registry that will be used when npm install. 16 | --out-dir, -o Create a self-contained project with the packages installed in the directory. 17 | --verbose, -v Verbose mode. Print debugging messages about their progress. 18 | 19 | Examples 20 | $ npm-try lodash 21 | $ npm-try lodash@3 22 | $ npm-try lodash underscore 23 | $ npm-try lodash -o my-lodash 24 | `, 25 | { 26 | flags: { 27 | registry: { 28 | type: "string", 29 | alias: "r", 30 | }, 31 | outDir: { 32 | type: "string", 33 | alias: "o", 34 | }, 35 | verbose: { 36 | default: false, 37 | type: "boolean", 38 | alias: "v", 39 | }, 40 | }, 41 | }, 42 | ); 43 | 44 | async function run() { 45 | const mods = cli.input.map((target) => new Mod(target)); 46 | const path = await createProjectDirectory(); 47 | const defines: string[] = []; 48 | let installed = 0; 49 | for (const mod of mods) { 50 | const spinner = ora(`Installing ${mod.target}...`).start(); 51 | try { 52 | await installModule(path, [mod.target], cli.flags); 53 | const succeed = `const ${mod.variableName} = require('${mod.packageName}')`; 54 | if (cli.flags.outDir) { 55 | spinner.succeed(); 56 | } else { 57 | spinner.succeed(succeed); 58 | } 59 | defines.push(succeed); 60 | installed += 1; 61 | } catch (err) { 62 | spinner.fail( 63 | `Install ${mod.target} failed. Does package "${mod.packageName}" exist?`, 64 | ); 65 | } 66 | } 67 | 68 | const success = installed > 0 || mods.length === 0; 69 | if (!success) { 70 | process.exit(1); 71 | } 72 | 73 | if (cli.flags.outDir) { 74 | createProject(path, cli.flags.outDir, defines); 75 | } else { 76 | startREPL(path, mods); 77 | } 78 | } 79 | 80 | function createProject(path: string, to: string, defines: string[]) { 81 | const spinner = ora(`Creating project at ${to}...`).start(); 82 | to = resolve(process.cwd(), to); 83 | const content = `'use strict'\n\n${defines.join( 84 | "\n", 85 | )}\n\n// Your awesome code here\n`; 86 | try { 87 | moveSync(path, to); 88 | writeFileSync(join(to, "index.js"), content); 89 | spinner.succeed("The project created at " + to); 90 | } catch (err) { 91 | spinner.fail(err.message); 92 | } 93 | } 94 | 95 | function startREPL(path: string, mods: Mod[]) { 96 | const r = repl.start({ prompt: "> " }); 97 | initializeContext(); 98 | 99 | function initializeContext() { 100 | r.context.module.paths.unshift(join(path, "node_modules")); 101 | mods.forEach((mod) => { 102 | try { 103 | const required = r.context.module.require(mod.packageName); 104 | if (mod.variableName) { 105 | r.context[mod.variableName] = required; 106 | } 107 | } catch (err) { 108 | /* ignore errors here */ 109 | } 110 | }); 111 | } 112 | } 113 | 114 | run(); 115 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from "child_process"; 2 | import { mkdtempSync, writeFileSync } from "fs"; 3 | import { tmpdir } from "os"; 4 | import { join as joinPath } from "path"; 5 | 6 | function getPackageJSONContent(name: string): string { 7 | const json = { 8 | name, 9 | version: "1.0.0", 10 | main: "index.js", 11 | }; 12 | return JSON.stringify(json); 13 | } 14 | 15 | export function createProjectDirectory(): Promise { 16 | return new Promise((resolve, reject) => { 17 | const path = mkdtempSync(joinPath(tmpdir(), "try-package-")); 18 | const name = "try-package-module"; 19 | const packageJSON = getPackageJSONContent(name); 20 | writeFileSync(joinPath(path, "package.json"), packageJSON); 21 | resolve(path); 22 | }); 23 | } 24 | 25 | export function installModule( 26 | path: string, 27 | packages: string[], 28 | flags: { [key: string]: string }, 29 | ): Promise { 30 | return new Promise((resolve, reject) => { 31 | const args = ["install"]; 32 | if (flags.registry) { 33 | args.push(`--registry=${flags.registry}`); 34 | } 35 | const npm = spawn( 36 | process.platform === "win32" ? "npm.cmd" : "npm", 37 | args.concat(packages), 38 | { 39 | cwd: path, 40 | stdio: flags.verbose ? "inherit" : "ignore", 41 | }, 42 | ); 43 | 44 | npm.on("close", (code) => { 45 | if (code === 0) { 46 | resolve(); 47 | } else { 48 | const error = new Error( 49 | `npm install ${packages.join(" ")} exited with code ${code}`, 50 | ); 51 | reject(error); 52 | } 53 | }); 54 | }); 55 | } 56 | 57 | export function supportReplAwait(): boolean { 58 | const version = Number(process.versions.node.split(".")[0]); 59 | return version >= 10; 60 | } 61 | -------------------------------------------------------------------------------- /test/Mod.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Mod } from "../src/Mod"; 3 | 4 | describe("#variableName", () => { 5 | it("get correct variableName", () => { 6 | expect(new Mod("ioredis").variableName).to.eql("Redis"); 7 | expect(new Mod("ioredis@2").variableName).to.eql("Redis"); 8 | expect(new Mod("core-js").variableName).to.eql("core"); 9 | expect(new Mod("lodash").variableName).to.eql("_"); 10 | expect(new Mod("lodash.assign").variableName).to.eql("assign"); 11 | expect(new Mod("a.js").variableName).to.eql("a"); 12 | expect(new Mod("mod-js").variableName).to.eql("mod"); 13 | expect(new Mod("jsyaml").variableName).to.eql("jsyaml"); 14 | expect(new Mod("js-yaml").variableName).to.eql("yaml"); 15 | expect(new Mod("@angular/core").variableName).to.eql("core"); 16 | expect(new Mod("@angular/core@2.9").variableName).to.eql("core"); 17 | }); 18 | 19 | it("get correct packageName", () => { 20 | expect(new Mod("ioredis").packageName).to.eql("ioredis"); 21 | expect(new Mod("ioredis@2").packageName).to.eql("ioredis"); 22 | expect(new Mod("core-js").packageName).to.eql("core-js"); 23 | expect(new Mod("lodash").packageName).to.eql("lodash"); 24 | expect(new Mod("lodash.assign").packageName).to.eql("lodash.assign"); 25 | expect(new Mod("a.js").packageName).to.eql("a.js"); 26 | expect(new Mod("mod-js").packageName).to.eql("mod-js"); 27 | expect(new Mod("jsyaml").packageName).to.eql("jsyaml"); 28 | expect(new Mod("js-yaml").packageName).to.eql("js-yaml"); 29 | expect(new Mod("@angular/core").packageName).to.eql("@angular/core"); 30 | expect(new Mod("@angular/core@2.9").packageName).to.eql("@angular/core"); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "resolveJsonModule": true, 5 | "module": "commonjs", 6 | "noImplicitAny": true, 7 | "removeComments": true, 8 | "preserveConstEnums": true, 9 | "outDir": "built" 10 | }, 11 | "include": [ 12 | "./src/**/*.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "semicolon": false, 5 | "object-literal-sort-keys": false, 6 | "no-console": false 7 | } 8 | } 9 | --------------------------------------------------------------------------------