├── .gitignore ├── LICENSE.md ├── README.md ├── index.js ├── menus └── npm-install.cson ├── package.json └── styles └── npm-install.less /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## The MIT License (MIT) ## 2 | 3 | Copyright (c) 2014 Hugh Kennedy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # npm-install [![stable](http://hughsk.github.io/stability-badges/dist/stable.svg)](http://github.com/hughsk/stability-badges) # 2 | 3 | [Atom](http://atom.io/) plugin to automatically install and save any selected 4 | npm packages not already included in the closest `package.json` file. 5 | 6 | ![npm-install](http://i.imgur.com/i5FKsXH.gif) 7 | 8 | ## Usage ## 9 | 10 | 1. Select the `require` or `import` statements of the 11 | packages you want to install. 12 | 2. Open the Command Palette, and type `npm install` to 13 | bring up the available commands. 14 | 3. Your packages will be installed. Enjoy! 15 | 16 | ## Configuration Options 17 | 18 | Accessible from `Settings > Packages > npm-install`: 19 | 20 | * **Install Cache:** force your installations to always or never use the local cache. 21 | * **Custom npm PATH lookup:** useful if you keep your npm 22 | in an unconventional location. Point this to the directory 23 | of your npm executable should you have 24 | [any issues](https://github.com/hughsk/atom-npm-install/issues/2). 25 | 26 | ## Changelog 27 | 28 | ### 3.0.0 29 | 30 | * Packages must now be selected to be installed. This gives 31 | you control over which packages are being installed instead 32 | of trying to work that out for you, making it simpler to 33 | support [scoped packages](https://docs.npmjs.com/misc/scope) :sparkles:. 34 | * The installation pane is now attached to the right 35 | instead of the bottom of the screen. 36 | * ES6 support! No more parsing errors, also `import` 37 | statements can be installed like you would requires too. 38 | * CoffeeScript is no longer supported. If you'd like that back, I'd recommend 39 | sticking with the previous version or submitting a pull request to 40 | [atom-selected-requires](https://github.com/hughsk/atom-selected-requires). 41 | 42 | ### 2.0.0 43 | 44 | As of version `2.0.0` keybindings are not included by default. If you miss 45 | these shortcuts, simply add the following to your keymap file: 46 | 47 | ``` coffeescript 48 | '.workspace': 49 | 'ctrl-alt-i': 'npm-install:save' 50 | 'ctrl-alt-d': 'npm-install:save-dev' 51 | ``` 52 | 53 | ## License ## 54 | 55 | MIT. See [LICENSE.md](http://github.com/hughsk/npm-install/blob/master/LICENSE.md) for details. 56 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var npmProc = null 2 | var messages 3 | var panel 4 | var outer 5 | var inner 6 | 7 | exports.activate = activate 8 | exports.config = { 9 | cacheBehaviour: { 10 | 'type': 'string', 11 | 'default': 'default', 12 | 'title': 'Install Cache', 13 | 'description': 'Completely enable or disable package caching. By default, this follows your npm configuration behaviour', 14 | 'enum': [ 15 | 'default', 16 | 'always cache', 17 | 'never cache' 18 | ] 19 | }, 20 | npmPath: { 21 | 'type': 'string', 22 | 'default': '', 23 | 'title': 'Custom npm PATH lookup', 24 | 'description': 'Useful if you keep your npm in an unconventional location' 25 | } 26 | } 27 | 28 | function activate() { 29 | const Messages = require('atom-message-panel').MessagePanelView 30 | 31 | messages = messages || new Messages({ 32 | title: 'npm install' 33 | }) 34 | 35 | atom.commands.add('atom-workspace', 'npm-install:save', Save({ dev: false })) 36 | atom.commands.add('atom-workspace', 'npm-install:save-dev', Save({ dev: true })) 37 | } 38 | 39 | function Save(opts) { 40 | const core = flattenCoreModuleList(require('resolve/lib/core.json')) 41 | const Selected = require('atom-selected-requires') 42 | const relative = require('relative-require-regex') 43 | const spawn = require('child_process').spawn 44 | const ansihtml = require('ansi-html-stream') 45 | const Combine = require('combine-stream') 46 | const ansiHTML = require('ansi-to-html') 47 | const findup = require('findup') 48 | const domify = require('domify') 49 | const which = require('which') 50 | const split = require('split') 51 | const xtend = require('xtend') 52 | const path = require('path') 53 | const fs = require('fs') 54 | const dev = !!opts.dev 55 | 56 | inner = inner || document.createElement('div') 57 | outer = outer || document.createElement('div') 58 | outer.setAttribute('class', 'npm-install') 59 | inner.setAttribute('class', 'terminal') 60 | outer.appendChild(inner) 61 | panel = panel || atom.workspace.addRightPanel({ 62 | item: outer, 63 | visible: false 64 | }) 65 | 66 | return function() { 67 | messages.clear() 68 | 69 | const editor = atom.workspace.getActiveTextEditor() 70 | const filename = editor.getPath() 71 | const dirname = path.dirname(filename) 72 | const depKey = dev ? 'devDependencies' : 'dependencies' 73 | const depFlag = dev ? '--save-dev' : '--save' 74 | 75 | try { 76 | var selected = Selected(editor) 77 | } catch(e) { 78 | return error(e) 79 | } 80 | 81 | findup(dirname, 'package.json', function(err, cwd) { 82 | if (err) return error(err) 83 | 84 | try { 85 | var pkgFile = path.join(cwd, 'package.json') 86 | var pkgData = fs.readFileSync(pkgFile, 'utf8') 87 | var pkgJSON = JSON.parse(pkgData) 88 | var pkgDeps = pkgJSON[depKey] || {} 89 | } catch(e) { 90 | return error(e) 91 | } 92 | 93 | var targets = selected.filter(function(name) { 94 | return !relative().test(name) 95 | }).filter(function(name) { 96 | return core.indexOf(name) === -1 97 | }).map(function(dir) { 98 | return dir.split('/')[0] 99 | }).filter(function(name) { 100 | return !(name in pkgDeps) 101 | }) 102 | 103 | if (!targets.length) return error(new Error('Nothing to install!')) 104 | 105 | var cache = atom.config.get('npm-install.cacheBehaviour') 106 | var exe = process.platform === 'win32' ? 'npm.cmd' : 'npm' 107 | var args = [exe, 'install', depFlag, '--color=always'] 108 | .concat(targets) 109 | 110 | if (cache === 'always cache') args.push('--cache-min=Infinity') 111 | if (cache === 'never cache') args.push('--cache-min=0') 112 | 113 | queue(dirname, args) 114 | }) 115 | } 116 | 117 | function queue(dirname, args) { 118 | if (npmProc) return error(new Error( 119 | 'Only one installation can be run at a time. ' + 120 | 'Try selecting multiple packages before commencing an install next time.' 121 | )) 122 | 123 | var convert = new ansiHTML({ 124 | newline: false, 125 | escapeXML: false, 126 | stream: true 127 | }) 128 | 129 | var addPath = (atom.config.get('npm-install:npmPath') || '').trim() 130 | var newPath = process.env.PATH + ':/usr/bin/node:/usr/local/bin' 131 | if (addPath) newPath = addPath + ':' + newPath 132 | 133 | which(args[0], { 134 | path: newPath 135 | }, function(err, npm) { 136 | if (err) return error(err) 137 | 138 | inner.innerHTML = '' 139 | npmProc = spawn(npm, args.slice(1), { 140 | cwd: dirname, 141 | env: xtend(process.env, { 142 | PATH: newPath 143 | }) 144 | }) 145 | 146 | var output = new Combine([ 147 | npmProc.stdout, 148 | npmProc.stderr 149 | ]) 150 | 151 | output.pipe(split()).on('data', function(line) { 152 | inner.appendChild(domify(convert.toHtml(line) + '
')) 153 | }) 154 | 155 | panel.show() 156 | npmProc.on('exit', function(code) { 157 | if (code !== 0) error(new Error('Unexpected exit code from npm: ' + code)) 158 | 159 | setTimeout(function() { 160 | npmProc = null 161 | panel.hide() 162 | inner.innerHTML = '' 163 | }, 1000) 164 | }) 165 | }) 166 | } 167 | } 168 | 169 | function error(err) { 170 | const Message = require('atom-message-panel').PlainMessageView 171 | const Lined = require('atom-message-panel').LineMessageView 172 | 173 | messages.attach() 174 | 175 | if (!err.loc) { 176 | messages.add(new Message({ 177 | message: err.message 178 | })) 179 | } else { 180 | messages.add(new Lined({ 181 | message: err.message, 182 | line: err.loc.line 183 | })) 184 | } 185 | } 186 | 187 | function flattenCoreModuleList(modules) { 188 | return Object.keys(modules).reduce(function (out, key) { 189 | return out.concat(modules[key]) 190 | }, []) 191 | } 192 | -------------------------------------------------------------------------------- /menus/npm-install.cson: -------------------------------------------------------------------------------- 1 | 'menu': [ 2 | { 3 | 'label': 'Packages' 4 | 'submenu': [ 5 | 'label': 'npm install' 6 | 'submenu': [ 7 | { 'label': '--save', 'command': 'npm-install:save' } 8 | { 'label': '--save-dev', 'command': 'npm-install:save-dev' } 9 | ] 10 | ] 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-install", 3 | "main": "./index", 4 | "version": "4.0.4", 5 | "private": true, 6 | "description": "Automatically install and save any missing npm modules being used in the current file", 7 | "activationCommands": { 8 | "atom-text-editor": [ 9 | "npm-install:save", 10 | "npm-install:save-dev" 11 | ] 12 | }, 13 | "repository": "https://github.com/hughsk/atom-npm-install", 14 | "license": "MIT", 15 | "engines": { 16 | "atom": ">=0.200.0" 17 | }, 18 | "dependencies": { 19 | "ansi-html-stream": "0.0.3", 20 | "ansi-to-html": "^0.3.0", 21 | "atom-message-panel": "^1.2.4", 22 | "atom-selected-requires": "^1.1.2", 23 | "combine-stream": "0.0.4", 24 | "domify": "^1.3.3", 25 | "findup": "^0.1.5", 26 | "relative-require-regex": "^1.0.1", 27 | "resolve": "1.3.2", 28 | "split": "^0.3.3", 29 | "which": "^1.1.1", 30 | "xtend": "^4.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /styles/npm-install.less: -------------------------------------------------------------------------------- 1 | // The ui-variables file is provided by base themes provided by Atom. 2 | // 3 | // See https://github.com/atom/atom-dark-ui/blob/master/stylesheets/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | 7 | atom-panel .npm-install { 8 | overflow-y: scroll; 9 | padding: @component-padding; 10 | font-size: 8px; 11 | font-family: monospace; 12 | white-space: pre; 13 | height: 100%; 14 | width: 20vw; 15 | max-width: 400px; 16 | background-color: #000; 17 | position: relative; 18 | border-top: 1px solid @background-color-highlight; 19 | text-shadow: none; 20 | color: #ccc; 21 | 22 | .terminal { 23 | position: absolute; 24 | bottom: @component-padding; 25 | } 26 | } 27 | --------------------------------------------------------------------------------