🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@zoroaster/fork

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@zoroaster/fork - npm Package Compare versions

Comparing version
1.0.0
to
1.1.0
+114
src/index.js
import { fork } from 'spawncommand'
import forkFeed from 'forkfeed'
import { getForkArguments, assertForkOutput } from './lib'
import getArgs from './lib/get-args'
import { PassThrough } from 'stream'
import Catchment from 'catchment'
/**
* Run a fork.
* @param {Run} config Options for the run method.
* @param {(string|ForkConfig)} config.forkConfig Either the config, or the path to the module to fork.
* @param {string} config.input The input to the test from the test mask.
* @param {*} [config.props="{}"] Other Properties Of The Test, Such As `stdout` And `stderr`. Default `{}`.
* @param {Array<Context>} [config.contexts="[]"] The contexts for the test to be passed to `getArgs` and `getOptions`. Default `[]`.
* @returns {Promise<{stdout: string, stderr: string, code: number}>} The result of the work, updated to contain answers in the interactive mode.
*/
const run = async (config) => {
const {
forkConfig,
input,
props = {},
contexts = [],
} = config
const a = input ? getArgs(input) : []
const {
mod, args, options,
} = await getForkArguments(forkConfig, a, contexts, {
...props,
input,
})
const { promise, stdout, stdin, stderr } = fork(mod, args, options)
const { includeAnswers = true, log, inputs, stderrInputs } = forkConfig
const stdoutLog = new PassThrough()
const stderrLog = new PassThrough()
if (log === true) {
stdoutLog.pipe(process.stdout)
stderrLog.pipe(process.stderr)
} else if (log) {
log.stdout && stdoutLog.pipe(log.stdout)
log.stderr && stderrLog.pipe(log.stderr)
}
const needsStdoutAnswers = includeAnswers && inputs
const needsStderrAnswers = includeAnswers && stderrInputs
let co, ce
if (needsStdoutAnswers) co = new Catchment({ rs: stdoutLog })
if (needsStderrAnswers) ce = new Catchment({ rs: stderrLog })
forkFeed(stdout, stdin, inputs, stdoutLog)
forkFeed(stderr, stdin, stderrInputs, stderrLog)
const res = await promise
// override process's outputs with outputs with answers
if (needsStdoutAnswers) {
co.end(); const stdoutWithAnswers = await co.promise
Object.assign(res, {
stdout: stdoutWithAnswers,
})
}
if (needsStderrAnswers) {
ce.end(); const stderrWithAnswers = await ce.promise
Object.assign(res, {
stderr: stderrWithAnswers,
})
}
assertFork(res, props)
return res
}
const assertFork = ({ code, stdout, stderr }, props) => {
assertForkOutput(stdout, props.stdout)
assertForkOutput(stderr, props.stderr)
if (props.code && code != props.code)
throw new Error(`Fork exited with code ${code} != ${props.code}`)
}
export default run
/* documentary types/context.xml */
/**
* @typedef {Object} Context A context made with a constructor.
* @prop {() => void} [_init] A function to initialise the context.
* @prop {() => void} [_destroy] A function to destroy the context.
*/
/* documentary types/index.xml */
/**
* @typedef {import('child_process').ForkOptions} ForkOptions
*
* @typedef {Object} ForkConfig Parameters for forking.
* @prop {string} module The path to the module to fork.
* @prop {(args: string[], ...contexts?: Context[]) => string[]|Promise.<string[]>} [getArgs] The function to get arguments to pass the fork based on the parsed mask input and contexts.
* @prop {(...contexts?: Context[]) => ForkOptions} [getOptions] The function to get options for the fork, such as `ENV` and `cwd`, based on contexts.
* @prop {ForkOptions} [options] Options for the forked processed, such as `ENV` and `cwd`.
* @prop {[RegExp, string][]} [inputs] Inputs to push to `stdin` when `stdout` writes data. The inputs are kept on stack, and taken off the stack when the RegExp matches the written data.
* @prop {[RegExp, string][]} [stderrInputs] Inputs to push to `stdin` when `stderr` writes data (similar to `inputs`).
* @prop {boolean|{stderr: Writable, stdout: Writable}} [log=false] Whether to pipe data from `stdout`, `stderr` to the process's streams. If an object is passed, the output will be piped to streams specified as its `stdout` and `stderr` properties. Default `false`.
* @prop {boolean} [includeAnswers=true] Whether to add the answers to the `stderr` and `stdout` output. Default `true`.
*/
/* documentary types/run.xml */
/**
* @typedef {Object} Run Options for the run method.
* @prop {(string|ForkConfig)} forkConfig Either the config, or the path to the module to fork.
* @prop {string} input The input to the test from the test mask.
* @prop {*} [props="{}"] Other Properties Of The Test, Such As `stdout` And `stderr`. Default `{}`.
* @prop {Array<Context>} [contexts="[]"] The contexts for the test to be passed to `getArgs` and `getOptions`. Default `[]`.
*/
import mismatch from 'mismatch'
/**
* Return shell arguments from a string.
* @param {string} input
*/
const getArgs = (input) => {
const res = mismatch(/(['"])?([\s\S]+?)\1(\s+|$)/g, input, ['q', 'a'])
.map(({ a }) => a)
return res
}
export default getArgs
import { deepEqual } from 'assert-diff'
import erte from 'erte'
import { equal } from 'assert'
export const assertExpected = (result, expected) => {
try {
equal(result, expected)
} catch (err) {
const e = erte(result, expected)
console.log(e) // eslint-disable-line no-console
throw err
}
}
/**
* @param {string|ForkConfig} forkConfig Parameters for forking.
* @param {string} forkConfig.module The path to the module to fork.
* @param {(args: string[], ...contexts?: Context[]) => string[]|Promise.<string[]>} [forkConfig.getArgs] The function to get arguments to pass the forked processed based on parsed masks input and contexts.
* @param {(...contexts?: Context[]) => ForkOptions} [forkConfig.getOptions] The function to get options for the forked processed, such as `ENV` and `cwd`, based on contexts.
* @param {ForkOptions} [forkConfig.options] Options for the forked processed, such as `ENV` and `cwd`.
* @param {string[]} args
* @param {Context[]} contexts
* @param {*} props The props found in the mask.
*/
export const getForkArguments = async (forkConfig, args = [], context = [], props = {}) => {
/**
* @type {ForkOptions}
*/
const stdioOpts = {
stdio: 'pipe',
execArgv: [],
}
if (typeof forkConfig == 'string') {
return {
mod: forkConfig,
args,
options: stdioOpts,
}
}
const {
module: mod,
getArgs,
options,
getOptions,
} = forkConfig
const a = getArgs ? await getArgs.call(props, args, ...context) : args
let opt = stdioOpts
if (options) {
opt = {
...stdioOpts,
...options,
}
} else if (getOptions) {
const o = await getOptions.call(props, ...context)
opt = {
...stdioOpts,
...o,
}
}
return {
mod,
args: a,
options: opt,
}
}
export const assertForkOutput = (actual, expected) => {
if (typeof expected == 'string') {
assertExpected(actual, expected)
} else if (expected) {
const a = JSON.parse(actual)
deepEqual(a, expected)
}
}
/**
* @typedef {import('..').Context} Context
* @typedef {import('..').ForkOptions} ForkOptions
* @typedef {import('..').ForkConfig} ForkConfig
*/
<types>
<type name="Run" desc="Options for the run method.">
<prop type="(string|ForkConfig)" name="forkConfig">
Either the config, or the path to the module to fork.
</prop>
<prop string name="input">
The input to the test from the test mask to set on the `this.input` property of the `getArgs` and `getOptions`.
</prop>
<prop name="props" default="{}">
The properties to pass to the `getArgs` and `getOptions` as their this context.
</prop>
<prop type="Array<Context>" name="contexts" default="[]">
The contexts for the test to be passed to `getArgs` and `getOptions`.
</prop>
</type>
</types>
+26
-8

@@ -10,14 +10,23 @@ const { fork } = require('spawncommand');

* Run a fork.
* @param {{forkConfig: string|ForkConfig, input: string, props?: *, contexts?: Context[] }}
* @param {Run} config Options for the run method.
* @param {(string|ForkConfig)} config.forkConfig Either the config, or the path to the module to fork.
* @param {string} config.input The input to the test from the test mask.
* @param {*} [config.props="{}"] Other Properties Of The Test, Such As `stdout` And `stderr`. Default `{}`.
* @param {Array<Context>} [config.contexts="[]"] The contexts for the test to be passed to `getArgs` and `getOptions`. Default `[]`.
* @returns {Promise<{stdout: string, stderr: string, code: number}>} The result of the work, updated to contain answers in the interactive mode.
*/
const run = async ({
forkConfig,
input,
props = {},
contexts = [],
}) => {
const run = async (config) => {
const {
forkConfig,
input,
props = {},
contexts = [],
} = config
const a = input ? getArgs(input) : []
const {
mod, args, options,
} = await getForkArguments(forkConfig, a, contexts)
} = await getForkArguments(forkConfig, a, contexts, {
...props,
input,
})
const { promise, stdout, stdin, stderr } = fork(mod, args, options)

@@ -98,1 +107,10 @@

*/
/* documentary types/run.xml */
/**
* @typedef {Object} Run Options for the run method.
* @prop {(string|ForkConfig)} forkConfig Either the config, or the path to the module to fork.
* @prop {string} input The input to the test from the test mask.
* @prop {*} [props="{}"] Other Properties Of The Test, Such As `stdout` And `stderr`. Default `{}`.
* @prop {Array<Context>} [contexts="[]"] The contexts for the test to be passed to `getArgs` and `getOptions`. Default `[]`.
*/

@@ -23,4 +23,5 @@ const { deepEqual } = require('assert-diff');

* @param {Context[]} contexts
* @param {*} props The props found in the mask.
*/
const getForkArguments = async (forkConfig, args = [], context = []) => {
const getForkArguments = async (forkConfig, args = [], context = [], props = {}) => {
/**

@@ -46,3 +47,3 @@ * @type {ForkOptions}

} = forkConfig
const a = getArgs ? await getArgs(args, ...context) : args
const a = getArgs ? await getArgs.call(props, args, ...context) : args
let opt = stdioOpts

@@ -55,3 +56,3 @@ if (options) {

} else if (getOptions) {
const o = await getOptions(...context)
const o = await getOptions.call(props, ...context)
opt = {

@@ -58,0 +59,0 @@ ...stdioOpts,

@@ -0,1 +1,9 @@

## 16 February 2019
### 1.1.0
- [doc] Document example.
- [package] Export the module.
- [feature] Pass the `input` and other properties in the context.
## 24 October 2018

@@ -2,0 +10,0 @@

{
"name": "@zoroaster/fork",
"version": "1.0.0",
"version": "1.1.0",
"description": "Test forks.",
"main": "build",
"main": "build/index.js",
"module": "src/index.js",
"scripts": {

@@ -24,3 +25,4 @@ "t": "zoroaster -a",

"build",
"types"
"types",
"src"
],

@@ -45,13 +47,13 @@ "repository": {

"devDependencies": {
"alamode": "1.6.0",
"documentary": "1.20.1",
"alamode": "1.8.1",
"documentary": "1.21.2",
"eslint-config-artdeco": "1.0.1",
"reloquent": "1.2.3",
"reloquent": "1.2.4",
"yarn-s": "1.1.0",
"zoroaster": "3.6.2"
"zoroaster": "3.6.7"
},
"dependencies": {
"assert-diff": "2.0.3",
"catchment": "3.1.1",
"erte": "1.1.4",
"catchment": "3.2.2",
"erte": "1.1.6",
"forkfeed": "1.0.0",

@@ -58,0 +60,0 @@ "mismatch": "1.0.3",

@@ -50,2 +50,45 @@ # @zoroaster/fork

_For example, to test the fork with the next code:_
```js
const [,, ...args] = process.argv
console.log(args)
console.error(process.env.EXAMPLE)
process.exit(5)
```
_The ContextTesting/Fork can be used:_
```js
/* yarn example/ */
import fork from '@zoroaster/fork'
(async () => {
const res = await fork({
contexts: ['CONTEXT'],
forkConfig: {
module: 'example/fork',
getArgs(inputs) {
return [...inputs, this.prop1]
},
getOptions(CONTEXT) {
return {
env: {
EXAMPLE: `${CONTEXT} - ${this.input}`,
},
}
},
},
input: 'hello world',
props: {
prop1: '999',
},
})
console.log(res)
})()
```
```js
{ code: 5,
stdout: '[ \'hello\', \'world\', \'999\' ]\n',
stderr: 'CONTEXT - hello world\n' }
```
<p align="center"><a href="#table-of-contents"><img src=".documentary/section-breaks/2.svg?sanitize=true"></a></p>

@@ -55,6 +98,4 @@

(c) [Context Testing][1] 2018
(c) [Context Testing](https://contexttesting.com) 2019
[1]: https://contexttesting.com
<p align="center"><a href="#table-of-contents"><img src=".documentary/section-breaks/-1.svg?sanitize=true"></a></p>