Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

"bundle exec" style exec command #2460

Closed
wants to merge 6 commits into from

10 participants

@beefsack

I've created a new command to execute commands using local binaries before global ones. The command auto completes based on which binaries are available in the node_modules/.bin.

Examples:
npm exec jasmine-node spec
npm exec jake build

Tested under Linux (arch) and Windows 7. I don't have an OSX machine to try it out on though.

@andriytyurnikov

feature is totally needed

@mullr

+1: This largely obliviates the need for global module installs during day-to-day development, which is a good thing.

@aq1018

+1

@xMartin

+1

@isaacs isaacs commented on the diff
lib/exec.js
@@ -0,0 +1,38 @@
+module.exports = exec
+
+var npm = require("./npm.js")
+ , output = require("./utils/output.js")
@isaacs Owner
isaacs added a note

utils/output.js has been removed. Either use console.log for "normal" output, or require("npmlog") for log output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@isaacs isaacs commented on the diff
doc/cli/exec.md
@@ -0,0 +1,15 @@
+npm-exec(1) -- Execute command using local package bin
+====================================
+
+## SYNOPSIS
+
+ npm exec
@isaacs Owner
isaacs added a note

Synopsis should match usage string.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@isaacs
Owner

+1 on the feature, but the implementation is not correct here. For example, if you depend on grunt, and it's provided by a parent package's dependencies, then you won't be able to access the grunt binaries. Also, the npm config settings provided to scripts are not being set, etc.

A better approach would be to abstract out the utils/lifecycle.js module so that it was less scripts-object oriented, but used the same logic in a DRY fashion, rather than constructing a cmd string which sets a PATH environ. (The correct way to set an environment variable is via the env hash passed to child_process.spawn; lifecycle.js does this.)

Then, lifecycle.js can call the exec function with an additional environment variable specifying the scripts-specific stuff. (Ie, the npm_lifecycle_event and npm_lifecycle_script environment vars.)

@glenux

+1

@isaacs
Owner

+1 (If the things mentioned in #2460 (comment) are fixed, of course.)

@chakrit

Looked at the code, not sure if I could pull this off. Any chance of this getting implemented (properly) by the core team? :p

@isaacs
Owner

The issue to target for this is #3313

@isaacs isaacs closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
15 doc/cli/exec.md
@@ -0,0 +1,15 @@
+npm-exec(1) -- Execute command using local package bin
+====================================
+
+## SYNOPSIS
+
+ npm exec
@isaacs Owner
isaacs added a note

Synopsis should match usage string.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+## DESCRIPTION
+
+Adds the local package bin path (eg. node_modules/.bin) before executing the
+command.
+
+## SEE ALSO
+
+* npm-bin(1)
View
38 lib/exec.js
@@ -0,0 +1,38 @@
+module.exports = exec
+
+var npm = require("./npm.js")
+ , output = require("./utils/output.js")
@isaacs Owner
isaacs added a note

utils/output.js has been removed. Either use console.log for "normal" output, or require("npmlog") for log output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ , cp = require("child_process")
+ , os = require("os")
+
+exec.usage = "npm exec <command>\n(execute command using local package bin)"
+
+exec.completion = require("./utils/completion/local-bin.js")
+
+function exec(args, cb) {
+ if (args.length == 0) return cb("Usage:\n"+exec.usage)
+ var b = npm.bin, cmd, o = ""
+ var isWin = process.platform.match(/win/)
+
+ // Create an OS specific call
+ if (isWin)
+ cmd = "SET OLDPATH=%PATH% & SET PATH=" + b + ";%PATH% & " +
+ args.join(" ") + " & SET PATH=%OLDPATH%"
+ else
+ cmd = "PATH=" + b + ":$PATH " + args.join(" ")
+
+ p = cp.exec(cmd, function(error, stdout, stderr) {
+ // Return all captured output
+ cb(null, o)
+ })
+ p.stdout.on('data', function(data) {
+ // Capture stdout output and output to screen
+ console.log(data.toString().replace(/\n$/, ''))
+ o += data.toString()
+ })
+ p.stderr.on('data', function(data) {
+ // Capture stderr output and output to screen
+ console.error(data.toString().replace(/\n$/, ''))
+ o += data.toString()
+ })
+}
View
1  lib/npm.js
@@ -156,6 +156,7 @@ var commandCache = {}
, "restart"
, "run-script"
, "completion"
+ , "exec"
]
, plumbing = [ "build"
, "unbuild"
View
19 lib/utils/completion/local-bin.js
@@ -0,0 +1,19 @@
+module.exports = localBin
+
+var npm = require("../../npm.js")
+ , find = require("../find.js")
+ , path = require("path")
+
+function localBin (filter, cb) {
+ var b = npm.bin;
+ path.exists(b, function(exists) {
+ // Return if there is no local bin dir
+ if (!exists) return cb(null, [])
+ // Search local bin for files
+ find(b, null, 1, function(er, files) {
+ return cb(null, (files || []).map(function(f) {
+ return f.replace(b + '/', '')
+ }))
+ })
+ })
+}
Something went wrong with that request. Please try again.