├── .eslintrc.json ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── package.json ├── src └── Node │ ├── ChildProcess.js │ ├── ChildProcess.purs │ ├── ChildProcess │ ├── Aff.purs │ ├── Types.js │ └── Types.purs │ └── UnsafeChildProcess │ ├── Safe.js │ ├── Safe.purs │ ├── Unsafe.js │ └── Unsafe.purs └── test ├── Main.purs └── sleep.sh /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module" 5 | }, 6 | "extends": "eslint:recommended", 7 | "env": { 8 | "node": true 9 | }, 10 | "rules": { 11 | "strict": [2, "global"], 12 | "block-scoped-var": 2, 13 | "consistent-return": 2, 14 | "eqeqeq": [2, "smart"], 15 | "guard-for-in": 2, 16 | "no-caller": 2, 17 | "no-extend-native": 2, 18 | "no-loop-func": 2, 19 | "no-new": 2, 20 | "no-param-reassign": 2, 21 | "no-return-assign": 2, 22 | "no-unused-expressions": 2, 23 | "no-use-before-define": 2, 24 | "radix": [2, "always"], 25 | "indent": [2, 2], 26 | "quotes": [2, "double"], 27 | "semi": [2, "always"] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Description of the change** 2 | 3 | Clearly and concisely describe the purpose of the pull request. If this PR relates to an existing issue or change proposal, please link to it. Include any other background context that would help reviewers understand the motivation for this PR. 4 | 5 | --- 6 | 7 | **Checklist:** 8 | 9 | - [ ] Added the change to the changelog's "Unreleased" section with a reference to this PR (e.g. "- Made a change (#0000)") 10 | - [ ] Linked any existing issues or proposals that this pull request should close 11 | - [ ] Updated or added relevant documentation 12 | - [ ] Added a test for the contribution (if applicable) 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - uses: purescript-contrib/setup-purescript@main 16 | with: 17 | purescript: "unstable" 18 | purs-tidy: "latest" 19 | 20 | - uses: actions/setup-node@v3 21 | with: 22 | node-version: "lts/*" 23 | 24 | - name: Install dependencies 25 | run: | 26 | npm install -g bower 27 | npm install 28 | bower install --production 29 | 30 | - name: Build source 31 | run: npm run-script build 32 | 33 | - name: Run tests 34 | run: | 35 | bower install 36 | npm run-script test --if-present 37 | 38 | - name: Check formatting 39 | run: | 40 | purs-tidy check src test 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.* 2 | !/.gitignore 3 | !/.eslintrc.json 4 | !/.github/ 5 | /bower_components/ 6 | /node_modules/ 7 | /output/ 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 4 | 5 | ## [Unreleased] 6 | 7 | Breaking changes: 8 | 9 | New features: 10 | 11 | Bugfixes: 12 | 13 | Other improvements: 14 | 15 | ## [v12.0.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v12.0.0) - 2025-03-22 16 | 17 | Breaking changes: 18 | - Account for child creation to possibly fail with system error `BySysError` (#66 by @Hi-Angel) 19 | 20 | Other improvements: 21 | - Changed `SpawnSyncOptions` docs to remove mention of input types other than `Buffer`, as that is all the PureScript API supports (#64 by @Hi-Angel) 22 | 23 | ## [v11.1.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v11.1.0) - 2023-11-14 24 | 25 | New Features: 26 | - Added `waitSpawned'` which works on UnsafeChildProcess (#62 by @JordanMartinez) 27 | 28 | Other improvements: 29 | - Noted `pipe`/`inherit` stdio issues with `stdin` (#62 by @JordanMartinez) 30 | 31 | ## [v11.0.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v11.0.0) - 2023-07-26 32 | 33 | Breaking changes: 34 | - Update the signal arg from `String` to `KillSignal` (#51, #52 by @JordanMartinez) 35 | 36 | - `Exit`'s `BySignal` constructor's arg 37 | - `exitH`/`closeH`'s signal arg 38 | - `spawnSync`'s `SpawnResult`'s `signal` field 39 | - `kill'`'s signal arg 40 | - Removed `safeStdio` (#60 by @JordanMartinez) 41 | 42 | Turns out this isn't safe for `*Sync` functions. AFAIK, this isn't documented 43 | in Node docs. 44 | 45 | New features: 46 | - Added `fromKillSignal`, `fromKillSignal'` (#51, #59, #60 by @JordanMartinez) 47 | - Added `pidExists` (#53 by @JordanMartinez) 48 | - Export `toUnsafeChildProcess` (#55 by @JordanMartinez) 49 | - Added `stdio` (#55 by @JordanMartinez) 50 | - Added `Eq` and `Show` instance to `Shell` & `KillSignal` (#58, #59 by @JordanMartinez) 51 | 52 | Bugfixes: 53 | - Fixed `exitH`'s String value for listener (#60 by @JordanMartinez) 54 | - Added missing FFI for `execSync'` (#60 by @JordanMartinez) 55 | - Update `node-streams` to `v9.0.0` to fix FFI issues (#61 by @JordanMartinez) 56 | 57 | Other improvements: 58 | - Fix regression: add `ref`/`unref` APIs that were dropped in `v10.0.0` (#50 by @JordanMartinez) 59 | - Wrap `Unsafe` API via `ChildProcess` in safer way (#54 by @JordanMartinez) 60 | - Update tests to actually throw if invalid state occurs (#60 by @JordanMartinez) 61 | 62 | ## [v10.0.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v10.0.0) - 2023-07-20 63 | 64 | Breaking changes: 65 | - Migrate `onEvent`-style event handlers to `eventH`-style (#43 by @JordanMartinez) 66 | 67 | ```purs 68 | -- Before 69 | onExit cp case _ of 70 | Normally exitCode -> ... 71 | BySignal signal -> ... 72 | 73 | -- After 74 | cp # on_ exitH case _ of 75 | Normally exitCode -> ... 76 | BySignal signal -> ... 77 | ``` 78 | See https://pursuit.purescript.org/packages/purescript-node-event-emitter/3.0.0/docs/Node.EventEmitter for more details. 79 | - Update `pid` type signature to return `Maybe Pid` rather than `Pid` (#44 by @JordanMartinez) 80 | - Update `kill` returned value from `Effect Unit` to `Effect Boolean` (#44 by @JordanMartinez) 81 | - Migrate `Error` to `node-os`' `SystemError` (#45 by @JordanMartinez) 82 | - Breaking changes made to the `Exit` type (#46 by @JordanMartinez) 83 | 84 | - Moved from `Node.ChildProces` to `Node.ChildProces.Types` 85 | - Changed the `BySignal`'s constructor's arg type from `Signal` to `String` 86 | - Breaking changes made to the `Handle` type (#46 by @JordanMartinez) 87 | 88 | - Moved from `Node.ChildProces` to `Node.ChildProces.Types` 89 | - Converted `defaultOptions { override = Just 1}` pattern to `(_ { override = Just 1})` (#46 by @JordanMartinez) 90 | 91 | Before: 92 | ```purs 93 | spawn "foo" [ "bar" ] (defaultSpawnOptions { someOption = Just overrideValue }) 94 | spawn "foo" [ "bar" ] defaultSpawnOptions 95 | ``` 96 | 97 | After: 98 | ```purs 99 | spawn "foo" [ "bar" ] (_ { someOption = Just overrideValue }) 100 | spawn "foo" [ "bar" ] identity 101 | ``` 102 | - Restrict end-user's ability to configure `stdio` to only those appended to `safeStdio` (#46 by @JordanMartinez) 103 | 104 | See the module docs for `Node.ChildProcess`. 105 | - All `ChildProcess`-creating functions have been updated to support no args and all args variants (#46, #48 by @JordanMartinez) 106 | 107 | New features: 108 | - Added event handler for `spawn` event (#43 by @JordanMartinez) 109 | - Added missing APIs (#44 by @JordanMartinez) 110 | 111 | - exitCode 112 | - kill (no signal specified) 113 | - kill' (kill with a `String` signal) 114 | - killSignal (kill with an ADT `Signal` arg) 115 | - killed 116 | - signalCode 117 | - spawnArgs 118 | - spawnFile 119 | - Added unsafe, uncurried API of all ChildProcess-creating functions (#46 by @JordanMartinez) 120 | - Added safe variant of `spawnSync`/`spawnSync'` (#46 by @JordanMartinez) 121 | - Added `Aff`-based `waitSpawned` to safely get `Pid` (#47 by @JordanMartinez) 122 | 123 | Blocks until child process either successfully spawns or fails to spawn. 124 | 125 | Bugfixes: 126 | 127 | Other improvements: 128 | - Bumped CI's node version to `lts/*` (#41 by @JordanMartinez) 129 | - Updated CI `actions/checkout` and `actions/setup-nodee` to `v3` (#41 by @JordanMartinez) 130 | - Format codebase & enforce formatting in CI via purs-tidy (#42 by @JordanMartinez) 131 | - Migrate FFI to uncurried functions (#44, #46 by @JordanMartinez) 132 | - Updated recommended module alias in docs (#46 by @JordanMartinez) 133 | 134 | ## [v9.0.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v9.0.0) - 2022-04-29 135 | 136 | Breaking changes: 137 | - Update project and deps to PureScript v0.15.0 (#31 by @JordanMartinez, @thomashoneyman, @sigma-andex) 138 | 139 | ## [v8.0.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v8.0.0) - 2022-04-27 140 | 141 | Due to an incorrectly-made breaking change, please use v8.0.0 instead. 142 | 143 | ## [v7.1.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v7.1.0) - 2021-07-13 144 | 145 | New features: 146 | 147 | - Added `shell` and `encoding` options to `exec` functions (#29 by @thomashoneyman) 148 | 149 | ## [v7.0.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v7.0.0) - 2021-02-26 150 | 151 | Breaking changes: 152 | 153 | - Updated dependencies for PureScript 0.14 (#25) 154 | 155 | Other improvements: 156 | 157 | - Migrated CI to GitHub Actions and updated installation instructions to use Spago (#24) 158 | - Added a CHANGELOG.md file and pull request template (#26) 159 | 160 | ## [v6.0.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v6.0.0) - 2019-03-15 161 | 162 | - Updated `purescript-foreign-object` dependency 163 | 164 | ## [v5.0.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v5.0.0) - 2018-06-02 165 | 166 | - Updates for 0.12 167 | 168 | **Breaking** 169 | 170 | - mkOnClose now reacts to the `close` signal instead of `exit` @Profpatsch 171 | - `kill` returns Unit instead of Boolean @Profpatsch 172 | - `exec`/`execFile` returns a ChildProcess @Profpatsch 173 | 174 | **Additions** 175 | 176 | - Bindings to the synchronous versions of exec @jyh1 177 | 178 | ## [v4.0.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v4.0.0) - 2017-04-05 179 | 180 | - Updates for 0.11 compiler 181 | 182 | ## [v3.0.1](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v3.0.1) - 2016-11-19 183 | 184 | - Fixed shadowed name warning 185 | 186 | ## [v3.0.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v3.0.0) - 2016-10-22 187 | 188 | - Updated dependencies 189 | 190 | ## [v2.0.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v2.0.0) - 2016-07-31 191 | 192 | - Updated dependencies 193 | 194 | ## [v1.0.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v1.0.0) - 2016-06-19 195 | 196 | - Updates for 0.9.1 compiler and 1.0 core libraries. 197 | 198 | ## [v0.6.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v0.6.0) - 2016-03-31 199 | 200 | - Bump dependencies (`purescript-node-streams` -> v0.4.0). 201 | 202 | ## [v0.5.1](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v0.5.1) - 2016-01-14 203 | 204 | - Add `execFile` 205 | 206 | ## [v0.5.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v0.5.0) - 2016-01-06 207 | 208 | - Bump dependencies (`node-fs` -> `~0.10.0`) 209 | 210 | ## [v0.4.2](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v0.4.2) - 2016-01-01 211 | 212 | - Fix `Node_ChildProcess.fork is not a function` errors when calling `Node.ChildProcess.fork` 213 | - Fix unused import warnings 214 | 215 | ## [v0.4.1](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v0.4.1) - 2015-12-31 216 | 217 | - Fix `onError` never firing. Oops. 218 | 219 | ## [v0.4.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v0.4.0) - 2015-12-29 220 | 221 | - **Breaking change**: 222 | 223 | - `SpawnOptions` now uses the `Uid` and `Gid` types from `purescript-posix-types` for its `uid` and `gid` options, instead of `Int`. 224 | 225 | - **New features**: 226 | - Added `exec`. 227 | 228 | ## [v0.3.2](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v0.3.2) - 2015-12-27 229 | 230 | - Documentation updates 231 | 232 | ## [v0.3.1](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v0.3.1) - 2015-12-27 233 | 234 | - Move documentation to Pursuit 235 | - Documentation updates 236 | 237 | ## [v0.3.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v0.3.0) - 2015-12-27 238 | 239 | - Use purescript-posix-types 240 | - Fix a bug where `kill`, `connected`, and `send` would perform the effects too early 241 | - Modify type names to avoid repeating the module name: `ChildProcessExit` -> `Exit`, and `ChildProcessError` -> `Error`. 242 | 243 | ## [v0.2.0](https://github.com/purescript-node/purescript-node-child-process/releases/tag/v0.2.0) - 2015-12-27 244 | 245 | - Use a `StrMap String` for child process environments in `spawn`, in order to ensure environment variable values are strings 246 | - Allow inheritance of parent process environment by passing `Nothing` to the `env` parameter 247 | - Use an opaque data type for `ChildProcess` values 248 | - Use `Int` instead of `Number` where applicable (eg, `gid`, `uid`, `pid`) 249 | - Require `Eff` for reading mutable state of a `ChildProcess` 250 | - Simplify effects; now, we just have `cp :: CHILD_PROCESS` for spawning and communicating with child processes 251 | - Use a sum type to allow more flexibility with what to do with standard IO streams / file descriptors in the child process after spawning 252 | - Fix a bug where callbacks in `onExit` and `onClose` did not get called 253 | - Add a `ChildProcessExit` type with information about how a child process exited 254 | - Fix warnings 255 | - Update dependencies: `purescript-node-streams` -> `~0.3.0` 256 | 257 | See https://github.com/joneshf/purescript-node-child-process/issues/2 for the rationale behind many of these changes. 258 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 Hardy Jones 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purescript-node-child-process 2 | 3 | [![Latest release](http://img.shields.io/github/release/purescript-node/purescript-node-child-process.svg)](https://github.com/purescript/purescript-node-child-process/releases) 4 | [![Build status](https://github.com/purescript-node/purescript-node-child-process/workflows/CI/badge.svg?branch=master)](https://github.com/purescript-node/purescript-node-child-process/actions?query=workflow%3ACI+branch%3Amaster) 5 | [![Pursuit](https://pursuit.purescript.org/packages/purescript-node-child-process/badge)](https://pursuit.purescript.org/packages/purescript-node-child-process) 6 | 7 | Bindings to Node's `child_process` API. 8 | 9 | ## Installation 10 | 11 | ``` 12 | spago install node-child-process 13 | ``` 14 | 15 | ## Documentation 16 | 17 | Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-node-child-process). 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-node-child-process", 3 | "description": "PureScript bindings to child_process.", 4 | "license": "MIT", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/purescript-node/purescript-node-child-process.git" 8 | }, 9 | "ignore": [ 10 | "**/.*", 11 | "bower_components", 12 | "node_modules", 13 | "output", 14 | "test", 15 | "bower.json", 16 | "package.json" 17 | ], 18 | "dependencies": { 19 | "purescript-aff": "^7.0.0", 20 | "purescript-exceptions": "^6.0.0", 21 | "purescript-node-event-emitter": "https://github.com/purescript-node/purescript-node-event-emitter.git#^3.0.0", 22 | "purescript-foreign": "^7.0.0", 23 | "purescript-foreign-object": "^4.0.0", 24 | "purescript-functions": "^6.0.0", 25 | "purescript-node-fs": "^9.1.0", 26 | "purescript-node-streams": "^9.0.0", 27 | "purescript-node-os": "v5.1.0", 28 | "purescript-nullable": "^6.0.0", 29 | "purescript-posix-types": "^6.0.0", 30 | "purescript-unsafe-coerce": "^6.0.0" 31 | }, 32 | "devDependencies": { 33 | "purescript-console": "^6.0.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "clean": "rimraf output && rimraf .pulp-cache", 5 | "build": "eslint src && pulp build -- --censor-lib --strict", 6 | "test": "pulp test -- --censor-lib" 7 | }, 8 | "devDependencies": { 9 | "eslint": "^7.15.0", 10 | "pulp": "16.0.0-0", 11 | "purescript-psa": "^0.8.2", 12 | "rimraf": "^3.0.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Node/ChildProcess.js: -------------------------------------------------------------------------------- 1 | const _undefined = undefined; 2 | export { _undefined as undefined }; 3 | -------------------------------------------------------------------------------- /src/Node/ChildProcess.purs: -------------------------------------------------------------------------------- 1 | -- | This module contains various types and functions to allow you to spawn and 2 | -- | interact with child processes. 3 | -- | 4 | -- | It is intended to be imported qualified, as follows: 5 | -- | 6 | -- | ```purescript 7 | -- | import Node.ChildProcess as ChildProcess 8 | -- | -- or... 9 | -- | import Node.ChildProcess as CP 10 | -- | ``` 11 | -- | 12 | -- | The [Node.js documentation](https://nodejs.org/api/child_process.html) 13 | -- | forms the basis for this module and has in-depth documentation about 14 | -- | runtime behaviour. 15 | -- | 16 | -- | ## Meaning of `appendStdio` 17 | -- | 18 | -- | By default, `ChildProcess` uses `safeStdio` for its `stdio` option. However, 19 | -- | Node allows one to pass in additional values besides the typical 3 (i.e. `stdin`, `stdout`, `stderr`) 20 | -- | and the IPC channel that might be used (i.e. `ipc`). Thus, `appendStdio` is an option 21 | -- | defined in this library that doesn't exist in the Node docs. 22 | -- | It exists to allow the end-user to append additional values to the `safeStdio` value 23 | -- | used here. For example, 24 | -- | 25 | -- | ``` 26 | -- | spawn' file args (_ { appendStdio = Just [ fileDescriptor8, pipe, pipe ]}) 27 | -- | ``` 28 | -- | 29 | -- | would end up calling `spawn` with the following `stdio`: 30 | -- | ``` 31 | -- | -- i.e. `safeStdio <> [ fileDescriptor8, pipe, pipe ]` 32 | -- | [pipe, pipe, pipe, ipc, fileDescriptor8, pipe, pipe] 33 | -- | ``` 34 | module Node.ChildProcess 35 | ( ChildProcess 36 | , toEventEmitter 37 | , toUnsafeChildProcess 38 | , closeH 39 | , disconnectH 40 | , errorH 41 | , exitH 42 | , messageH 43 | , spawnH 44 | , stdin 45 | , stdout 46 | , stderr 47 | , pid 48 | , pidExists 49 | , connected 50 | , disconnect 51 | , exitCode 52 | , kill 53 | , kill' 54 | , killSignal 55 | , killed 56 | , ref 57 | , unref 58 | , signalCode 59 | , spawnFile 60 | , spawnArgs 61 | , stdio 62 | , spawnSync 63 | , SpawnSyncOptions 64 | , spawnSync' 65 | , spawn 66 | , SpawnOptions 67 | , spawn' 68 | , execSync 69 | , ExecSyncOptions 70 | , execSync' 71 | , exec 72 | , ExecResult 73 | , ExecOptions 74 | , exec' 75 | , execFileSync 76 | , ExecFileSyncOptions 77 | , execFileSync' 78 | , execFile 79 | , ExecFileOptions 80 | , execFile' 81 | , fork 82 | , fork' 83 | , send 84 | , send' 85 | ) where 86 | 87 | import Prelude 88 | 89 | import Data.Maybe (Maybe(..), fromMaybe, isJust) 90 | import Data.Nullable (Nullable, toMaybe, toNullable) 91 | import Data.Posix (Pid, Gid, Uid) 92 | import Data.Posix.Signal (Signal) 93 | import Data.Time.Duration (Milliseconds) 94 | import Effect (Effect) 95 | import Effect.Exception (Error) 96 | import Effect.Uncurried (EffectFn2) 97 | import Foreign (Foreign) 98 | import Foreign.Object (Object) 99 | import Node.Buffer (Buffer) 100 | import Node.ChildProcess.Types (Exit(..), Handle, KillSignal, Shell, StdIO, UnsafeChildProcess, ipc, pipe) 101 | import Node.Errors.SystemError (SystemError) 102 | import Node.EventEmitter (EventEmitter, EventHandle) 103 | import Node.EventEmitter.UtilTypes (EventHandle0, EventHandle1) 104 | import Node.Stream (Readable, Writable) 105 | import Node.UnsafeChildProcess.Safe as SafeCP 106 | import Node.UnsafeChildProcess.Unsafe (unsafeSOBToBuffer) 107 | import Node.UnsafeChildProcess.Unsafe as UnsafeCP 108 | import Partial.Unsafe (unsafeCrashWith) 109 | import Safe.Coerce (coerce) 110 | import Unsafe.Coerce (unsafeCoerce) 111 | 112 | -- | Opaque type returned by `spawn`, `fork` and `exec`. 113 | -- | Needed as input for most methods in this module. 114 | -- | 115 | -- | `ChildProcess` extends `EventEmitter` 116 | newtype ChildProcess = ChildProcess UnsafeChildProcess 117 | 118 | toEventEmitter :: ChildProcess -> EventEmitter 119 | toEventEmitter = toUnsafeChildProcess >>> SafeCP.toEventEmitter 120 | 121 | toUnsafeChildProcess :: ChildProcess -> UnsafeChildProcess 122 | toUnsafeChildProcess (ChildProcess p) = p 123 | 124 | closeH :: EventHandle ChildProcess (Exit -> Effect Unit) (EffectFn2 (Nullable Int) (Nullable String) Unit) 125 | closeH = unsafeCoerce SafeCP.closeH 126 | 127 | disconnectH :: EventHandle0 ChildProcess 128 | disconnectH = unsafeCoerce SafeCP.disconnectH 129 | 130 | errorH :: EventHandle1 ChildProcess SystemError 131 | errorH = unsafeCoerce SafeCP.errorH 132 | 133 | exitH :: EventHandle ChildProcess (Exit -> Effect Unit) (EffectFn2 (Nullable Int) (Nullable String) Unit) 134 | exitH = unsafeCoerce SafeCP.exitH 135 | 136 | messageH :: EventHandle ChildProcess (Foreign -> Maybe Handle -> Effect Unit) (EffectFn2 Foreign (Nullable Handle) Unit) 137 | messageH = unsafeCoerce SafeCP.messageH 138 | 139 | spawnH :: EventHandle0 ChildProcess 140 | spawnH = unsafeCoerce SafeCP.spawnH 141 | 142 | unsafeFromNull :: forall a. Nullable a -> a 143 | unsafeFromNull = unsafeCoerce 144 | 145 | -- | The standard input stream of a child process. 146 | stdin :: ChildProcess -> Writable () 147 | stdin = toUnsafeChildProcess >>> UnsafeCP.unsafeStdin >>> unsafeFromNull 148 | 149 | -- | The standard output stream of a child process. 150 | stdout :: ChildProcess -> Readable () 151 | stdout = toUnsafeChildProcess >>> UnsafeCP.unsafeStdout >>> unsafeFromNull 152 | 153 | -- | The standard error stream of a child process. 154 | stderr :: ChildProcess -> Readable () 155 | stderr = toUnsafeChildProcess >>> UnsafeCP.unsafeStderr >>> unsafeFromNull 156 | 157 | -- | The process ID of a child process. This will be `Nothing` until 158 | -- | the process has spawned. Note that if the process has already 159 | -- | exited, another process may have taken the same ID, so be careful! 160 | pid :: ChildProcess -> Effect (Maybe Pid) 161 | pid cp = SafeCP.pid $ toUnsafeChildProcess cp 162 | 163 | pidExists :: ChildProcess -> Effect Boolean 164 | pidExists cp = SafeCP.pidExists $ toUnsafeChildProcess cp 165 | 166 | -- | Indicates whether it is still possible to send and receive 167 | -- | messages from the child process. 168 | connected :: ChildProcess -> Effect Boolean 169 | connected = unsafeCoerce SafeCP.connected 170 | 171 | exitCode :: ChildProcess -> Effect (Maybe Int) 172 | exitCode cp = SafeCP.exitCode $ toUnsafeChildProcess cp 173 | 174 | -- | Closes the IPC channel between parent and child. 175 | disconnect :: ChildProcess -> Effect Unit 176 | disconnect cp = SafeCP.disconnect $ toUnsafeChildProcess cp 177 | 178 | kill :: ChildProcess -> Effect Boolean 179 | kill cp = SafeCP.kill $ toUnsafeChildProcess cp 180 | 181 | kill' :: KillSignal -> ChildProcess -> Effect Boolean 182 | kill' sig cp = SafeCP.kill' sig $ toUnsafeChildProcess cp 183 | 184 | -- | Send a signal to a child process. In the same way as the 185 | -- | [unix kill(2) system call](https://linux.die.net/man/2/kill), 186 | -- | sending a signal to a child process won't necessarily kill it. 187 | -- | 188 | -- | The resulting effects of this function depend on the process 189 | -- | and the signal. They can vary from system to system. 190 | -- | The child process might emit an `"error"` event if the signal 191 | -- | could not be delivered. 192 | killSignal :: Signal -> ChildProcess -> Effect Boolean 193 | killSignal sig cp = SafeCP.killSignal sig $ toUnsafeChildProcess cp 194 | 195 | killed :: ChildProcess -> Effect Boolean 196 | killed cp = SafeCP.killed $ toUnsafeChildProcess cp 197 | 198 | ref :: ChildProcess -> Effect Unit 199 | ref cp = SafeCP.ref $ toUnsafeChildProcess cp 200 | 201 | unref :: ChildProcess -> Effect Unit 202 | unref cp = SafeCP.unref $ toUnsafeChildProcess cp 203 | 204 | signalCode :: ChildProcess -> Effect (Maybe String) 205 | signalCode cp = SafeCP.signalCode $ toUnsafeChildProcess cp 206 | 207 | spawnArgs :: ChildProcess -> Array String 208 | spawnArgs cp = SafeCP.spawnArgs $ toUnsafeChildProcess cp 209 | 210 | spawnFile :: ChildProcess -> String 211 | spawnFile cp = SafeCP.spawnFile $ toUnsafeChildProcess cp 212 | 213 | stdio :: ChildProcess -> Array StdIO 214 | stdio cp = SafeCP.stdio $ toUnsafeChildProcess cp 215 | 216 | -- | Note: `exitStatus` combines the `status` and `signal` fields 217 | -- | from the value normally returned by `spawnSync` into one value 218 | -- | since only one of them can be non-null at the end. 219 | type SpawnSyncResult = 220 | { pid :: Pid 221 | , output :: Array Foreign 222 | , stdout :: Buffer 223 | , stderr :: Buffer 224 | , exitStatus :: Exit 225 | , error :: Maybe SystemError 226 | } 227 | 228 | spawnSync 229 | :: String 230 | -> Array String 231 | -> Effect SpawnSyncResult 232 | spawnSync command args = (UnsafeCP.spawnSync command args) <#> \r -> 233 | { pid: r.pid 234 | , output: r.output 235 | , stdout: unsafeSOBToBuffer r.stdout 236 | , stderr: unsafeSOBToBuffer r.stderr 237 | , exitStatus: case toMaybe r.status, toMaybe r.signal of 238 | Just c, _ -> Normally c 239 | _, Just s -> BySignal s 240 | _, _ -> 241 | if isJust $ toMaybe r.error then 242 | BySysError 243 | else 244 | unsafeCrashWith $ "Impossible: `spawnSync` child process neither exited nor was killed." 245 | , error: toMaybe r.error 246 | } 247 | 248 | -- | - `cwd` | Current working directory of the child process. 249 | -- | - `input` The value which will be passed as stdin to the spawned process. Supplying this value will override stdio[0]. 250 | -- | - `argv0` Explicitly set the value of argv[0] sent to the child process. This will be set to command if not specified. 251 | -- | - `env` Environment key-value pairs. Default: process.env. 252 | -- | - `uid` Sets the user identity of the process (see setuid(2)). 253 | -- | - `gid` Sets the group identity of the process (see setgid(2)). 254 | -- | - `timeout` In milliseconds the maximum amount of time the process is allowed to run. Default: undefined. 255 | -- | - `killSignal` | The signal value to be used when the spawned process will be killed. Default: 'SIGTERM'. 256 | -- | - `maxBuffer` Largest amount of data in bytes allowed on stdout or stderr. If exceeded, the child process is terminated and any output is truncated. See caveat at maxBuffer and Unicode. Default: 1024 * 1024. 257 | -- | - `shell` | If true, runs command inside of a shell. Uses '/bin/sh' on Unix, and process.env.ComSpec on Windows. A different shell can be specified as a string. See Shell requirements and Default Windows shell. Default: false (no shell). 258 | -- | - `windowsVerbatimArguments` No quoting or escaping of arguments is done on Windows. Ignored on Unix. This is set to true automatically when shell is specified and is CMD. Default: false. 259 | -- | - `windowsHide` Hide the subprocess console window that would normally be created on Windows systems. Default: false. 260 | type SpawnSyncOptions = 261 | { cwd :: Maybe String 262 | , input :: Maybe Buffer 263 | , appendStdio :: Maybe (Array StdIO) 264 | , argv0 :: Maybe String 265 | , env :: Maybe (Object String) 266 | , uid :: Maybe Uid 267 | , gid :: Maybe Gid 268 | , timeout :: Maybe Milliseconds 269 | , killSignal :: Maybe KillSignal 270 | , maxBuffer :: Maybe Number 271 | , shell :: Maybe Shell 272 | , windowsVerbatimArguments :: Maybe Boolean 273 | , windowsHide :: Maybe Boolean 274 | } 275 | 276 | spawnSync' 277 | :: String 278 | -> Array String 279 | -> (SpawnSyncOptions -> SpawnSyncOptions) 280 | -> Effect SpawnSyncResult 281 | spawnSync' command args buildOpts = (UnsafeCP.spawnSync' command args opts) <#> \r -> 282 | { pid: r.pid 283 | , output: r.output 284 | , stdout: unsafeSOBToBuffer r.stdout 285 | , stderr: unsafeSOBToBuffer r.stderr 286 | , exitStatus: case toMaybe r.status, toMaybe r.signal of 287 | Just c, _ -> Normally c 288 | _, Just s -> BySignal s 289 | _, _ -> 290 | if isJust $ toMaybe r.error then 291 | BySysError 292 | else 293 | unsafeCrashWith $ "Impossible: `spawnSync` child process neither exited nor was killed." 294 | , error: toMaybe r.error 295 | } 296 | where 297 | opts = 298 | { stdio: [ pipe, pipe, pipe ] <> fromMaybe [] o.appendStdio 299 | , encoding: "buffer" 300 | , cwd: fromMaybe undefined o.cwd 301 | , input: fromMaybe undefined o.input 302 | , argv0: fromMaybe undefined o.argv0 303 | , env: fromMaybe undefined o.env 304 | , uid: fromMaybe undefined o.uid 305 | , gid: fromMaybe undefined o.gid 306 | , timeout: fromMaybe undefined o.timeout 307 | , killSignal: fromMaybe undefined o.killSignal 308 | , maxBuffer: fromMaybe undefined o.maxBuffer 309 | , shell: fromMaybe undefined o.shell 310 | , windowsVerbatimArguments: fromMaybe undefined o.windowsVerbatimArguments 311 | , windowsHide: fromMaybe undefined o.windowsHide 312 | } 313 | 314 | o = buildOpts 315 | { cwd: Nothing 316 | , input: Nothing 317 | , appendStdio: Nothing 318 | , argv0: Nothing 319 | , env: Nothing 320 | , uid: Nothing 321 | , gid: Nothing 322 | , timeout: Nothing 323 | , killSignal: Nothing 324 | , maxBuffer: Nothing 325 | , shell: Nothing 326 | , windowsVerbatimArguments: Nothing 327 | , windowsHide: Nothing 328 | } 329 | 330 | -- | Spawn a child process. Note that, in the event that a child process could 331 | -- | not be spawned (for example, if the executable was not found) this will 332 | -- | not throw an error. Instead, the `ChildProcess` will be created anyway, 333 | -- | but it will immediately emit an 'error' event. 334 | spawn 335 | :: String 336 | -> Array String 337 | -> Effect ChildProcess 338 | spawn cmd args = coerce $ UnsafeCP.spawn cmd args 339 | 340 | -- | - `cwd` | Current working directory of the child process. 341 | -- | - `env` Environment key-value pairs. Default: process.env. 342 | -- | - `argv0` Explicitly set the value of argv[0] sent to the child process. This will be set to command if not specified. 343 | -- | - `detached` Prepare child to run independently of its parent process. Specific behavior depends on the platform, see options.detached). 344 | -- | - `uid` Sets the user identity of the process (see setuid(2)). 345 | -- | - `gid` Sets the group identity of the process (see setgid(2)). 346 | -- | - `serialization` Specify the kind of serialization used for sending messages between processes. Possible values are 'json' and 'advanced'. See Advanced serialization for more details. Default: 'json'. 347 | -- | - `shell` | If true, runs command inside of a shell. Uses '/bin/sh' on Unix, and process.env.ComSpec on Windows. A different shell can be specified as a string. See Shell requirements and Default Windows shell. Default: false (no shell). 348 | -- | - `windowsVerbatimArguments` No quoting or escaping of arguments is done on Windows. Ignored on Unix. This is set to true automatically when shell is specified and is CMD. Default: false. 349 | -- | - `windowsHide` Hide the subprocess console window that would normally be created on Windows systems. Default: false. 350 | -- | - `signal` allows aborting the child process using an AbortSignal. 351 | -- | - `timeout` In milliseconds the maximum amount of time the process is allowed to run. Default: undefined. 352 | -- | - `killSignal` | The signal value to be used when the spawned process will be killed by timeout or abort signal. Default: 'SIGTERM'. 353 | type SpawnOptions = 354 | { cwd :: Maybe String 355 | , env :: Maybe (Object String) 356 | , argv0 :: Maybe String 357 | , appendStdio :: Maybe (Array StdIO) 358 | , detached :: Maybe Boolean 359 | , uid :: Maybe Uid 360 | , gid :: Maybe Gid 361 | , serialization :: Maybe String 362 | , shell :: Maybe Shell 363 | , windowsVerbatimArguments :: Maybe Boolean 364 | , windowsHide :: Maybe Boolean 365 | , timeout :: Maybe Milliseconds 366 | , killSignal :: Maybe KillSignal 367 | } 368 | 369 | spawn' 370 | :: String 371 | -> Array String 372 | -> (SpawnOptions -> SpawnOptions) 373 | -> Effect ChildProcess 374 | spawn' cmd args buildOpts = coerce $ UnsafeCP.spawn' cmd args opts 375 | where 376 | opts = 377 | { stdio: [ pipe, pipe, pipe, ipc ] <> fromMaybe [] o.appendStdio 378 | , cwd: fromMaybe undefined o.cwd 379 | , env: fromMaybe undefined o.env 380 | , argv0: fromMaybe undefined o.argv0 381 | , detached: fromMaybe undefined o.detached 382 | , uid: fromMaybe undefined o.uid 383 | , gid: fromMaybe undefined o.gid 384 | , serialization: fromMaybe undefined o.serialization 385 | , shell: fromMaybe undefined o.shell 386 | , windowsVerbatimArguments: fromMaybe undefined o.windowsVerbatimArguments 387 | , windowsHide: fromMaybe undefined o.windowsHide 388 | , timeout: fromMaybe undefined o.timeout 389 | , killSignal: fromMaybe undefined o.killSignal 390 | } 391 | o = buildOpts 392 | { cwd: Nothing 393 | , env: Nothing 394 | , argv0: Nothing 395 | , appendStdio: Nothing 396 | , detached: Nothing 397 | , uid: Nothing 398 | , gid: Nothing 399 | , serialization: Nothing 400 | , shell: Nothing 401 | , windowsVerbatimArguments: Nothing 402 | , windowsHide: Nothing 403 | , timeout: Nothing 404 | , killSignal: Nothing 405 | } 406 | 407 | -- | Generally identical to `exec`, with the exception that 408 | -- | the method will not return until the child process has fully closed. 409 | -- | Returns: The `stdout` from the command. 410 | execSync 411 | :: String 412 | -> Effect Buffer 413 | execSync cmd = map unsafeSOBToBuffer $ UnsafeCP.execSync cmd 414 | 415 | -- | - `cwd` | Current working directory of the child process. 416 | -- | - `input` | | | The value which will be passed as stdin to the spawned process. Supplying this value will override stdio[0]. 417 | -- | - `stdio` | Child's stdio configuration. stderr by default will be output to the parent process' stderr unless stdio is specified. Default: 'pipe'. 418 | -- | - `env` Environment key-value pairs. Default: process.env. 419 | -- | - `shell` Shell to execute the command with. See Shell requirements and Default Windows shell. Default: '/bin/sh' on Unix, process.env.ComSpec on Windows. 420 | -- | - `uid` Sets the user identity of the process. (See setuid(2)). 421 | -- | - `gid` Sets the group identity of the process. (See setgid(2)). 422 | -- | - `timeout` In milliseconds the maximum amount of time the process is allowed to run. Default: undefined. 423 | -- | - `killSignal` | The signal value to be used when the spawned process will be killed. Default: 'SIGTERM'. 424 | -- | - `maxBuffer` Largest amount of data in bytes allowed on stdout or stderr. If exceeded, the child process is terminated and any output is truncated. See caveat at maxBuffer and Unicode. Default: 1024 * 1024. 425 | -- | - `encoding` The encoding used for all stdio inputs and outputs. Default: 'buffer'. 426 | -- | - `windowsHide` Hide the subprocess console window that would normally be created on Windows systems. Default: false. 427 | type ExecSyncOptions = 428 | { cwd :: Maybe String 429 | , input :: Maybe Buffer 430 | , appendStdio :: Maybe (Array StdIO) 431 | , env :: Maybe (Object String) 432 | , shell :: Maybe String 433 | , uid :: Maybe Uid 434 | , gid :: Maybe Gid 435 | , timeout :: Maybe Milliseconds 436 | , killSignal :: Maybe KillSignal 437 | , maxBuffer :: Maybe Number 438 | , windowsHide :: Maybe Boolean 439 | } 440 | 441 | execSync' 442 | :: String 443 | -> (ExecSyncOptions -> ExecSyncOptions) 444 | -> Effect Buffer 445 | execSync' cmd buildOpts = do 446 | map unsafeSOBToBuffer $ UnsafeCP.execSync' cmd opts 447 | where 448 | o = buildOpts 449 | { cwd: Nothing 450 | , input: Nothing 451 | , appendStdio: Nothing 452 | , env: Nothing 453 | , shell: Nothing 454 | , uid: Nothing 455 | , gid: Nothing 456 | , timeout: Nothing 457 | , killSignal: Nothing 458 | , maxBuffer: Nothing 459 | , windowsHide: Nothing 460 | } 461 | opts = 462 | { stdio: [ pipe, pipe, pipe ] <> fromMaybe [] o.appendStdio 463 | , encoding: "buffer" 464 | , cwd: fromMaybe undefined o.cwd 465 | , input: fromMaybe undefined o.input 466 | , env: fromMaybe undefined o.env 467 | , shell: fromMaybe undefined o.shell 468 | , uid: fromMaybe undefined o.uid 469 | , gid: fromMaybe undefined o.gid 470 | , timeout: fromMaybe undefined o.timeout 471 | , killSignal: fromMaybe undefined o.killSignal 472 | , maxBuffer: fromMaybe undefined o.maxBuffer 473 | , windowsHide: fromMaybe undefined o.windowsHide 474 | } 475 | 476 | -- | Similar to `spawn`, except that this variant will: 477 | -- | * run the given command with the shell, 478 | -- | * buffer output, and wait until the process has exited. 479 | -- | 480 | -- | Note that the child process will be killed if the amount of output exceeds 481 | -- | a certain threshold (the default is defined by Node.js). 482 | exec :: String -> Effect ChildProcess 483 | exec command = coerce $ UnsafeCP.execOpts command { encoding: "buffer" } 484 | 485 | -- | The combined output of a process called with `exec`. 486 | type ExecResult = 487 | { stdout :: Buffer 488 | , stderr :: Buffer 489 | , error :: Maybe SystemError 490 | } 491 | 492 | -- | - `cwd` | Current working directory of the child process. 493 | -- | - `env` Environment key-value pairs. Default: process.env. 494 | -- | - `timeout` Default: 0 495 | -- | - `maxBuffer` Largest amount of data in bytes allowed on stdout or stderr. If exceeded, the child process is terminated and any output is truncated. See caveat at maxBuffer and Unicode. Default: 1024 * 1024. 496 | -- | - `killSignal` | Default: 'SIGTERM' 497 | -- | - `uid` Sets the user identity of the process (see setuid(2)). 498 | -- | - `gid` Sets the group identity of the process (see setgid(2)). 499 | -- | - `windowsHide` Hide the subprocess console window that would normally be created on Windows systems. Default: false. 500 | -- | - `shell` | If true, runs command inside of a shell. Uses '/bin/sh' on Unix, and process.env.ComSpec on Windows. A different shell can be specified as a string. See Shell requirements and Default Windows shell. Default: false (no shell). 501 | type ExecOptions = 502 | { cwd :: Maybe String 503 | , env :: Maybe (Object String) 504 | , timeout :: Maybe Milliseconds 505 | , maxBuffer :: Maybe Number 506 | , killSignal :: Maybe KillSignal 507 | , uid :: Maybe Uid 508 | , gid :: Maybe Gid 509 | , windowsHide :: Maybe Boolean 510 | , shell :: Maybe Shell 511 | } 512 | 513 | -- | Similar to `spawn`, except that this variant will: 514 | -- | * run the given command with the shell, 515 | -- | * buffer output, and wait until the process has exited before calling the 516 | -- | callback. 517 | -- | 518 | -- | Note that the child process will be killed if the amount of output exceeds 519 | -- | a certain threshold (the default is defined by Node.js). 520 | exec' 521 | :: String 522 | -> (ExecOptions -> ExecOptions) 523 | -> (ExecResult -> Effect Unit) 524 | -> Effect ChildProcess 525 | exec' command buildOpts cb = coerce $ UnsafeCP.execOptsCb command opts \err sout serr -> 526 | cb { stdout: unsafeSOBToBuffer sout, stderr: unsafeSOBToBuffer serr, error: err } 527 | where 528 | opts = 529 | { encoding: "buffer" 530 | , cwd: fromMaybe undefined o.cwd 531 | , env: fromMaybe undefined o.env 532 | , timeout: fromMaybe undefined o.timeout 533 | , maxBuffer: fromMaybe undefined o.maxBuffer 534 | , killSignal: fromMaybe undefined o.killSignal 535 | , uid: fromMaybe undefined o.uid 536 | , gid: fromMaybe undefined o.gid 537 | , windowsHide: fromMaybe undefined o.windowsHide 538 | , shell: fromMaybe undefined o.shell 539 | } 540 | o = buildOpts 541 | { cwd: Nothing 542 | , env: Nothing 543 | , timeout: Nothing 544 | , maxBuffer: Nothing 545 | , killSignal: Nothing 546 | , uid: Nothing 547 | , gid: Nothing 548 | , windowsHide: Nothing 549 | , shell: Nothing 550 | } 551 | 552 | -- | Generally identical to `execFile`, with the exception that 553 | -- | the method will not return until the child process has fully closed. 554 | -- | Returns: The `stdout` from the command. 555 | execFileSync 556 | :: String 557 | -> Array String 558 | -> Effect Buffer 559 | execFileSync file args = 560 | map unsafeSOBToBuffer $ UnsafeCP.execFileSync' file args { encoding: "buffer" } 561 | 562 | -- | - `cwd` | Current working directory of the child process. 563 | -- | - `input` | | | The value which will be passed as stdin to the spawned process. Supplying this value will override stdio[0]. 564 | -- | - `env` Environment key-value pairs. Default: process.env. 565 | -- | - `uid` Sets the user identity of the process (see setuid(2)). 566 | -- | - `gid` Sets the group identity of the process (see setgid(2)). 567 | -- | - `timeout` In milliseconds the maximum amount of time the process is allowed to run. Default: undefined. 568 | -- | - `killSignal` | The signal value to be used when the spawned process will be killed. Default: 'SIGTERM'. 569 | -- | - `maxBuffer` Largest amount of data in bytes allowed on stdout or stderr. If exceeded, the child process is terminated. See caveat at maxBuffer and Unicode. Default: 1024 * 1024. 570 | -- | - `windowsHide` Hide the subprocess console window that would normally be created on Windows systems. Default: false. 571 | -- | - `shell` | If true, runs command inside of a shell. Uses '/bin/sh' on Unix, and process.env.ComSpec on Windows. A different shell can be specified as a string. See Shell requirements and Default Windows shell. Default: false (no shell). 572 | type ExecFileSyncOptions = 573 | { cwd :: Maybe String 574 | , input :: Maybe Buffer 575 | , appendStdio :: Maybe (Array StdIO) 576 | , env :: Maybe (Object String) 577 | , uid :: Maybe Uid 578 | , gid :: Maybe Gid 579 | , timeout :: Maybe Milliseconds 580 | , killSignal :: Maybe KillSignal 581 | , maxBuffer :: Maybe Number 582 | , windowsHide :: Maybe Boolean 583 | , shell :: Maybe Shell 584 | } 585 | 586 | execFileSync' 587 | :: String 588 | -> Array String 589 | -> (ExecFileSyncOptions -> ExecFileSyncOptions) 590 | -> Effect Buffer 591 | execFileSync' file args buildOpts = 592 | map unsafeSOBToBuffer $ UnsafeCP.execFileSync' file args opts 593 | where 594 | opts = 595 | { stdio: [ pipe, pipe, pipe ] <> fromMaybe [] o.appendStdio 596 | , encoding: "buffer" 597 | , cwd: fromMaybe undefined o.cwd 598 | , input: fromMaybe undefined o.input 599 | , env: fromMaybe undefined o.env 600 | , uid: fromMaybe undefined o.uid 601 | , gid: fromMaybe undefined o.gid 602 | , timeout: fromMaybe undefined o.timeout 603 | , killSignal: fromMaybe undefined o.killSignal 604 | , maxBuffer: fromMaybe undefined o.maxBuffer 605 | , windowsHide: fromMaybe undefined o.windowsHide 606 | , shell: fromMaybe undefined o.shell 607 | } 608 | o = buildOpts 609 | { cwd: Nothing 610 | , input: Nothing 611 | , appendStdio: Nothing 612 | , env: Nothing 613 | , uid: Nothing 614 | , gid: Nothing 615 | , timeout: Nothing 616 | , killSignal: Nothing 617 | , maxBuffer: Nothing 618 | , windowsHide: Nothing 619 | , shell: Nothing 620 | } 621 | 622 | -- | Like `exec`, except instead of using a shell, it passes the arguments 623 | -- | directly to the specified command. 624 | execFile 625 | :: String 626 | -> Array String 627 | -> Effect ChildProcess 628 | execFile cmd args = coerce $ UnsafeCP.execFileOpts cmd args { encoding: "buffer" } 629 | 630 | -- | - `cwd` | Current working directory of the child process. 631 | -- | - `env` Environment key-value pairs. Default: process.env. 632 | -- | - `timeout` Default: 0 633 | -- | - `maxBuffer` Largest amount of data in bytes allowed on stdout or stderr. If exceeded, the child process is terminated and any output is truncated. See caveat at maxBuffer and Unicode. Default: 1024 * 1024. 634 | -- | - `killSignal` | Default: 'SIGTERM' 635 | -- | - `uid` Sets the user identity of the process (see setuid(2)). 636 | -- | - `gid` Sets the group identity of the process (see setgid(2)). 637 | -- | - `windowsHide` Hide the subprocess console window that would normally be created on Windows systems. Default: false. 638 | -- | - `windowsVerbatimArguments` No quoting or escaping of arguments is done on Windows. Ignored on Unix. Default: false. 639 | -- | - `shell` | If true, runs command inside of a shell. Uses '/bin/sh' on Unix, and process.env.ComSpec on Windows. A different shell can be specified as a string. See Shell requirements and Default Windows shell. Default: false (no shell). 640 | type ExecFileOptions = 641 | { cwd :: Maybe String 642 | , env :: Maybe (Object String) 643 | , timeout :: Maybe Milliseconds 644 | , maxBuffer :: Maybe Number 645 | , killSignal :: Maybe KillSignal 646 | , uid :: Maybe Uid 647 | , gid :: Maybe Gid 648 | , windowsHide :: Maybe Boolean 649 | , windowsVerbatimArguments :: Maybe Boolean 650 | , shell :: Maybe Shell 651 | } 652 | 653 | execFile' 654 | :: String 655 | -> Array String 656 | -> (ExecFileOptions -> ExecFileOptions) 657 | -> (ExecResult -> Effect Unit) 658 | -> Effect ChildProcess 659 | execFile' cmd args buildOpts cb = coerce $ UnsafeCP.execFileOptsCb cmd args opts \err sout serr -> 660 | cb { stdout: unsafeSOBToBuffer sout, stderr: unsafeSOBToBuffer serr, error: err } 661 | where 662 | opts = 663 | { cwd: fromMaybe undefined o.cwd 664 | , env: fromMaybe undefined o.env 665 | , encoding: "buffer" 666 | , timeout: fromMaybe undefined o.timeout 667 | , maxBuffer: fromMaybe undefined o.maxBuffer 668 | , killSignal: fromMaybe undefined o.killSignal 669 | , uid: fromMaybe undefined o.uid 670 | , gid: fromMaybe undefined o.gid 671 | , windowsHide: fromMaybe undefined o.windowsHide 672 | , windowsVerbatimArguments: fromMaybe undefined o.windowsVerbatimArguments 673 | , shell: fromMaybe undefined o.shell 674 | } 675 | o = buildOpts 676 | { cwd: Nothing 677 | , env: Nothing 678 | , timeout: Nothing 679 | , maxBuffer: Nothing 680 | , killSignal: Nothing 681 | , uid: Nothing 682 | , gid: Nothing 683 | , windowsHide: Nothing 684 | , windowsVerbatimArguments: Nothing 685 | , shell: Nothing 686 | } 687 | 688 | -- | A special case of `spawn` for creating Node.js child processes. The first 689 | -- | argument is the module to be run, and the second is the argv (command line 690 | -- | arguments). 691 | fork 692 | :: String 693 | -> Array String 694 | -> Effect ChildProcess 695 | fork modulePath args = coerce $ UnsafeCP.fork' modulePath args { stdio: [ pipe, pipe, pipe, ipc ] } 696 | 697 | -- | - `cwd` | Current working directory of the child process. 698 | -- | - `detached` Prepare child to run independently of its parent process. Specific behavior depends on the platform, see options.detached). 699 | -- | - `env` Environment key-value pairs. Default: process.env. 700 | -- | - `execPath` Executable used to create the child process. 701 | -- | - `execArgv` List of string arguments passed to the executable. Default: process.execArgv. 702 | -- | - `gid` Sets the group identity of the process (see setgid(2)). 703 | -- | - `serialization` Specify the kind of serialization used for sending messages between processes. Possible values are 'json' and 'advanced'. See Advanced serialization for more details. Default: 'json'. 704 | -- | - `signal` Allows closing the child process using an AbortSignal. 705 | -- | - `killSignal` | The signal value to be used when the spawned process will be killed by timeout or abort signal. Default: 'SIGTERM'. 706 | -- | - `silent` If true, stdin, stdout, and stderr of the child will be piped to the parent, otherwise they will be inherited from the parent, see the 'pipe' and 'inherit' options for child_process.spawn()'s stdio for more details. Default: false. 707 | -- | - `uid` Sets the user identity of the process (see setuid(2)). 708 | -- | - `windowsVerbatimArguments` No quoting or escaping of arguments is done on Windows. Ignored on Unix. Default: false. 709 | -- | - `timeout` In milliseconds the maximum amount of time the process is allowed to run. Default: undefined. 710 | type ForkOptions = 711 | { cwd :: Maybe String 712 | , detached :: Maybe Boolean 713 | , appendStdio :: Maybe (Array StdIO) 714 | , env :: Maybe (Object String) 715 | , execPath :: Maybe String 716 | , execArgv :: Maybe (Array String) 717 | , gid :: Maybe Gid 718 | , serialization :: Maybe String 719 | , killSignal :: Maybe KillSignal 720 | , silent :: Maybe Boolean 721 | , uid :: Maybe Uid 722 | , windowsVerbatimArguments :: Maybe Boolean 723 | , timeout :: Maybe Milliseconds 724 | } 725 | 726 | fork' 727 | :: String 728 | -> Array String 729 | -> (ForkOptions -> ForkOptions) 730 | -> Effect ChildProcess 731 | fork' modulePath args buildOpts = coerce $ UnsafeCP.fork' modulePath args opts 732 | where 733 | opts = 734 | { stdio: [ pipe, pipe, pipe, ipc ] <> fromMaybe [] o.appendStdio 735 | , cwd: fromMaybe undefined o.cwd 736 | , detached: fromMaybe undefined o.detached 737 | , env: fromMaybe undefined o.env 738 | , execPath: fromMaybe undefined o.execPath 739 | , execArgv: fromMaybe undefined o.execArgv 740 | , gid: fromMaybe undefined o.gid 741 | , serialization: fromMaybe undefined o.serialization 742 | , killSignal: fromMaybe undefined o.killSignal 743 | , silent: fromMaybe undefined o.silent 744 | , uid: fromMaybe undefined o.uid 745 | , windowsVerbatimArguments: fromMaybe undefined o.windowsVerbatimArguments 746 | , timeout: fromMaybe undefined o.timeout 747 | } 748 | o = buildOpts 749 | { cwd: Nothing 750 | , detached: Nothing 751 | , appendStdio: Nothing 752 | , env: Nothing 753 | , execPath: Nothing 754 | , execArgv: Nothing 755 | , gid: Nothing 756 | , serialization: Nothing 757 | , killSignal: Nothing 758 | , silent: Nothing 759 | , uid: Nothing 760 | , windowsVerbatimArguments: Nothing 761 | , timeout: Nothing 762 | } 763 | 764 | -- | Send messages to the (`nodejs`) child process. 765 | -- | 766 | -- | See the [node documentation](https://nodejs.org/api/child_process.html#child_process_subprocess_send_message_sendhandle_options_callback) 767 | -- | for in-depth documentation. 768 | send 769 | :: forall props 770 | . { | props } 771 | -> Maybe Handle 772 | -> ChildProcess 773 | -> Effect Boolean 774 | send msg handle cp = UnsafeCP.unsafeSend msg (toNullable handle) (coerce cp) 775 | 776 | -- | - `keepAlive` A value that can be used when passing instances of `net.Socket` as the `Handle`. When true, the socket is kept open in the sending process. Default: false. 777 | type SendOptions = 778 | { keepAlive :: Maybe Boolean 779 | } 780 | 781 | send' 782 | :: forall props 783 | . { | props } 784 | -> Maybe Handle 785 | -> (SendOptions -> SendOptions) 786 | -> (Maybe Error -> Effect Unit) 787 | -> ChildProcess 788 | -> Effect Boolean 789 | send' msg handle buildOpts cb cp = 790 | UnsafeCP.unsafeSendOptsCb msg (toNullable handle) opts cb (coerce cp) 791 | where 792 | opts = 793 | { keepAlive: fromMaybe undefined o.keepAlive } 794 | o = buildOpts 795 | { keepAlive: Nothing 796 | } 797 | 798 | -- Unfortunately, there's not be a better way... 799 | foreign import undefined :: forall a. a 800 | -------------------------------------------------------------------------------- /src/Node/ChildProcess/Aff.purs: -------------------------------------------------------------------------------- 1 | module Node.ChildProcess.Aff where 2 | 3 | import Prelude 4 | 5 | import Control.Parallel (parOneOf) 6 | import Data.Either (Either(..)) 7 | import Data.Maybe (fromJust) 8 | import Data.Posix (Pid) 9 | import Effect.Aff (Aff, effectCanceler, makeAff) 10 | import Effect.Ref as Ref 11 | import Node.ChildProcess.Types (UnsafeChildProcess) 12 | import Node.UnsafeChildProcess.Safe as CPSafe 13 | import Node.ChildProcess (ChildProcess, toUnsafeChildProcess) 14 | import Node.Errors.SystemError (SystemError) 15 | import Node.EventEmitter (once) 16 | import Partial.Unsafe (unsafePartial) 17 | 18 | -- | Blocks until either a `spawn` or `error` event is fired. 19 | -- | If a `spawn` event fired, child process was successfully started 20 | -- | and the `pid` of the process can be obtained. 21 | -- | If an `error` event fires, child process was not started successfully. 22 | waitSpawned :: ChildProcess -> Aff (Either SystemError Pid) 23 | waitSpawned = toUnsafeChildProcess >>> waitSpawned' 24 | 25 | -- | Same as `waitSpawned` but works on `UnsafeChildProcess` 26 | -- | 27 | -- | Blocks until either a `spawn` or `error` event is fired. 28 | -- | If a `spawn` event fired, child process was successfully started 29 | -- | and the `pid` of the process can be obtained. 30 | -- | If an `error` event fires, child process was not started successfully. 31 | waitSpawned' :: UnsafeChildProcess -> Aff (Either SystemError Pid) 32 | waitSpawned' cp = parOneOf [ pidOnSpawn, errored ] 33 | where 34 | pidOnSpawn = makeAff \done -> do 35 | ref <- Ref.new mempty 36 | removeListener <- cp # once CPSafe.spawnH do 37 | join $ Ref.read ref 38 | pid' <- CPSafe.pid cp 39 | done $ Right $ Right $ unsafePartial $ fromJust pid' 40 | Ref.write removeListener ref 41 | pure $ effectCanceler do 42 | removeListener 43 | 44 | errored = makeAff \done -> do 45 | ref <- Ref.new mempty 46 | removeListener <- cp # once CPSafe.errorH \sysErr -> do 47 | join $ Ref.read ref 48 | done $ Right $ Left sysErr 49 | Ref.write removeListener ref 50 | pure $ effectCanceler do 51 | removeListener 52 | -------------------------------------------------------------------------------- /src/Node/ChildProcess/Types.js: -------------------------------------------------------------------------------- 1 | export const showKillSignal = (ks) => ks + ""; 2 | export const showShell = (shell) => shell + ""; 3 | export const fromKillSignalImpl = (fromInt, fromStr, sig) => { 4 | const ty = typeof sig; 5 | if (ty === "number") return fromInt(sig | 0); 6 | if (ty === "string") return fromStr(sig); 7 | throw new Error("Impossible. Got kill signal that was neither int nor string: " + sig); 8 | }; 9 | -------------------------------------------------------------------------------- /src/Node/ChildProcess/Types.purs: -------------------------------------------------------------------------------- 1 | module Node.ChildProcess.Types 2 | ( UnsafeChildProcess 3 | , Handle 4 | , StdIO 5 | , pipe 6 | , ignore 7 | , overlapped 8 | , ipc 9 | , inherit 10 | , shareStream 11 | , fileDescriptor 12 | , fileDescriptor' 13 | , defaultStdIO 14 | , KillSignal 15 | , intSignal 16 | , stringSignal 17 | , fromKillSignal 18 | , fromKillSignal' 19 | , Shell 20 | , enableShell 21 | , customShell 22 | , StringOrBuffer 23 | , Exit(..) 24 | ) where 25 | 26 | import Prelude 27 | 28 | import Data.Either (Either(..), either) 29 | import Data.Function.Uncurried (Fn3, runFn3) 30 | import Data.Nullable (Nullable, null) 31 | import Node.FS (FileDescriptor) 32 | import Node.Stream (Stream) 33 | import Unsafe.Coerce (unsafeCoerce) 34 | 35 | -- | A child process with no guarantees about whether or not 36 | -- | properties or methods (e.g. `stdin`, `send`) that depend on 37 | -- | options or which function used to start the child process 38 | -- | (e.g. `stdio`, `fork`) exist. 39 | foreign import data UnsafeChildProcess :: Type 40 | 41 | -- | A handle for inter-process communication (IPC). 42 | foreign import data Handle :: Type 43 | 44 | -- | See https://nodejs.org/docs/latest-v18.x/api/child_process.html#optionsstdio 45 | foreign import data StdIO :: Type 46 | 47 | -- | When used for X, then Y will exist on the child process where X and Y are 48 | -- | - `stdio[0]` - `stdin` 49 | -- | - `stdio[1]` - `stdout` 50 | -- | - `stdio[2]` - `stderr` 51 | -- | 52 | -- | Note: when used with `stdin`, piping the parent stdin to this stream 53 | -- | will not cause the child process to terminate when that parent stdin stream 54 | -- | ends via `Ctrl+D` user input. Rather, the child process will hang 55 | -- | until the parent process calls `Stream.end` on the child process' 56 | -- | `stdin` stream. Since it's impossible to know when the user 57 | -- | inputs `Ctrl+D`, `inherit` should be used instead. 58 | pipe :: StdIO 59 | pipe = unsafeCoerce "pipe" 60 | 61 | ignore :: StdIO 62 | ignore = unsafeCoerce "ignore" 63 | 64 | overlapped :: StdIO 65 | overlapped = unsafeCoerce "overlapped" 66 | 67 | ipc :: StdIO 68 | ipc = unsafeCoerce "ipc" 69 | 70 | -- | Uses the parent's corresponding stream. 71 | -- | 72 | -- | Note: this value must be used for `stdin` if one 73 | -- | wants to pipe the parent's `stdin` into the child process' `stdin` 74 | -- | AND cause the child process to terminate when the user closes 75 | -- | the parent's `stdin` via `Ctrl+D`. Using `pipe` instead 76 | -- | will cause the child process to hang, even when `Ctrl+D` is pressed, 77 | -- | until the parent process calls `Stream.end`, which cannot be reliably 78 | -- | called the moment AFTER `Ctrl+D` is pressed. 79 | inherit :: StdIO 80 | inherit = unsafeCoerce "inherit" 81 | 82 | shareStream :: forall r. Stream r -> StdIO 83 | shareStream = unsafeCoerce 84 | 85 | fileDescriptor :: Int -> StdIO 86 | fileDescriptor = unsafeCoerce 87 | 88 | fileDescriptor' :: FileDescriptor -> StdIO 89 | fileDescriptor' = unsafeCoerce 90 | 91 | defaultStdIO :: StdIO 92 | defaultStdIO = unsafeCoerce (null :: Nullable String) 93 | 94 | foreign import data KillSignal :: Type 95 | 96 | instance Eq KillSignal where 97 | eq a b = a # fromKillSignal' 98 | ( \i -> b # fromKillSignal' 99 | (\b' -> i == b') 100 | (const false) 101 | ) 102 | ( \s -> b # fromKillSignal' 103 | (const false) 104 | (\b' -> s == b') 105 | ) 106 | 107 | instance Show KillSignal where 108 | show = showKillSignal 109 | 110 | foreign import showKillSignal :: KillSignal -> String 111 | 112 | intSignal :: Int -> KillSignal 113 | intSignal = unsafeCoerce 114 | 115 | stringSignal :: String -> KillSignal 116 | stringSignal = unsafeCoerce 117 | 118 | fromKillSignal :: KillSignal -> Either Int String 119 | fromKillSignal sig = fromKillSignal' Left Right sig 120 | 121 | fromKillSignal' :: forall r. (Int -> r) -> (String -> r) -> KillSignal -> r 122 | fromKillSignal' fromInt fromStr sig = runFn3 fromKillSignalImpl fromInt fromStr sig 123 | 124 | foreign import fromKillSignalImpl :: forall r. Fn3 (Int -> r) (String -> r) (KillSignal) r 125 | 126 | foreign import data Shell :: Type 127 | 128 | instance Show Shell where 129 | show = showShell 130 | 131 | foreign import showShell :: Shell -> String 132 | 133 | enableShell :: Shell 134 | enableShell = unsafeCoerce true 135 | 136 | customShell :: String -> Shell 137 | customShell = unsafeCoerce 138 | 139 | -- | Indicates value is either a String or a Buffer depending on 140 | -- | what options were used. 141 | foreign import data StringOrBuffer :: Type 142 | 143 | -- | Specifies how a child process exited; normally (with an exit code), due to 144 | -- | a signal or if it failed to even launch (e.g. if a command doesn't exist). 145 | data Exit 146 | = Normally Int 147 | | BySignal KillSignal 148 | | BySysError 149 | 150 | instance showExit :: Show Exit where 151 | show (Normally x) = "Normally " <> show x 152 | show (BySignal sig) = "BySignal " <> (either show show $ fromKillSignal sig) 153 | show BySysError = "BySysError" 154 | -------------------------------------------------------------------------------- /src/Node/UnsafeChildProcess/Safe.js: -------------------------------------------------------------------------------- 1 | export const connectedImpl = (cp) => cp.connected; 2 | export const disconnectImpl = (cp) => cp.disconnect(); 3 | export const exitCodeImpl = (cp) => cp.exitCode; 4 | export const pidImpl = (cp) => cp.pid; 5 | export const killImpl = (cp) => cp.kill(); 6 | export const killStrImpl = (cp, str) => cp.kill(str); 7 | export const killedImpl = (cp) => cp.killed; 8 | export const refImpl = (cp) => cp.ref(); 9 | export const unrefImpl = (cp) => cp.unref(); 10 | export const signalCodeImpl = (cp) => cp.signalCode; 11 | export const spawnArgs = (cp) => cp.spawnArgs; 12 | export const spawnFile = (cp) => cp.spawnFile; 13 | export const stdio = (cp) => cp.stdio; 14 | -------------------------------------------------------------------------------- /src/Node/UnsafeChildProcess/Safe.purs: -------------------------------------------------------------------------------- 1 | -- | All API below is safe (i.e. does not crash if called) 2 | -- | and independent from options or which 3 | -- | function was used to start the `ChildProcess`. 4 | module Node.UnsafeChildProcess.Safe 5 | ( toEventEmitter 6 | , closeH 7 | , disconnectH 8 | , errorH 9 | , exitH 10 | , messageH 11 | , spawnH 12 | , pid 13 | , pidExists 14 | , connected 15 | , disconnect 16 | , exitCode 17 | , kill 18 | , kill' 19 | , killSignal 20 | , killed 21 | , ref 22 | , unref 23 | , signalCode 24 | , spawnFile 25 | , spawnArgs 26 | , stdio 27 | ) where 28 | 29 | import Prelude 30 | 31 | import Data.Maybe (Maybe(..)) 32 | import Data.Nullable (Nullable, toMaybe) 33 | import Data.Posix (Pid) 34 | import Data.Posix.Signal (Signal) 35 | import Data.Posix.Signal as Signal 36 | import Effect (Effect) 37 | import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn1, mkEffectFn2, runEffectFn1, runEffectFn2) 38 | import Foreign (Foreign) 39 | import Node.ChildProcess.Types (Exit(..), Handle, KillSignal, StdIO, UnsafeChildProcess, intSignal, stringSignal) 40 | import Node.Errors.SystemError (SystemError) 41 | import Node.EventEmitter (EventEmitter, EventHandle(..)) 42 | import Node.EventEmitter.UtilTypes (EventHandle0, EventHandle1) 43 | import Partial.Unsafe (unsafeCrashWith) 44 | import Unsafe.Coerce (unsafeCoerce) 45 | 46 | toEventEmitter :: UnsafeChildProcess -> EventEmitter 47 | toEventEmitter = unsafeCoerce 48 | 49 | closeH :: EventHandle UnsafeChildProcess (Exit -> Effect Unit) (EffectFn2 (Nullable Int) (Nullable KillSignal) Unit) 50 | closeH = EventHandle "close" \cb -> mkEffectFn2 \code signal -> 51 | case toMaybe code, toMaybe signal of 52 | Just c, _ -> cb $ Normally c 53 | _, Just s -> cb $ BySignal s 54 | _, _ -> unsafeCrashWith $ "Impossible. 'close' event did not get an exit code or kill signal: " <> show code <> "; " <> (unsafeCoerce signal) 55 | 56 | disconnectH :: EventHandle0 UnsafeChildProcess 57 | disconnectH = EventHandle "disconnect" identity 58 | 59 | errorH :: EventHandle1 UnsafeChildProcess SystemError 60 | errorH = EventHandle "error" mkEffectFn1 61 | 62 | exitH :: EventHandle UnsafeChildProcess (Exit -> Effect Unit) (EffectFn2 (Nullable Int) (Nullable KillSignal) Unit) 63 | exitH = EventHandle "exit" \cb -> mkEffectFn2 \code signal -> 64 | case toMaybe code, toMaybe signal of 65 | Just c, _ -> cb $ Normally c 66 | _, Just s -> cb $ BySignal s 67 | _, _ -> unsafeCrashWith $ "Impossible. 'exit' event did not get an exit code or kill signal: " <> show code <> "; " <> (unsafeCoerce signal) 68 | 69 | messageH :: EventHandle UnsafeChildProcess (Foreign -> Maybe Handle -> Effect Unit) (EffectFn2 Foreign (Nullable Handle) Unit) 70 | messageH = EventHandle "message" \cb -> mkEffectFn2 \a b -> cb a $ toMaybe b 71 | 72 | spawnH :: EventHandle0 UnsafeChildProcess 73 | spawnH = EventHandle "spawn" identity 74 | 75 | -- | The process ID of a child process. Note that if the process has already 76 | -- | exited, another process may have taken the same ID, so be careful! 77 | pid :: UnsafeChildProcess -> Effect (Maybe Pid) 78 | pid cp = map toMaybe $ runEffectFn1 pidImpl cp 79 | 80 | foreign import pidImpl :: EffectFn1 (UnsafeChildProcess) (Nullable Pid) 81 | 82 | -- | Note: this will not work if the user does not have permission to kill 83 | -- | a `PID`. Uses `cp.kill(0)` underneath. 84 | pidExists :: UnsafeChildProcess -> Effect Boolean 85 | pidExists cp = kill' (intSignal 0) cp 86 | 87 | -- | Indicates whether it is still possible to send and receive 88 | -- | messages from the child process. 89 | connected :: UnsafeChildProcess -> Effect Boolean 90 | connected cp = runEffectFn1 connectedImpl cp 91 | 92 | foreign import connectedImpl :: EffectFn1 (UnsafeChildProcess) (Boolean) 93 | 94 | exitCode :: UnsafeChildProcess -> Effect (Maybe Int) 95 | exitCode cp = map toMaybe $ runEffectFn1 exitCodeImpl cp 96 | 97 | foreign import exitCodeImpl :: EffectFn1 (UnsafeChildProcess) (Nullable Int) 98 | 99 | -- | Closes the IPC channel between parent and child. 100 | disconnect :: UnsafeChildProcess -> Effect Unit 101 | disconnect cp = runEffectFn1 disconnectImpl cp 102 | 103 | foreign import disconnectImpl :: EffectFn1 (UnsafeChildProcess) (Unit) 104 | 105 | kill :: UnsafeChildProcess -> Effect Boolean 106 | kill cp = runEffectFn1 killImpl cp 107 | 108 | foreign import killImpl :: EffectFn1 (UnsafeChildProcess) (Boolean) 109 | 110 | kill' :: KillSignal -> UnsafeChildProcess -> Effect Boolean 111 | kill' sig cp = runEffectFn2 killStrImpl cp sig 112 | 113 | foreign import killStrImpl :: EffectFn2 (UnsafeChildProcess) (KillSignal) (Boolean) 114 | 115 | -- | Send a signal to a child process. In the same way as the 116 | -- | [unix kill(2) system call](https://linux.die.net/man/2/kill), 117 | -- | sending a signal to a child process won't necessarily kill it. 118 | -- | 119 | -- | The resulting effects of this function depend on the process 120 | -- | and the signal. They can vary from system to system. 121 | -- | The child process might emit an `"error"` event if the signal 122 | -- | could not be delivered. 123 | killSignal :: Signal -> UnsafeChildProcess -> Effect Boolean 124 | killSignal sig cp = kill' (stringSignal $ Signal.toString sig) cp 125 | 126 | killed :: UnsafeChildProcess -> Effect Boolean 127 | killed cp = runEffectFn1 killedImpl cp 128 | 129 | foreign import killedImpl :: EffectFn1 (UnsafeChildProcess) (Boolean) 130 | 131 | ref :: UnsafeChildProcess -> Effect Unit 132 | ref cp = runEffectFn1 refImpl cp 133 | 134 | foreign import refImpl :: EffectFn1 (UnsafeChildProcess) (Unit) 135 | 136 | unref :: UnsafeChildProcess -> Effect Unit 137 | unref cp = runEffectFn1 unrefImpl cp 138 | 139 | foreign import unrefImpl :: EffectFn1 (UnsafeChildProcess) (Unit) 140 | 141 | signalCode :: UnsafeChildProcess -> Effect (Maybe String) 142 | signalCode cp = map toMaybe $ runEffectFn1 signalCodeImpl cp 143 | 144 | foreign import signalCodeImpl :: EffectFn1 (UnsafeChildProcess) (Nullable String) 145 | 146 | foreign import spawnArgs :: UnsafeChildProcess -> Array String 147 | 148 | foreign import spawnFile :: UnsafeChildProcess -> String 149 | 150 | foreign import stdio :: UnsafeChildProcess -> Array StdIO 151 | -------------------------------------------------------------------------------- /src/Node/UnsafeChildProcess/Unsafe.js: -------------------------------------------------------------------------------- 1 | export { 2 | exec as execImpl, 3 | exec as execOptsImpl, 4 | exec as execCbImpl, 5 | exec as execOptsCbImpl, 6 | execFile as execFileImpl, 7 | execFile as execFileOptsImpl, 8 | execFile as execFileCbImpl, 9 | execFile as execFileOptsCbImpl, 10 | spawn as spawnImpl, 11 | spawn as spawnOptsImpl, 12 | execSync as execSyncImpl, 13 | execSync as execSyncOptsImpl, 14 | execFileSync as execFileSyncImpl, 15 | execFileSync as execFileSyncOptsImpl, 16 | spawnSync as spawnSyncImpl, 17 | spawnSync as spawnSyncOptsImpl, 18 | fork as forkImpl, 19 | fork as forkOptsImpl, 20 | } from "node:child_process"; 21 | 22 | export const unsafeStdin = (cp) => cp.stdin; 23 | export const unsafeStdout = (cp) => cp.stdout; 24 | export const unsafeStderr = (cp) => cp.stderr; 25 | export const unsafeChannelRefImpl = (cp) => cp.channel.ref(); 26 | export const unsafeChannelUnrefImpl = (cp) => cp.channel.unref(); 27 | export const sendImpl = (cp, msg, handle) => cp.send(msg, handle); 28 | export const sendOptsImpl = (cp, msg, handle, opts) => cp.send(msg, handle, opts); 29 | export const sendCbImpl = (cp, msg, handle, cb) => cp.send(msg, handle, cb); 30 | export const sendOptsCbImpl = (cp, msg, handle, opts, cb) => cp.send(msg, handle, opts, cb); 31 | -------------------------------------------------------------------------------- /src/Node/UnsafeChildProcess/Unsafe.purs: -------------------------------------------------------------------------------- 1 | -- | Exposes low-level functions for ChildProcess 2 | -- | where JavaScript values, rather than PureScript ones, 3 | -- | are expected. 4 | -- | 5 | -- | All functions prefixed with `unsafe` indicate why they can be unsafe 6 | -- | (i.e. produce a crash a runtime). All other functions 7 | -- | are unsafe because their options (or default ones if not specified) 8 | -- | can affect whether the `unsafe*` values/methods exist. 9 | -- | 10 | -- | All type aliases for options (e.g. `JsExecSyncOptions`) are well-typed. 11 | module Node.UnsafeChildProcess.Unsafe 12 | ( unsafeSOBToString 13 | , unsafeSOBToBuffer 14 | , unsafeStdin 15 | , unsafeStdout 16 | , unsafeStderr 17 | , execSync 18 | , JsExecSyncOptions 19 | , execSync' 20 | , exec 21 | , JsExecOptions 22 | , execOpts 23 | , execCb 24 | , execOptsCb 25 | , execFileSync 26 | , JsExecFileSyncOptions 27 | , execFileSync' 28 | , execFile 29 | , JsExecFileOptions 30 | , execFileOpts 31 | , execFileCb 32 | , execFileOptsCb 33 | , JsSpawnSyncResult 34 | , spawnSync 35 | , JsSpawnSyncOptions 36 | , spawnSync' 37 | , spawn 38 | , JsSpawnOptions 39 | , spawn' 40 | , fork 41 | , JsForkOptions 42 | , fork' 43 | , unsafeSend 44 | , JsSendOptions 45 | , unsafeSendOpts 46 | , unsafeSendCb 47 | , unsafeSendOptsCb 48 | , unsafeChannelRef 49 | , unsafeChannelUnref 50 | ) where 51 | 52 | import Prelude 53 | 54 | import Data.Maybe (Maybe) 55 | import Data.Nullable (Nullable, toMaybe) 56 | import Data.Posix (Gid, Pid, Uid) 57 | import Data.Time.Duration (Milliseconds) 58 | import Effect (Effect) 59 | import Effect.Exception (Error) 60 | import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, EffectFn5, mkEffectFn1, mkEffectFn3, runEffectFn1, runEffectFn2, runEffectFn3, runEffectFn4, runEffectFn5) 61 | import Foreign (Foreign) 62 | import Foreign.Object (Object) 63 | import Node.Buffer (Buffer) 64 | import Node.ChildProcess.Types (Handle, KillSignal, Shell, StdIO, StringOrBuffer, UnsafeChildProcess) 65 | import Node.Errors.SystemError (SystemError) 66 | import Node.Stream (Readable, Writable) 67 | import Prim.Row as Row 68 | import Unsafe.Coerce (unsafeCoerce) 69 | 70 | -- | Same as `unsafeCoerce`. No runtime checking is done to ensure 71 | -- | the value is a `String`. 72 | unsafeSOBToString :: StringOrBuffer -> String 73 | unsafeSOBToString = unsafeCoerce 74 | 75 | -- | Same as `unsafeCoerce`. No runtime checking is done to ensure 76 | -- | the value is a `Buffer`. 77 | unsafeSOBToBuffer :: StringOrBuffer -> Buffer 78 | unsafeSOBToBuffer = unsafeCoerce 79 | 80 | -- | Unsafe because it depends on what value was passed in via `stdio[0]` 81 | foreign import unsafeStdin :: UnsafeChildProcess -> Nullable (Writable ()) 82 | -- | Unsafe because it depends on what value was passed in via `stdio[1]` 83 | foreign import unsafeStdout :: UnsafeChildProcess -> Nullable (Readable ()) 84 | -- | Unsafe because it depends on what value was passed in via `stdio[2]` 85 | foreign import unsafeStderr :: UnsafeChildProcess -> Nullable (Readable ()) 86 | 87 | execSync :: String -> Effect StringOrBuffer 88 | execSync command = runEffectFn1 execSyncImpl command 89 | 90 | foreign import execSyncImpl :: EffectFn1 (String) (StringOrBuffer) 91 | 92 | -- | - `cwd` | Current working directory of the child process. 93 | -- | - `input` | | | The value which will be passed as stdin to the spawned process. Supplying this value will override stdio[0]. 94 | -- | - `stdio` | Child's stdio configuration. stderr by default will be output to the parent process' stderr unless stdio is specified. Default: 'pipe'. 95 | -- | - `env` Environment key-value pairs. Default: process.env. 96 | -- | - `shell` Shell to execute the command with. See Shell requirements and Default Windows shell. Default: '/bin/sh' on Unix, process.env.ComSpec on Windows. 97 | -- | - `uid` Sets the user identity of the process. (See setuid(2)). 98 | -- | - `gid` Sets the group identity of the process. (See setgid(2)). 99 | -- | - `timeout` In milliseconds the maximum amount of time the process is allowed to run. Default: undefined. 100 | -- | - `killSignal` | The signal value to be used when the spawned process will be killed. Default: 'SIGTERM'. 101 | -- | - `maxBuffer` Largest amount of data in bytes allowed on stdout or stderr. If exceeded, the child process is terminated and any output is truncated. See caveat at maxBuffer and Unicode. Default: 1024 * 1024. 102 | -- | - `encoding` The encoding used for all stdio inputs and outputs. Default: 'buffer'. 103 | -- | - `windowsHide` Hide the subprocess console window that would normally be created on Windows systems. Default: false. 104 | type JsExecSyncOptions = 105 | ( cwd :: String 106 | , input :: Buffer 107 | , stdio :: Array StdIO 108 | , env :: Object String 109 | , shell :: String 110 | , uid :: Uid 111 | , gid :: Gid 112 | , timeout :: Milliseconds 113 | , killSignal :: KillSignal 114 | , maxBuffer :: Number 115 | , encoding :: String 116 | , windowsHide :: Boolean 117 | ) 118 | 119 | execSync' 120 | :: forall r trash 121 | . Row.Union r trash JsExecSyncOptions 122 | => String 123 | -> { | r } 124 | -> Effect StringOrBuffer 125 | execSync' command opts = runEffectFn2 execSyncOptsImpl command opts 126 | 127 | foreign import execSyncOptsImpl :: forall r. EffectFn2 (String) ({ | r }) (StringOrBuffer) 128 | 129 | exec :: String -> Effect UnsafeChildProcess 130 | exec command = runEffectFn1 execImpl command 131 | 132 | foreign import execImpl :: EffectFn1 (String) (UnsafeChildProcess) 133 | 134 | -- | - `cwd` | Current working directory of the child process. 135 | -- | - `env` Environment key-value pairs. Default: process.env. 136 | -- | - `encoding` Default: 'utf8' 137 | -- | - `timeout` Default: 0 138 | -- | - `maxBuffer` Largest amount of data in bytes allowed on stdout or stderr. If exceeded, the child process is terminated and any output is truncated. See caveat at maxBuffer and Unicode. Default: 1024 * 1024. 139 | -- | - `killSignal` | Default: 'SIGTERM' 140 | -- | - `uid` Sets the user identity of the process (see setuid(2)). 141 | -- | - `gid` Sets the group identity of the process (see setgid(2)). 142 | -- | - `windowsHide` Hide the subprocess console window that would normally be created on Windows systems. Default: false. 143 | -- | - `shell` | If true, runs command inside of a shell. Uses '/bin/sh' on Unix, and process.env.ComSpec on Windows. A different shell can be specified as a string. See Shell requirements and Default Windows shell. Default: false (no shell). 144 | type JsExecOptions = 145 | ( cwd :: String 146 | , env :: Object String 147 | , encoding :: String 148 | , timeout :: Milliseconds 149 | , maxBuffer :: Number 150 | , killSignal :: KillSignal 151 | , uid :: Uid 152 | , gid :: Gid 153 | , windowsHide :: Boolean 154 | , shell :: Shell 155 | ) 156 | 157 | execOpts 158 | :: forall r trash 159 | . Row.Union r trash JsExecOptions 160 | => String 161 | -> { | r } 162 | -> Effect UnsafeChildProcess 163 | execOpts command opts = runEffectFn2 execOptsImpl command opts 164 | 165 | foreign import execOptsImpl :: forall r. EffectFn2 (String) ({ | r }) (UnsafeChildProcess) 166 | 167 | execCb :: String -> (Maybe SystemError -> StringOrBuffer -> StringOrBuffer -> Effect Unit) -> Effect UnsafeChildProcess 168 | execCb command cb = runEffectFn2 execCbImpl command $ mkEffectFn3 \err sout serr -> 169 | cb (toMaybe err) sout serr 170 | 171 | foreign import execCbImpl :: EffectFn2 (String) (EffectFn3 (Nullable SystemError) StringOrBuffer StringOrBuffer Unit) (UnsafeChildProcess) 172 | 173 | execOptsCb 174 | :: forall r trash 175 | . Row.Union r trash JsExecOptions 176 | => String 177 | -> { | r } 178 | -> (Maybe SystemError -> StringOrBuffer -> StringOrBuffer -> Effect Unit) 179 | -> Effect UnsafeChildProcess 180 | execOptsCb command opts cb = runEffectFn3 execOptsCbImpl command opts $ mkEffectFn3 \err sout serr -> 181 | cb (toMaybe err) sout serr 182 | 183 | foreign import execOptsCbImpl :: forall r. EffectFn3 (String) ({ | r }) (EffectFn3 (Nullable SystemError) StringOrBuffer StringOrBuffer Unit) (UnsafeChildProcess) 184 | 185 | execFileSync :: String -> Array String -> Effect StringOrBuffer 186 | execFileSync file args = runEffectFn2 execFileSyncImpl file args 187 | 188 | foreign import execFileSyncImpl :: EffectFn2 (String) (Array String) (StringOrBuffer) 189 | 190 | -- | - `cwd` | Current working directory of the child process. 191 | -- | - `input` | | | The value which will be passed as stdin to the spawned process. Supplying this value will override stdio[0]. 192 | -- | - `stdio` | Child's stdio configuration. stderr by default will be output to the parent process' stderr unless stdio is specified. Default: 'pipe'. 193 | -- | - `env` Environment key-value pairs. Default: process.env. 194 | -- | - `uid` Sets the user identity of the process (see setuid(2)). 195 | -- | - `gid` Sets the group identity of the process (see setgid(2)). 196 | -- | - `timeout` In milliseconds the maximum amount of time the process is allowed to run. Default: undefined. 197 | -- | - `killSignal` | The signal value to be used when the spawned process will be killed. Default: 'SIGTERM'. 198 | -- | - `maxBuffer` Largest amount of data in bytes allowed on stdout or stderr. If exceeded, the child process is terminated. See caveat at maxBuffer and Unicode. Default: 1024 * 1024. 199 | -- | - `encoding` The encoding used for all stdio inputs and outputs. Default: 'buffer'. 200 | -- | - `windowsHide` Hide the subprocess console window that would normally be created on Windows systems. Default: false. 201 | -- | - `shell` | If true, runs command inside of a shell. Uses '/bin/sh' on Unix, and process.env.ComSpec on Windows. A different shell can be specified as a string. See Shell requirements and Default Windows shell. Default: false (no shell). 202 | type JsExecFileSyncOptions = 203 | ( cwd :: String 204 | , input :: Buffer 205 | , stdio :: Array StdIO 206 | , env :: Object String 207 | , uid :: Uid 208 | , gid :: Gid 209 | , timeout :: Milliseconds 210 | , killSignal :: KillSignal 211 | , maxBuffer :: Number 212 | , encoding :: String 213 | , windowsHide :: Boolean 214 | , shell :: Shell 215 | ) 216 | 217 | execFileSync' 218 | :: forall r trash 219 | . Row.Union r trash JsExecFileSyncOptions 220 | => String 221 | -> Array String 222 | -> { | r } 223 | -> Effect StringOrBuffer 224 | execFileSync' file args options = runEffectFn3 execFileSyncOptsImpl file args options 225 | 226 | foreign import execFileSyncOptsImpl :: forall r. EffectFn3 (String) (Array String) ({ | r }) (StringOrBuffer) 227 | 228 | execFile :: String -> Array String -> Effect UnsafeChildProcess 229 | execFile file args = runEffectFn2 execFileImpl file args 230 | 231 | foreign import execFileImpl :: EffectFn2 (String) (Array String) (UnsafeChildProcess) 232 | 233 | -- | - `cwd` | Current working directory of the child process. 234 | -- | - `env` Environment key-value pairs. Default: process.env. 235 | -- | - `encoding` Default: 'utf8' 236 | -- | - `timeout` Default: 0 237 | -- | - `maxBuffer` Largest amount of data in bytes allowed on stdout or stderr. If exceeded, the child process is terminated and any output is truncated. See caveat at maxBuffer and Unicode. Default: 1024 * 1024. 238 | -- | - `killSignal` | Default: 'SIGTERM' 239 | -- | - `uid` Sets the user identity of the process (see setuid(2)). 240 | -- | - `gid` Sets the group identity of the process (see setgid(2)). 241 | -- | - `windowsHide` Hide the subprocess console window that would normally be created on Windows systems. Default: false. 242 | -- | - `windowsVerbatimArguments` No quoting or escaping of arguments is done on Windows. Ignored on Unix. Default: false. 243 | -- | - `shell` | If true, runs command inside of a shell. Uses '/bin/sh' on Unix, and process.env.ComSpec on Windows. A different shell can be specified as a string. See Shell requirements and Default Windows shell. Default: false (no shell). 244 | type JsExecFileOptions = 245 | ( cwd :: String 246 | , env :: Object String 247 | , encoding :: String 248 | , timeout :: Milliseconds 249 | , maxBuffer :: Number 250 | , killSignal :: KillSignal 251 | , uid :: Uid 252 | , gid :: Gid 253 | , windowsHide :: Boolean 254 | , windowsVerbatimArguments :: Boolean 255 | , shell :: Shell 256 | ) 257 | 258 | execFileOpts 259 | :: forall r trash 260 | . Row.Union r trash JsExecFileOptions 261 | => String 262 | -> Array String 263 | -> { | r } 264 | -> Effect UnsafeChildProcess 265 | execFileOpts file args opts = runEffectFn3 execFileOptsImpl file args opts 266 | 267 | foreign import execFileOptsImpl :: forall r. EffectFn3 (String) (Array String) ({ | r }) (UnsafeChildProcess) 268 | 269 | execFileCb :: String -> Array String -> (SystemError -> StringOrBuffer -> StringOrBuffer -> Effect Unit) -> Effect UnsafeChildProcess 270 | execFileCb file args cb = runEffectFn3 execFileCbImpl file args $ mkEffectFn3 cb 271 | 272 | foreign import execFileCbImpl :: EffectFn3 (String) (Array String) (EffectFn3 SystemError StringOrBuffer StringOrBuffer Unit) (UnsafeChildProcess) 273 | 274 | execFileOptsCb 275 | :: forall r trash 276 | . Row.Union r trash JsExecFileOptions 277 | => String 278 | -> Array String 279 | -> { | r } 280 | -> (Maybe SystemError -> StringOrBuffer -> StringOrBuffer -> Effect Unit) 281 | -> Effect UnsafeChildProcess 282 | execFileOptsCb file args opts cb = runEffectFn4 execFileOptsCbImpl file args opts $ mkEffectFn3 \err sout serr -> 283 | cb (toMaybe err) sout serr 284 | 285 | foreign import execFileOptsCbImpl :: forall r. EffectFn4 (String) (Array String) ({ | r }) (EffectFn3 (Nullable SystemError) StringOrBuffer StringOrBuffer Unit) (UnsafeChildProcess) 286 | 287 | type JsSpawnSyncResult = 288 | { pid :: Pid 289 | , output :: Array Foreign 290 | , stdout :: StringOrBuffer 291 | , stderr :: StringOrBuffer 292 | , status :: Nullable Int 293 | , signal :: Nullable KillSignal 294 | , error :: Nullable SystemError 295 | } 296 | 297 | spawnSync :: String -> Array String -> Effect JsSpawnSyncResult 298 | spawnSync command args = runEffectFn2 spawnSyncImpl command args 299 | 300 | foreign import spawnSyncImpl :: EffectFn2 (String) (Array String) (JsSpawnSyncResult) 301 | 302 | -- | - `cwd` | Current working directory of the child process. 303 | -- | - `input` | | | The value which will be passed as stdin to the spawned process. Supplying this value will override stdio[0]. 304 | -- | - `argv0` Explicitly set the value of argv[0] sent to the child process. This will be set to command if not specified. 305 | -- | - `stdio` | Child's stdio configuration. 306 | -- | - `env` Environment key-value pairs. Default: process.env. 307 | -- | - `uid` Sets the user identity of the process (see setuid(2)). 308 | -- | - `gid` Sets the group identity of the process (see setgid(2)). 309 | -- | - `timeout` In milliseconds the maximum amount of time the process is allowed to run. Default: undefined. 310 | -- | - `killSignal` | The signal value to be used when the spawned process will be killed. Default: 'SIGTERM'. 311 | -- | - `maxBuffer` Largest amount of data in bytes allowed on stdout or stderr. If exceeded, the child process is terminated and any output is truncated. See caveat at maxBuffer and Unicode. Default: 1024 * 1024. 312 | -- | - `encoding` The encoding used for all stdio inputs and outputs. Default: 'buffer'. 313 | -- | - `shell` | If true, runs command inside of a shell. Uses '/bin/sh' on Unix, and process.env.ComSpec on Windows. A different shell can be specified as a string. See Shell requirements and Default Windows shell. Default: false (no shell). 314 | -- | - `windowsVerbatimArguments` No quoting or escaping of arguments is done on Windows. Ignored on Unix. This is set to true automatically when shell is specified and is CMD. Default: false. 315 | -- | - `windowsHide` Hide the subprocess console window that would normally be created on Windows systems. Default: false. 316 | type JsSpawnSyncOptions = 317 | ( cwd :: String 318 | , input :: Buffer 319 | , argv0 :: String 320 | , stdio :: Array StdIO 321 | , env :: Object String 322 | , uid :: Uid 323 | , gid :: Gid 324 | , timeout :: Milliseconds 325 | , killSignal :: KillSignal 326 | , maxBuffer :: Number 327 | , encoding :: String 328 | , shell :: Shell 329 | , windowsVerbatimArguments :: Boolean 330 | , windowsHide :: Boolean 331 | ) 332 | 333 | spawnSync' 334 | :: forall r trash 335 | . Row.Union r trash JsSpawnSyncOptions 336 | => String 337 | -> Array String 338 | -> { | r } 339 | -> Effect JsSpawnSyncResult 340 | spawnSync' command args opts = runEffectFn3 spawnSyncOptsImpl command args opts 341 | 342 | foreign import spawnSyncOptsImpl :: forall r. EffectFn3 (String) (Array String) ({ | r }) (JsSpawnSyncResult) 343 | 344 | spawn :: String -> Array String -> Effect UnsafeChildProcess 345 | spawn command args = runEffectFn2 spawnImpl command args 346 | 347 | foreign import spawnImpl :: EffectFn2 (String) (Array String) (UnsafeChildProcess) 348 | 349 | -- | - `cwd` | Current working directory of the child process. 350 | -- | - `env` Environment key-value pairs. Default: process.env. 351 | -- | - `argv0` Explicitly set the value of argv[0] sent to the child process. This will be set to command if not specified. 352 | -- | - `stdio` | Child's stdio configuration (see options.stdio). 353 | -- | - `detached` Prepare child to run independently of its parent process. Specific behavior depends on the platform, see options.detached). 354 | -- | - `uid` Sets the user identity of the process (see setuid(2)). 355 | -- | - `gid` Sets the group identity of the process (see setgid(2)). 356 | -- | - `serialization` Specify the kind of serialization used for sending messages between processes. Possible values are 'json' and 'advanced'. See Advanced serialization for more details. Default: 'json'. 357 | -- | - `shell` | If true, runs command inside of a shell. Uses '/bin/sh' on Unix, and process.env.ComSpec on Windows. A different shell can be specified as a string. See Shell requirements and Default Windows shell. Default: false (no shell). 358 | -- | - `windowsVerbatimArguments` No quoting or escaping of arguments is done on Windows. Ignored on Unix. This is set to true automatically when shell is specified and is CMD. Default: false. 359 | -- | - `windowsHide` Hide the subprocess console window that would normally be created on Windows systems. Default: false. 360 | -- | - `signal` allows aborting the child process using an AbortSignal. 361 | -- | - `timeout` In milliseconds the maximum amount of time the process is allowed to run. Default: undefined. 362 | -- | - `killSignal` | The signal value to be used when the spawned process will be killed by timeout or abort signal. Default: 'SIGTERM'. 363 | type JsSpawnOptions = 364 | ( cwd :: String 365 | , env :: Object String 366 | , argv0 :: String 367 | , stdio :: Array StdIO 368 | , detached :: Boolean 369 | , uid :: Uid 370 | , gid :: Gid 371 | , serialization :: String 372 | , shell :: Shell 373 | , windowsVerbatimArguments :: Boolean 374 | , windowsHide :: Boolean 375 | , timeout :: Milliseconds 376 | , killSignal :: KillSignal 377 | ) 378 | 379 | spawn' 380 | :: forall r trash 381 | . Row.Union r trash JsSpawnOptions 382 | => String 383 | -> Array String 384 | -> { | r } 385 | -> Effect UnsafeChildProcess 386 | spawn' command args opts = runEffectFn3 spawnOptsImpl command args opts 387 | 388 | foreign import spawnOptsImpl :: forall r. EffectFn3 (String) (Array String) ({ | r }) (UnsafeChildProcess) 389 | 390 | fork :: String -> Array String -> Effect UnsafeChildProcess 391 | fork modulePath args = runEffectFn2 forkImpl modulePath args 392 | 393 | foreign import forkImpl :: EffectFn2 (String) (Array String) (UnsafeChildProcess) 394 | 395 | -- | - `cwd` | Current working directory of the child process. 396 | -- | - `detached` Prepare child to run independently of its parent process. Specific behavior depends on the platform, see options.detached). 397 | -- | - `env` Environment key-value pairs. Default: process.env. 398 | -- | - `execPath` Executable used to create the child process. 399 | -- | - `execArgv` List of string arguments passed to the executable. Default: process.execArgv. 400 | -- | - `gid` Sets the group identity of the process (see setgid(2)). 401 | -- | - `serialization` Specify the kind of serialization used for sending messages between processes. Possible values are 'json' and 'advanced'. See Advanced serialization for more details. Default: 'json'. 402 | -- | - `signal` Allows closing the child process using an AbortSignal. 403 | -- | - `killSignal` | The signal value to be used when the spawned process will be killed by timeout or abort signal. Default: 'SIGTERM'. 404 | -- | - `silent` If true, stdin, stdout, and stderr of the child will be piped to the parent, otherwise they will be inherited from the parent, see the 'pipe' and 'inherit' options for child_process.spawn()'s stdio for more details. Default: false. 405 | -- | - `stdio` | See child_process.spawn()'s stdio. When this option is provided, it overrides silent. If the array variant is used, it must contain exactly one item with value 'ipc' or an error will be thrown. For instance [0, 1, 2, 'ipc']. 406 | -- | - `uid` Sets the user identity of the process (see setuid(2)). 407 | -- | - `windowsVerbatimArguments` No quoting or escaping of arguments is done on Windows. Ignored on Unix. Default: false. 408 | -- | - `timeout` In milliseconds the maximum amount of time the process is allowed to run. Default: undefined. 409 | type JsForkOptions = 410 | ( cwd :: String 411 | , detached :: Boolean 412 | , env :: Object String 413 | , execPath :: String 414 | , execArgv :: Array String 415 | , gid :: Gid 416 | , serialization :: String 417 | , killSignal :: KillSignal 418 | , silent :: Boolean 419 | , stdio :: Array StdIO 420 | , uid :: Uid 421 | , windowsVerbatimArguments :: Boolean 422 | , timeout :: Milliseconds 423 | ) 424 | 425 | fork' 426 | :: forall r trash 427 | . Row.Union r trash JsForkOptions 428 | => String 429 | -> Array String 430 | -> { | r } 431 | -> Effect UnsafeChildProcess 432 | fork' modulePath args opts = runEffectFn3 forkOptsImpl modulePath args opts 433 | 434 | foreign import forkOptsImpl :: forall r. EffectFn3 (String) (Array String) { | r } (UnsafeChildProcess) 435 | 436 | -- | Unsafe because child process must be a Node child process and an IPC channel must exist. 437 | unsafeSend :: forall messageRows. { | messageRows } -> Nullable Handle -> UnsafeChildProcess -> Effect Boolean 438 | unsafeSend msg handle cp = runEffectFn3 sendImpl cp msg handle 439 | 440 | foreign import sendImpl :: forall messageRows. EffectFn3 (UnsafeChildProcess) ({ | messageRows }) (Nullable Handle) (Boolean) 441 | 442 | -- | - `keepAlive` A value that can be used when passing instances of `net.Socket` as the `Handle`. When true, the socket is kept open in the sending process. Default: false. 443 | type JsSendOptions = 444 | ( keepAlive :: Boolean 445 | ) 446 | 447 | -- | Unsafe because child process must be a Node child process and an IPC channel must exist. 448 | unsafeSendOpts 449 | :: forall r trash messageRows 450 | . Row.Union r trash JsSendOptions 451 | => { | messageRows } 452 | -> Nullable Handle 453 | -> { | r } 454 | -> UnsafeChildProcess 455 | -> Effect Boolean 456 | unsafeSendOpts msg handle opts cp = runEffectFn4 sendOptsImpl cp msg handle opts 457 | 458 | foreign import sendOptsImpl :: forall messageRows r. EffectFn4 (UnsafeChildProcess) ({ | messageRows }) (Nullable Handle) ({ | r }) (Boolean) 459 | 460 | -- | Unsafe because child process must be a Node child process and an IPC channel must exist. 461 | unsafeSendCb :: forall messageRows. { | messageRows } -> Nullable Handle -> (Maybe Error -> Effect Unit) -> UnsafeChildProcess -> Effect Boolean 462 | unsafeSendCb msg handle cb cp = runEffectFn4 sendCbImpl cp msg handle $ mkEffectFn1 \err -> cb $ toMaybe err 463 | 464 | foreign import sendCbImpl :: forall messageRows. EffectFn4 (UnsafeChildProcess) ({ | messageRows }) (Nullable Handle) (EffectFn1 (Nullable Error) Unit) (Boolean) 465 | 466 | -- | Unsafe because child process must be a Node child process and an IPC channel must exist. 467 | unsafeSendOptsCb 468 | :: forall r trash messageRows 469 | . Row.Union r trash JsSendOptions 470 | => { | messageRows } 471 | -> Nullable Handle 472 | -> { | r } 473 | -> (Maybe Error -> Effect Unit) 474 | -> UnsafeChildProcess 475 | -> Effect Boolean 476 | unsafeSendOptsCb msg handle opts cb cp = runEffectFn5 sendOptsCbImpl cp msg handle opts $ mkEffectFn1 \err -> cb $ toMaybe err 477 | 478 | foreign import sendOptsCbImpl :: forall messageRows r. EffectFn5 (UnsafeChildProcess) ({ | messageRows }) (Nullable Handle) ({ | r }) (EffectFn1 (Nullable Error) Unit) (Boolean) 479 | 480 | -- | Unsafe because it depends on whether an IPC channel exists. 481 | unsafeChannelRef :: UnsafeChildProcess -> Effect Unit 482 | unsafeChannelRef cp = runEffectFn1 unsafeChannelRefImpl cp 483 | 484 | foreign import unsafeChannelRefImpl :: EffectFn1 (UnsafeChildProcess) (Unit) 485 | 486 | -- | Unsafe because it depends on whether an IPC channel exists. 487 | unsafeChannelUnref :: UnsafeChildProcess -> Effect Unit 488 | unsafeChannelUnref cp = runEffectFn1 unsafeChannelUnrefImpl cp 489 | 490 | foreign import unsafeChannelUnrefImpl :: EffectFn1 (UnsafeChildProcess) (Unit) 491 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Data.Either (Either(..), hush) 6 | import Data.Maybe (Maybe(..)) 7 | import Data.Posix.Signal (Signal(..)) 8 | import Data.Posix.Signal as Signal 9 | import Effect (Effect) 10 | import Effect.Aff (Aff, effectCanceler, launchAff_, makeAff, nonCanceler) 11 | import Effect.Class (liftEffect) 12 | import Effect.Class.Console (log) 13 | import Effect.Exception (throw, throwException) 14 | import Node.Buffer as Buffer 15 | import Node.ChildProcess (exec', execSync', kill, spawn, stdin) 16 | import Node.ChildProcess as CP 17 | import Node.ChildProcess.Aff (waitSpawned) 18 | import Node.ChildProcess.Types (Exit(..), fromKillSignal) 19 | import Node.Encoding (Encoding(..)) 20 | import Node.Encoding as NE 21 | import Node.EventEmitter (EventHandle, once, once_) 22 | import Node.Stream as Stream 23 | import Unsafe.Coerce (unsafeCoerce) 24 | 25 | main :: Effect Unit 26 | main = launchAff_ do 27 | writingToStdinWorks 28 | spawnLs 29 | nonExistentExecutable 30 | noEffectsTooEarly 31 | killsProcess 32 | execLs 33 | execSyncEcho "some value" 34 | 35 | until 36 | :: forall emitter psCb jsCb a 37 | . emitter 38 | -> EventHandle emitter psCb jsCb 39 | -> ((a -> Effect Unit) -> psCb) 40 | -> Aff a 41 | until ee event cb = makeAff \done -> do 42 | rm <- ee # once event (cb (done <<< Right)) 43 | pure $ effectCanceler rm 44 | 45 | writingToStdinWorks :: Aff Unit 46 | writingToStdinWorks = do 47 | log "\nwriting to stdin works" 48 | sp <- liftEffect $ spawn "sh" [ "./test/sleep.sh" ] 49 | liftEffect do 50 | (stdin sp) # once_ Stream.errorH \err -> do 51 | log "Error in stdin" 52 | throwException $ unsafeCoerce err 53 | buf <- Buffer.fromString "helllo" UTF8 54 | void $ Stream.write (stdin sp) buf 55 | sp # once_ CP.errorH \err -> do 56 | log "Error in child process" 57 | throwException $ unsafeCoerce err 58 | exit <- until sp CP.closeH \completeAff -> \exit -> 59 | completeAff exit 60 | log $ "spawn sleep done " <> show exit 61 | 62 | spawnLs :: Aff Unit 63 | spawnLs = do 64 | log "\nspawns processes ok" 65 | ls <- liftEffect $ spawn "ls" [ "-la" ] 66 | res <- waitSpawned ls 67 | case res of 68 | Right pid -> log $ "ls successfully spawned with PID: " <> show pid 69 | Left err -> liftEffect $ throwException $ unsafeCoerce err 70 | exit <- until ls CP.closeH \complete -> \exit -> complete exit 71 | case exit of 72 | Normally 0 -> log $ "ls exited with 0" 73 | Normally i -> liftEffect $ throw $ "ls had non-zero exit: " <> show i 74 | BySignal sig -> liftEffect $ throw $ "ls exited with sig: " <> show sig 75 | BySysError -> liftEffect $ throw "ls exited with system error" 76 | 77 | nonExistentExecutable :: Aff Unit 78 | nonExistentExecutable = do 79 | log "\nemits an error if executable does not exist" 80 | ch <- liftEffect $ spawn "this-does-not-exist" [] 81 | res <- waitSpawned ch 82 | case res of 83 | Left _ -> log "nonexistent executable: all good." 84 | Right pid -> liftEffect $ throw $ "nonexistent executable started with PID: " <> show pid 85 | 86 | noEffectsTooEarly :: Aff Unit 87 | noEffectsTooEarly = do 88 | log "\ndoesn't perform effects too early" 89 | ls <- liftEffect $ spawn "ls" [ "-la" ] 90 | let _ = kill ls 91 | exit <- until ls CP.exitH \complete -> \exit -> complete exit 92 | case exit of 93 | Normally 0 -> 94 | log "All good!" 95 | _ -> 96 | liftEffect $ throw $ "Bad exit: expected `Normally 0`, got: " <> show exit 97 | 98 | killsProcess :: Aff Unit 99 | killsProcess = do 100 | log "\nkills processes" 101 | ls <- liftEffect $ spawn "ls" [ "-la" ] 102 | _ <- liftEffect $ kill ls 103 | exit <- until ls CP.exitH \complete -> \exit -> complete exit 104 | case exit of 105 | BySignal s | Just SIGTERM <- Signal.fromString =<< (hush $ fromKillSignal s) -> 106 | log "All good!" 107 | _ -> do 108 | liftEffect $ throw $ "Bad exit: expected `BySignal SIGTERM`, got: " <> show exit 109 | 110 | execLs :: Aff Unit 111 | execLs = do 112 | log "\nexec" 113 | r <- makeAff \done -> do 114 | -- returned ChildProcess is ignored here 115 | void $ exec' "ls >&2" identity (done <<< Right) 116 | pure nonCanceler 117 | stdout' <- liftEffect $ Buffer.toString UTF8 r.stdout 118 | stderr' <- liftEffect $ Buffer.toString UTF8 r.stderr 119 | when (stdout' /= "") do 120 | liftEffect $ throw $ "stdout should be redirected to stderr but had content: " <> show stdout' 121 | when (stderr' == "") do 122 | liftEffect $ throw $ "stderr should have content but was empty" 123 | log "stdout was successfully redirected to stderr" 124 | 125 | execSyncEcho :: String -> Aff Unit 126 | execSyncEcho str = liftEffect do 127 | log "\nexecSyncEcho" 128 | buf <- Buffer.fromString str UTF8 129 | resBuf <- execSync' "cat" (_ { input = Just buf }) 130 | res <- Buffer.toString NE.UTF8 resBuf 131 | when (str /= res) do 132 | throw $ "cat did not output its input. \nGot: " <> show res <> "\nExpected: " <> show str 133 | log "cat successfully re-outputted its input" 134 | -------------------------------------------------------------------------------- /test/sleep.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | sleep 2 4 | echo "$1" 5 | 6 | echo "Done" 7 | --------------------------------------------------------------------------------