Options
All
  • Public
  • Public/Protected
  • All
Menu

Package yarnpkg-shell

@yarnpkg/shell

A JavaScript implementation of a bash-like shell (we use it in Yarn 2 to provide cross-platform scripting). This package exposes an API that abstracts both the parser and the interpreter; should you only need the parser you can check out @yarnpkg/parsers, but you probably won't need it.

Usage

import {execute} from '@yarnpkg/shell';

process.exitCode = await execute(ls "$0" | wc -l, [process.cwd()]);

Features

  • Typechecked
  • Portable across systems
  • Supports custom JS builtins
  • Supports pipes
  • Supports glob patterns (only for files that exist on the filesystem: ls *.txt)
  • Supports logical operators
  • Supports subshells
  • Supports variables
  • Supports string manipulators
  • Supports argc/argv
  • Supports background jobs with color-coded output
  • Supports the most classic builtins
  • Doesn't necessarily need to access the fs

Help Wanted

  • Full glob support (mv build/{index.js,index.build.js}, echo {foo,bar}, FOO=a,b echo {$FOO,x})
  • More string manipulators

Non-Goals

  • Perfect POSIX compliance (basic scripting is enough for now)
  • Multiline scripts (we mostly target one-liners)
  • Control structures (same reason)

Type aliases

Glob

Glob: object

Type declaration

  • isGlobPattern: function
      • (pattern: string): boolean
      • Parameters

        • pattern: string

        Returns boolean

  • match: function
      • (pattern: string, options: object): Promise<Array<string>>

Glob

Glob: globUtils.Glob

ProcessImplementation

ProcessImplementation: function

Type declaration

    • Parameters

      Returns object

      • promise: Promise<number>
      • stdin: Writable

Result

Result: object

Type declaration

  • exitCode: number
  • stderr: string
  • stdout: string

ShellBuiltin

ShellBuiltin: function

Type declaration

ShellOptions

ShellOptions: object

Type declaration

  • args: Array<string>
  • baseFs: FakeFS<PortablePath>
  • builtins: Map<string, ShellBuiltin>
  • glob: globUtils.Glob
  • initialStderr: Writable
  • initialStdin: Readable
  • initialStdout: Writable

ShellState

ShellState: object

Type declaration

  • backgroundJobs: Array<Promise<unknown>>
  • cwd: PortablePath
  • environment: object
    • [key: string]: string
  • exitCode: number | null
  • nextBackgroundJobIndex: number
  • procedures: object
  • stderr: Writable
  • stdin: Readable
  • stdout: Writable
  • variables: object
    • [key: string]: string

StartOptions

StartOptions: object

Type declaration

Stdio

Stdio: []

UserOptions

UserOptions: object

Type declaration

  • baseFs: FakeFS<PortablePath>
  • builtins: object
  • cwd: PortablePath
  • env: object
    • [key: string]: string | undefined
  • glob: globUtils.Glob
  • stderr: Writable
  • stdin: Readable | null
  • stdout: Writable
  • variables: object
    • [key: string]: string

Variables

Const BUILTINS

BUILTINS: Map<string, function> = new Map<string, ShellBuiltin>([[`cd`, async ([target = homedir(), ...rest]: Array<string>, opts: ShellOptions, state: ShellState) => {const resolvedTarget = ppath.resolve(state.cwd, npath.toPortablePath(target));const stat = await opts.baseFs.statPromise(resolvedTarget).catch(error => {throw error.code === `ENOENT`? new ShellError(`cd: no such file or directory: ${target}`): error;});if (!stat.isDirectory())throw new ShellError(`cd: not a directory: ${target}`);state.cwd = resolvedTarget;return 0;}],[`pwd`, async (args: Array<string>, opts: ShellOptions, state: ShellState) => {state.stdout.write(`${npath.fromPortablePath(state.cwd)}\n`);return 0;}],[`:`, async (args: Array<string>, opts: ShellOptions, state: ShellState) => {return 0;}],[`true`, async (args: Array<string>, opts: ShellOptions, state: ShellState) => {return 0;}],[`false`, async (args: Array<string>, opts: ShellOptions, state: ShellState) => {return 1;}],[`exit`, async ([code, ...rest]: Array<string>, opts: ShellOptions, state: ShellState) => {return state.exitCode = parseInt(code ?? state.variables[`?`], 10);}],[`echo`, async (args: Array<string>, opts: ShellOptions, state: ShellState) => {state.stdout.write(`${args.join(` `)}\n`);return 0;}],[`sleep`, async ([time]: Array<string>, opts: ShellOptions, state: ShellState) => {if (typeof time === `undefined`)throw new ShellError(`sleep: missing operand`);// TODO: make it support unit suffixesconst seconds = Number(time);if (Number.isNaN(seconds))throw new ShellError(`sleep: invalid time interval '${time}'`);return await setTimeoutPromise(1000 * seconds, 0);}],[`__ysh_run_procedure`, async (args: Array<string>, opts: ShellOptions, state: ShellState) => {const procedure = state.procedures[args[0]];const exitCode = await start(procedure, {stdin: new ProtectedStream<Readable>(state.stdin),stdout: new ProtectedStream<Writable>(state.stdout),stderr: new ProtectedStream<Writable>(state.stderr),}).run();return exitCode;}],[`__ysh_set_redirects`, async (args: Array<string>, opts: ShellOptions, state: ShellState) => {let stdin = state.stdin;let stdout = state.stdout;let stderr = state.stderr;const inputs: Array<() => Readable> = [];const outputs: Array<Writable> = [];const errors: Array<Writable> = [];let t = 0;while (args[t] !== `--`) {const key = args[t++];const {type, fd} = JSON.parse(key);const pushInput = (readableFactory: () => Readable) => {switch (fd) {case null:case 0: {inputs.push(readableFactory);} break;default:throw new Error(`Unsupported file descriptor: "${fd}"`);}};const pushOutput = (writable: Writable) => {switch (fd) {case null:case 1: {outputs.push(writable);} break;case 2: {errors.push(writable);} break;default:throw new Error(`Unsupported file descriptor: "${fd}"`);}};const count = Number(args[t++]);const last = t + count;for (let u = t; u < last; ++t, ++u) {switch (type) {case `<`: {pushInput(() => {return opts.baseFs.createReadStream(ppath.resolve(state.cwd, npath.toPortablePath(args[u])));});} break;case `<<<`: {pushInput(() => {const input = new PassThrough();process.nextTick(() => {input.write(`${args[u]}\n`);input.end();});return input;});} break;case `<&`: {pushInput(() => getFileDescriptorStream(Number(args[u]), StreamType.Readable, state));} break;case `>`:case `>>`: {const outputPath = ppath.resolve(state.cwd, npath.toPortablePath(args[u]));if (outputPath === `/dev/null`) {pushOutput(new Writable({autoDestroy: true,emitClose: true,write(chunk, encoding, callback) {setImmediate(callback);},}),);} else {pushOutput(opts.baseFs.createWriteStream(outputPath, type === `>>` ? {flags: `a`} : undefined));}} break;case `>&`: {pushOutput(getFileDescriptorStream(Number(args[u]), StreamType.Writable, state));} break;default: {throw new Error(`Assertion failed: Unsupported redirection type: "${type}"`);}}}}if (inputs.length > 0) {const pipe = new PassThrough();stdin = pipe;const bindInput = (n: number) => {if (n === inputs.length) {pipe.end();} else {const input = inputs[n]();input.pipe(pipe, {end: false});input.on(`end`, () => {bindInput(n + 1);});}};bindInput(0);}if (outputs.length > 0) {const pipe = new PassThrough();stdout = pipe;for (const output of outputs) {pipe.pipe(output);}}if (errors.length > 0) {const pipe = new PassThrough();stderr = pipe;for (const error of errors) {pipe.pipe(error);}}const exitCode = await start(makeCommandAction(args.slice(t + 1), opts, state), {stdin: new ProtectedStream<Readable>(stdin),stdout: new ProtectedStream<Writable>(stdout),stderr: new ProtectedStream<Writable>(stderr),}).run();// Close all the outputs (since the shell never closes the output stream)await Promise.all(outputs.map(output => {// Wait until the output got flushed to the diskreturn new Promise<void>((resolve, reject) => {output.on(`error`, error => {reject(error);});output.on(`close`, () => {resolve();});output.end();});}));// Close all the errors (since the shell never closes the error stream)await Promise.all(errors.map(err => {// Wait until the error got flushed to the diskreturn new Promise<void>((resolve, reject) => {err.on(`error`, error => {reject(error);});err.on(`close`, () => {resolve();});err.end();});}));return exitCode;}],])

Const activeChildren

activeChildren: Set<ChildProcess> = new Set<ChildProcess>()

Const cli

cli: Cli<object> = new Cli({binaryLabel: `Yarn Shell`,binaryName: `yarn shell`,binaryVersion: require(`@yarnpkg/shell/package.json`).version || `<unknown>`,})

Const ifNotWin32It

ifNotWin32It: any = isNotWin32? it: it.skip

Const isNotWin32

isNotWin32: boolean = process.platform !== `win32`

Const setTimeoutPromise

setTimeoutPromise: setTimeout = promisify(setTimeout)

Const setTimeoutPromise

setTimeoutPromise: setTimeout = promisify(setTimeout)

Functions

applyEnvVariables

  • applyEnvVariables(environmentSegments: Array<EnvSegment>, opts: ShellOptions, state: ShellState): Promise<object>

Const bufferResult

  • bufferResult(command: string, args?: Array<string>, options?: Partial<UserOptions> & object): Promise<Result>

cloneState

createOutputStreamsWithPrefix

  • createOutputStreamsWithPrefix(state: ShellState, __namedParameters: object): object

createStreamReporter

  • createStreamReporter(reportFn: function, prefix?: string | null): PassThrough

evaluateArithmetic

  • evaluateArithmetic(arithmetic: ArithmeticExpression, opts: ShellOptions, state: ShellState): Promise<number>

evaluateVariable

  • evaluateVariable(segment: ArgumentSegment & object, opts: ShellOptions, state: ShellState, push: function, pushAndClose?: function): Promise<void>
  • Parameters

    • segment: ArgumentSegment & object
    • opts: ShellOptions
    • state: ShellState
    • push: function
        • (value: string): void
        • Parameters

          • value: string

          Returns void

    • Default value pushAndClose: function = push
        • (value: string): void
        • Parameters

          • value: string

          Returns void

    Returns Promise<void>

execute

  • execute(command: string, args?: Array<string>, __namedParameters?: object): Promise<number>
  • Parameters

    • command: string
    • Default value args: Array<string> = []
    • Default value __namedParameters: object = {}
      • baseFs: FakeFS<string & object>
      • builtins: object
      • cwd: string & object
      • env: object
        • [key: string]: string | undefined
      • glob: object
        • isGlobPattern: function
            • (pattern: string): boolean
            • Parameters

              • pattern: string

              Returns boolean

        • match: function
            • (pattern: string, options: object): Promise<Array<string>>
      • stderr: Writable
      • stdin: null | Readable
      • stdout: Writable
      • variables: object
        • [key: string]: string

    Returns Promise<number>

executeBufferedSubshell

executeCommandChain

  • executeCommandChain(node: CommandChain, opts: ShellOptions, state: ShellState, __namedParameters?: object): Promise<number>

executeCommandChainImpl

executeCommandLine

  • executeCommandLine(node: CommandLine, opts: ShellOptions, state: ShellState, __namedParameters?: object): Promise<number>
  • Execute a command line. A command line is a list of command shells linked together thanks to the use of either of the || or && operators.

    Parameters

    • node: CommandLine
    • opts: ShellOptions
    • state: ShellState
    • Default value __namedParameters: object = {}
      • background: boolean

    Returns Promise<number>

executeShellLine

Const expectResult

  • expectResult(promise: Promise<any>, __namedParameters: object): Promise<any>

getFileDescriptorStream

interpolateArguments

  • interpolateArguments(commandArgs: Array<Argument>, opts: ShellOptions, state: ShellState): Promise<string[]>

isBraceExpansion

  • isBraceExpansion(pattern: string): boolean

isGlobPattern

  • isGlobPattern(pattern: string): boolean
  • Decides whether a string is a glob pattern, using micromatch.

    Required because fastGlob.isDynamicPattern doesn't have the strictBrackets option.

    Required because fastGlob.isDynamicPattern doesn't have the strictBrackets option.

    Parameters

    • pattern: string

    Returns boolean

locateArgsVariable

  • locateArgsVariable(node: ShellLine): boolean

locateArgsVariableInArgument

  • locateArgsVariableInArgument(arg: Argument): boolean

locateArgsVariableInArithmetic

  • locateArgsVariableInArithmetic(arg: ArithmeticExpression): boolean

locateArgsVariableInSegment

  • locateArgsVariableInSegment(segment: ArgumentSegment | ArithmeticPrimary): boolean

makeActionFromProcedure

makeBuiltin

makeCommandAction

  • Executes a command chain. A command chain is a list of commands linked together thanks to the use of either of the | or |& operators:

    $ cat hello | grep world | grep -v foobar

    $ cat hello | grep world | grep -v foobar

    Parameters

    Returns function

      • Parameters

        Returns object

        • promise: Promise<number>
        • stdin: Writable

makeGroupAction

makeProcess

makeSubshellAction

match

  • match(pattern: string, __namedParameters: object): Promise<string[]>

sigintHandler

  • sigintHandler(): void

sigtermHandler

  • sigtermHandler(): void

split

  • split(raw: string): RegExpMatchArray | []

start

Object literals

Const fastGlobOptions

fastGlobOptions: object

onlyDirectories

onlyDirectories: false = false

onlyFiles

onlyFiles: false = false

Const micromatchOptions

micromatchOptions: object

strictBrackets

strictBrackets: true = true

Const operators

operators: object

addition

  • addition(left: number, right: number): number

division

  • division(left: number, right: number): number

multiplication

  • multiplication(left: number, right: number): number

subtraction

  • subtraction(left: number, right: number): number

Generated using TypeDoc