diff --git a/app/common/src/main/scala/io/scalajs/nodejs/child_process/ChildProcess.scala b/app/common/src/main/scala/io/scalajs/nodejs/child_process/ChildProcess.scala index 3d0adceff..b8ce019cd 100644 --- a/app/common/src/main/scala/io/scalajs/nodejs/child_process/ChildProcess.scala +++ b/app/common/src/main/scala/io/scalajs/nodejs/child_process/ChildProcess.scala @@ -1,9 +1,8 @@ package io.scalajs.nodejs package child_process -import io.scalajs.RawOptions +import io.scalajs.nodejs import io.scalajs.nodejs.events.IEventEmitter -import io.scalajs.nodejs.buffer.Buffer import scala.scalajs.js import scala.scalajs.js.annotation.JSImport @@ -12,91 +11,56 @@ import scala.scalajs.js.| /** * The child_process module provides the ability to spawn child processes in a manner that is similar, * but not identical, to popen(3). This capability is primarily provided by the child_process.spawn() function. + * * @see https://nodejs.org/api/child_process.html - * @author lawrence.daniels@gmail.com */ @js.native trait ChildProcess extends IEventEmitter { + def kill(signal: js.UndefOr[Int | String] = js.native): Unit = js.native +} - /** - * Spawns a shell then executes the command within that shell, buffering any generated output. - * @param command The command to run, with space-separated arguments - * @param options the execution [[ExecOptions options]] - * @return the [[ChildProcess]] - * @example {{{ child_process.exec(command[, options][, callback]) }}} - */ - def exec(command: String, options: ExecOptions | RawOptions): this.type = js.native +/** + * @see https://nodejs.org/api/child_process.html + */ +@JSImport("child_process", JSImport.Namespace) +@js.native +object ChildProcess extends scala.scalajs.js.Object { + def exec( + command: String, + options: js.UndefOr[ExecOptions | io.scalajs.RawOptions] = js.undefined, + callback: js.Function3[ + nodejs.Error, + Output, + Output, + Any + ] + ): ChildProcess = js.native - /** - * Spawns a shell then executes the command within that shell, buffering any generated output. - * @param command The command to run, with space-separated arguments - * @param callback called with the output when process terminates - * @return the [[ChildProcess]] - * @example {{{ child_process.exec(command[, options][, callback]) }}} - */ - def exec(command: String, callback: js.Function3[Error, Buffer | String, Buffer | String, Any]): this.type = - js.native + def execSync( + command: String, + options: ExecOptions | io.scalajs.RawOptions = js.native + ): Output = js.native - /** - * Spawns a shell then executes the command within that shell, buffering any generated output. - * @param command The command to run, with space-separated arguments - * @param options the execution [[ExecOptions options]] - * @param callback called with the output when process terminates - * @return the [[ChildProcess]] - * @example {{{ child_process.exec(command[, options][, callback]) }}} - */ - def exec(command: String, - options: ExecOptions | RawOptions, - callback: js.Function3[Error, Buffer | String, Buffer | String, Any]): this.type = js.native + def fork( + modulePath: String, + args: js.Array[String] = js.native, + options: ForkOptions | io.scalajs.RawOptions = js.native + ): ChildProcess = js.native - /** - * The child_process.fork() method is a special case of child_process.spawn() used specifically to spawn new - * Node.js processes. Like child_process.spawn(), a ChildProcess object is returned. The returned ChildProcess - * will have an additional communication channel built-in that allows messages to be passed back and forth between - * the parent and child. See child.send() for details. - * - * It is important to keep in mind that spawned Node.js child processes are independent of the parent with exception - * of the IPC communication channel that is established between the two. Each process has its own memory, with their - * own V8 instances. Because of the additional resource allocations required, spawning a large number of child Node.js - * processes is not recommended. - * - * By default, child_process.fork() will spawn new Node.js instances using the process.execPath of the parent process. - * The execPath property in the options object allows for an alternative execution path to be used. - * - * Node.js processes launched with a custom execPath will communicate with the parent process using the - * file descriptor (fd) identified using the environment variable NODE_CHANNEL_FD on the child process. - * The input and output on this fd is expected to be line delimited JSON objects. - * - * Note: Unlike the fork(2) POSIX system call, child_process.fork() does not clone the current process. - * @param modulePath The module to run in the child - * @param args List of string arguments - * @param options the fork [[ForkOptions options]] - * @return the [[ChildProcess]] - * @example {{{ child_process.fork(modulePath[, args][, options]) }}} - */ - def fork(modulePath: String, - args: js.Array[String] = js.native, - options: ForkOptions | RawOptions = js.native): this.type = js.native + def spawn( + command: String, + args: js.Array[String] = js.native, + options: SpawnOptions | io.scalajs.RawOptions = js.native + ): ChildProcess = js.native - /** - * The child_process.spawn() method spawns a new process using the given command, with command line arguments - * in args. If omitted, args defaults to an empty array. - * @param command The command to run - * @param args List of string arguments - * @param options the spawn [[SpawnOptions options]] - * @return the [[ChildProcess]] - * @example {{{ child_process.spawn(command[, args][, options]) }}} - */ - def spawn(command: String, - args: js.Array[String] = js.native, - options: SpawnOptions | RawOptions = js.native): this.type = js.native + def spawnSync( + command: String, + args: js.Array[String] = js.native, + options: SpawnOptions | io.scalajs.RawOptions = js.native + ): SpawnResult = js.native + def spawnSync( + command: String, + options: SpawnOptions | io.scalajs.RawOptions + ): SpawnResult = js.native } - -/** - * ChildProcess Singleton - * @author lawrence.daniels@gmail.com - */ -@js.native -@JSImport("child_process", JSImport.Namespace) -object ChildProcess extends ChildProcess diff --git a/app/common/src/main/scala/io/scalajs/nodejs/child_process/SpawnOptions.scala b/app/common/src/main/scala/io/scalajs/nodejs/child_process/SpawnOptions.scala index 5e749121f..3a3f6ddec 100644 --- a/app/common/src/main/scala/io/scalajs/nodejs/child_process/SpawnOptions.scala +++ b/app/common/src/main/scala/io/scalajs/nodejs/child_process/SpawnOptions.scala @@ -1,13 +1,11 @@ package io.scalajs.nodejs.child_process -import io.scalajs.JsNumber - import scala.scalajs.js - import scala.scalajs.js.| /** * Spawn Options + * * @param cwd Current working directory of the child process * @param env Environment key-value pairs * @param argv0 Explicitly set the value of argv[0] sent to the child process. @@ -22,12 +20,15 @@ import scala.scalajs.js.| * The shell should understand the -c switch on UNIX, or /d /s /c on Windows. Defaults to false (no shell). * @author lawrence.daniels@gmail.com */ -class SpawnOptions(val cwd: js.UndefOr[String] = js.undefined, - val env: js.Any = js.undefined, - val argv0: js.UndefOr[String] = js.undefined, - val stdio: js.UndefOr[Array[String] | String] = js.undefined, - val detached: js.UndefOr[Boolean] = js.undefined, - val uid: js.UndefOr[JsNumber] = js.undefined, - val gid: js.UndefOr[JsNumber] = js.undefined, - val shell: js.UndefOr[Boolean | String] = js.undefined) - extends js.Object +class SpawnOptions( + val cwd: js.UndefOr[String] = js.undefined, + val env: js.Any = js.undefined, + val argv0: js.UndefOr[String] = js.undefined, + val stdio: js.UndefOr[js.Array[String | io.scalajs.nodejs.FileDescriptor] | js.Array[ + io.scalajs.nodejs.FileDescriptor + ] | js.Array[String] | String] = js.undefined, + val detached: js.UndefOr[Boolean] = js.undefined, + val uid: js.UndefOr[Int] = js.undefined, + val gid: js.UndefOr[Int] = js.undefined, + val shell: js.UndefOr[Boolean | String] = js.undefined +) extends js.Object diff --git a/app/common/src/main/scala/io/scalajs/nodejs/child_process/SpawnResult.scala b/app/common/src/main/scala/io/scalajs/nodejs/child_process/SpawnResult.scala new file mode 100644 index 000000000..0714cc22e --- /dev/null +++ b/app/common/src/main/scala/io/scalajs/nodejs/child_process/SpawnResult.scala @@ -0,0 +1,13 @@ +package io.scalajs.nodejs.child_process + +import scala.scalajs.js + +class SpawnResult( + val pid: Int, + val output: js.Array[Output], + val stdout: Output, + val stderr: Output, + val status: js.UndefOr[Int], + val signal: js.UndefOr[String], + val error: js.UndefOr[js.Error] +) extends js.Object diff --git a/app/common/src/main/scala/io/scalajs/nodejs/child_process/package.scala b/app/common/src/main/scala/io/scalajs/nodejs/child_process/package.scala new file mode 100644 index 000000000..cf2b71ac7 --- /dev/null +++ b/app/common/src/main/scala/io/scalajs/nodejs/child_process/package.scala @@ -0,0 +1,23 @@ +package io.scalajs.nodejs + +import io.scalajs.nodejs +import io.scalajs.util.PromiseHelper._ + +import scala.concurrent.Future +import scala.scalajs.js +import scala.scalajs.js.| + +package object child_process { + type Output = nodejs.buffer.Buffer | String + + implicit final class ChildProcessExtensions(val cp: ChildProcess.type) extends AnyVal { + @inline + def execFuture( + command: String, + options: js.UndefOr[ExecOptions | io.scalajs.RawOptions] = js.undefined + ): Future[(Output, Output)] = { + promiseWithError2[nodejs.Error, Output, Output](cp.exec(command, options, _)) + } + } + +} diff --git a/app/common/src/test/scala/io/scalajs/nodejs/child_process/ChildProcessAsyncTest.scala b/app/common/src/test/scala/io/scalajs/nodejs/child_process/ChildProcessAsyncTest.scala new file mode 100644 index 000000000..33184f600 --- /dev/null +++ b/app/common/src/test/scala/io/scalajs/nodejs/child_process/ChildProcessAsyncTest.scala @@ -0,0 +1,21 @@ +package io.scalajs.nodejs.child_process + +import io.scalajs.nodejs.buffer.Buffer +import org.scalatest.AsyncFunSpec + +import scala.concurrent.ExecutionContext + +class ChildProcessAsyncTest extends AsyncFunSpec { + override implicit val executionContext = ExecutionContext.Implicits.global + + describe("ChildProcess") { + it("supports execFuture(...)") { + for { + r <- ChildProcess.execFuture("cat ./package.json | wc -l") + } yield { + assert(r._1.asInstanceOf[Buffer].toString().trim.toInt > 0) + } + } + } + +} diff --git a/app/common/src/test/scala/io/scalajs/nodejs/child_process/ChildProcessTest.scala b/app/common/src/test/scala/io/scalajs/nodejs/child_process/ChildProcessTest.scala index 403bf4cc8..16046008b 100644 --- a/app/common/src/test/scala/io/scalajs/nodejs/child_process/ChildProcessTest.scala +++ b/app/common/src/test/scala/io/scalajs/nodejs/child_process/ChildProcessTest.scala @@ -9,6 +9,7 @@ import scala.scalajs.js.| /** * ChildProcess Test + * * @author lawrence.daniels@gmail.com */ class ChildProcessTest extends FunSpec { @@ -18,7 +19,7 @@ class ChildProcessTest extends FunSpec { it("supports exec(...)") { ChildProcess.exec( "cat ./package.json | wc -l", - (error: Error, stdout: Buffer | String, stderr: Buffer | String) => { + callback = (error: Error, stdout: Buffer | String, stderr: Buffer | String) => { if (isDefined(error)) { console.error(s"exec error: $error") } @@ -28,6 +29,10 @@ class ChildProcessTest extends FunSpec { ) } + it("supports execSync(...)") { + val r = ChildProcess.execSync("cat ./package.json | wc -l") + assert(r.asInstanceOf[Buffer].toString().trim.toInt > 0) + } } }