@@ -3,32 +3,87 @@ package com.typesafe.sbt.jse.npm
33
44import java .io .File
55
6- import com .typesafe .sbt .jse .engines .{Engine , JsExecutionResult }
6+ import com .typesafe .sbt .jse .engines .{Engine , JsExecutionResult , LocalEngine }
77
88import scala .collection .immutable
9+ import scala .collection .JavaConverters ._
910import scala .collection .mutable .ListBuffer
11+ import scala .sys .process .{Process , ProcessLogger }
12+ import scala .util .Try
1013
1114/**
1215 * A JVM class for performing NPM commands. Requires a JS engine to use.
1316 */
14- class Npm (engine : Engine , npmFile : File , verbose : Boolean = false ) {
17+ class Npm (engine : Engine , npmFile : File , verbose : Boolean = false , preferSystemNpm : Boolean = true ) {
1518
19+ // TODO deprecate all constructors, file not always necessary (when using local installed)
1620 def this (engine : Engine , npmFile : File ) = this (engine, npmFile, false )
1721
22+ def install (global : Boolean = false , names : Seq [String ] = Nil , outSink : String => Unit , errSink : String => Unit ): JsExecutionResult = {
23+ val args = ListBuffer [String ]()
24+ args += " install"
25+ if (global) args += " -g"
26+ if (verbose) args += " --verbose"
27+ // args += "--no-audit" // TODO: audit as optional
28+ args ++= names
29+ invokeNpm(args, outSink, errSink)
30+ }
31+
1832 def update (global : Boolean = false , names : Seq [String ] = Nil , outSink : String => Unit , errSink : String => Unit ): JsExecutionResult = {
1933 val args = ListBuffer [String ]()
2034 args += " update"
2135 if (global) args += " -g"
2236 if (verbose) args += " --verbose"
37+ // args += "--no-audit" // TODO: audit as optional
2338 args ++= names
2439 invokeNpm(args, outSink, errSink)
2540 }
2641
42+ // TODO: Also provide ci subcommand, see https://github.com/typesafehub/npm/issues/28
43+
44+ private def detectNpm (command : String ): Option [String ] = {
45+ val npmExists = Try (Process (s " $command --version " ).!! ).isSuccess
46+ if (! npmExists) {
47+ println(s " Warning: npm detection failed. Tried the command: $command" )
48+ None
49+ } else {
50+ Some (command)
51+ }
52+ }
53+
2754 private def invokeNpm (args : ListBuffer [String ], outSink : String => Unit , errSink : String => Unit ): JsExecutionResult = {
2855 if (! engine.isNode) {
2956 throw new IllegalStateException (" node not found: a Node.js installation is required to run npm." )
3057 }
31- engine.executeJs(npmFile, args.to[immutable.Seq ], Map .empty, outSink, errSink)
58+
59+ def executeJsNpm (): JsExecutionResult = engine.executeJs(npmFile, args.to[immutable.Seq ], Map .empty, outSink, errSink)
60+
61+ engine match {
62+ case localEngine : LocalEngine if preferSystemNpm => {
63+ // The first argument always is the command of the js engine, e.g. either just "node", "phantomjs,.. or a path like "/usr/bin/node"
64+ // So we first try to detect a npm command in the same folder in case the user provided an explicit command that is a path
65+ val localEngineCmd = new File (localEngine.stdArgs.head)
66+ val localNpmCmd = if (localEngineCmd.getParent() == null ) {
67+ // Pretty sure the command was not a path but just something like "node"
68+ // Therefore we assume the npm command is on the path, just like the js engine command
69+ detectNpm(" npm" )
70+ } else {
71+ val cmdPath = new File (localEngineCmd.getParentFile, " npm" ).getCanonicalPath
72+ detectNpm(cmdPath).orElse(detectNpm(" npm" ))
73+ }
74+ localNpmCmd match {
75+ case Some (cmd) => {
76+ val allArgs = immutable.Seq (cmd) ++ args
77+ val pb = new ProcessBuilder (LocalEngine .prepareArgs(allArgs).asJava)
78+ pb.environment().putAll(localEngine.stdEnvironment.asJava)
79+ JsExecutionResult (Process (pb).! (ProcessLogger (outSink, errSink)))
80+ }
81+ case None => executeJsNpm() // TODO log that fallback is happening
82+ }
83+ }
84+ case _ => // e.g. Trireme provides node, but is not a local install and does not provide npm, therefore fallback using the webjar npm
85+ executeJsNpm()
86+ }
3287 }
3388
3489}
0 commit comments