@yarnpkg/shell
Advanced tools
Comparing version 2.1.0 to 2.2.0
322
lib/index.js
@@ -8,2 +8,3 @@ "use strict"; | ||
const fast_glob_1 = tslib_1.__importDefault(require("fast-glob")); | ||
const os_1 = require("os"); | ||
const stream_1 = require("stream"); | ||
@@ -19,3 +20,3 @@ const pipe_1 = require("./pipe"); | ||
const BUILTINS = new Map([ | ||
[`cd`, async ([target, ...rest], opts, state) => { | ||
[`cd`, async ([target = os_1.homedir(), ...rest], opts, state) => { | ||
const resolvedTarget = fslib_1.ppath.resolve(state.cwd, fslib_1.npath.toPortablePath(target)); | ||
@@ -43,3 +44,3 @@ const stat = await fslib_1.xfs.statPromise(resolvedTarget); | ||
[`exit`, async ([code, ...rest], opts, state) => { | ||
return state.exitCode = parseInt(code, 10); | ||
return state.exitCode = parseInt(code !== null && code !== void 0 ? code : state.variables[`?`], 10); | ||
}], | ||
@@ -167,2 +168,106 @@ [`echo`, async (args, opts, state) => { | ||
} | ||
function split(raw) { | ||
return raw.match(/[^ \r\n\t]+/g) || []; | ||
} | ||
async function evaluateVariable(segment, opts, state, push, pushAndClose = push) { | ||
switch (segment.name) { | ||
case `#`: | ||
{ | ||
push(String(opts.args.length)); | ||
} | ||
break; | ||
case `@`: | ||
{ | ||
if (segment.quoted) { | ||
for (const raw of opts.args) { | ||
pushAndClose(raw); | ||
} | ||
} | ||
else { | ||
for (const raw of opts.args) { | ||
const parts = split(raw); | ||
for (let t = 0; t < parts.length - 1; ++t) | ||
pushAndClose(parts[t]); | ||
push(parts[parts.length - 1]); | ||
} | ||
} | ||
} | ||
break; | ||
case `*`: | ||
{ | ||
const raw = opts.args.join(` `); | ||
if (segment.quoted) { | ||
push(raw); | ||
} | ||
else { | ||
for (const part of split(raw)) { | ||
pushAndClose(part); | ||
} | ||
} | ||
} | ||
break; | ||
case `RANDOM`: | ||
{ | ||
push(String(Math.floor(Math.random() * 32768))); | ||
} | ||
break; | ||
default: | ||
{ | ||
const argIndex = parseInt(segment.name, 10); | ||
if (Number.isFinite(argIndex)) { | ||
if (!(argIndex >= 0 && argIndex < opts.args.length)) { | ||
throw new Error(`Unbound argument #${argIndex}`); | ||
} | ||
else { | ||
push(opts.args[argIndex]); | ||
} | ||
} | ||
else { | ||
if (Object.prototype.hasOwnProperty.call(state.variables, segment.name)) { | ||
push(state.variables[segment.name]); | ||
} | ||
else if (Object.prototype.hasOwnProperty.call(state.environment, segment.name)) { | ||
push(state.environment[segment.name]); | ||
} | ||
else if (segment.defaultValue) { | ||
push((await interpolateArguments(segment.defaultValue, opts, state)).join(` `)); | ||
} | ||
else { | ||
throw new Error(`Unbound variable "${segment.name}"`); | ||
} | ||
} | ||
} | ||
break; | ||
} | ||
} | ||
const operators = { | ||
addition: (left, right) => left + right, | ||
subtraction: (left, right) => left - right, | ||
multiplication: (left, right) => left * right, | ||
division: (left, right) => Math.trunc(left / right), | ||
}; | ||
async function evaluateArithmetic(arithmetic, opts, state) { | ||
if (arithmetic.type === `number`) { | ||
if (!Number.isInteger(arithmetic.value)) { | ||
throw new Error(`Invalid number: "${arithmetic.value}", only integers are allowed`); | ||
} | ||
else { | ||
return arithmetic.value; | ||
} | ||
} | ||
else if (arithmetic.type === `variable`) { | ||
const parts = []; | ||
await evaluateVariable({ ...arithmetic, quoted: true }, opts, state, result => parts.push(result)); | ||
const number = Number(parts.join(` `)); | ||
if (Number.isNaN(number)) { | ||
return evaluateArithmetic({ type: `variable`, name: parts.join(` `) }, opts, state); | ||
} | ||
else { | ||
return evaluateArithmetic({ type: `number`, value: number }, opts, state); | ||
} | ||
} | ||
else { | ||
return operators[arithmetic.type](await evaluateArithmetic(arithmetic.left, opts, state), await evaluateArithmetic(arithmetic.right, opts, state)); | ||
} | ||
} | ||
async function interpolateArguments(commandArgs, opts, state) { | ||
@@ -172,5 +277,2 @@ const redirections = new Map(); | ||
let interpolatedSegments = []; | ||
const split = (raw) => { | ||
return raw.match(/[^ \r\n\t]+/g) || []; | ||
}; | ||
const push = (segment) => { | ||
@@ -239,68 +341,10 @@ interpolatedSegments.push(segment); | ||
{ | ||
switch (segment.name) { | ||
case `#`: | ||
{ | ||
push(String(opts.args.length)); | ||
} | ||
break; | ||
case `@`: | ||
{ | ||
if (segment.quoted) { | ||
for (const raw of opts.args) { | ||
pushAndClose(raw); | ||
} | ||
} | ||
else { | ||
for (const raw of opts.args) { | ||
const parts = split(raw); | ||
for (let t = 0; t < parts.length - 1; ++t) | ||
pushAndClose(parts[t]); | ||
push(parts[parts.length - 1]); | ||
} | ||
} | ||
} | ||
break; | ||
case `*`: | ||
{ | ||
const raw = opts.args.join(` `); | ||
if (segment.quoted) { | ||
push(raw); | ||
} | ||
else { | ||
for (const part of split(raw)) { | ||
pushAndClose(part); | ||
} | ||
} | ||
} | ||
break; | ||
default: | ||
{ | ||
const argIndex = parseInt(segment.name, 10); | ||
if (Number.isFinite(argIndex)) { | ||
if (!(argIndex >= 0 && argIndex < opts.args.length)) { | ||
throw new Error(`Unbound argument #${argIndex}`); | ||
} | ||
else { | ||
push(opts.args[argIndex]); | ||
} | ||
} | ||
else { | ||
if (Object.prototype.hasOwnProperty.call(state.variables, segment.name)) { | ||
push(state.variables[segment.name]); | ||
} | ||
else if (Object.prototype.hasOwnProperty.call(state.environment, segment.name)) { | ||
push(state.environment[segment.name]); | ||
} | ||
else if (segment.defaultValue) { | ||
push((await interpolateArguments(segment.defaultValue, opts, state)).join(` `)); | ||
} | ||
else { | ||
throw new Error(`Unbound variable "${segment.name}"`); | ||
} | ||
} | ||
} | ||
break; | ||
} | ||
await evaluateVariable(segment, opts, state, push, pushAndClose); | ||
} | ||
break; | ||
case `arithmetic`: | ||
{ | ||
push(String(await evaluateArithmetic(segment.arithmetic, opts, state))); | ||
} | ||
break; | ||
} | ||
@@ -358,2 +402,23 @@ } | ||
} | ||
function makeGroupAction(ast, opts, state) { | ||
return (stdio) => { | ||
const stdin = new stream_1.PassThrough(); | ||
const promise = executeShellLine(ast, opts, state); | ||
return { stdin, promise }; | ||
}; | ||
} | ||
function makeActionFromProcedure(procedure, args, opts, activeState) { | ||
if (args.length === 0) { | ||
return procedure; | ||
} | ||
else { | ||
let key; | ||
do { | ||
key = String(Math.random()); | ||
} while (Object.prototype.hasOwnProperty.call(activeState.procedures, key)); | ||
activeState.procedures = { ...activeState.procedures }; | ||
activeState.procedures[key] = procedure; | ||
return makeCommandAction([...args, `__ysh_run_procedure`, key], opts, activeState); | ||
} | ||
} | ||
async function executeCommandChain(node, opts, state) { | ||
@@ -386,16 +451,12 @@ let current = node; | ||
const procedure = makeSubshellAction(current.subshell, opts, activeState); | ||
if (args.length === 0) { | ||
action = procedure; | ||
} | ||
else { | ||
let key; | ||
do { | ||
key = String(Math.random()); | ||
} while (Object.prototype.hasOwnProperty.call(activeState.procedures, key)); | ||
activeState.procedures = { ...activeState.procedures }; | ||
activeState.procedures[key] = procedure; | ||
action = makeCommandAction([...args, `__ysh_run_procedure`, key], opts, activeState); | ||
} | ||
action = makeActionFromProcedure(procedure, args, opts, activeState); | ||
} | ||
break; | ||
case `group`: | ||
{ | ||
const args = await interpolateArguments(current.args, opts, state); | ||
const procedure = makeGroupAction(current.group, opts, activeState); | ||
action = makeActionFromProcedure(procedure, args, opts, activeState); | ||
} | ||
break; | ||
case `envs`: | ||
@@ -428,3 +489,3 @@ { | ||
{ | ||
execution = execution.pipeTo(action); | ||
execution = execution.pipeTo(action, pipe_2.Pipe.STDOUT); | ||
} | ||
@@ -434,3 +495,3 @@ break; | ||
{ | ||
execution = execution.pipeTo(action); | ||
execution = execution.pipeTo(action, pipe_2.Pipe.STDOUT | pipe_2.Pipe.STDERR); | ||
} | ||
@@ -457,38 +518,42 @@ break; | ||
async function executeCommandLine(node, opts, state) { | ||
if (!node.then) | ||
return await executeCommandChain(node.chain, opts, state); | ||
const code = await executeCommandChain(node.chain, opts, state); | ||
// If the execution aborted (usually through "exit"), we must bailout | ||
if (state.exitCode !== null) | ||
return state.exitCode; | ||
// We must update $?, which always contains the exit code from | ||
// the right-most command | ||
state.variables[`?`] = String(code); | ||
switch (node.then.type) { | ||
case `&&`: | ||
{ | ||
if (code === 0) { | ||
return await executeCommandLine(node.then.line, opts, state); | ||
let code; | ||
const setCode = (newCode) => { | ||
code = newCode; | ||
// We must update $?, which always contains the exit code from | ||
// the right-most command | ||
state.variables[`?`] = String(newCode); | ||
}; | ||
setCode(await executeCommandChain(node.chain, opts, state)); | ||
// We use a loop because we must make sure that we respect | ||
// the left associativity of lists, as per the bash spec. | ||
// (e.g. `inexistent && echo yes || echo no` must be | ||
// the same as `{inexistent && echo yes} || echo no`) | ||
while (node.then) { | ||
// If the execution aborted (usually through "exit"), we must bailout | ||
if (state.exitCode !== null) | ||
return state.exitCode; | ||
switch (node.then.type) { | ||
case `&&`: | ||
{ | ||
if (code === 0) { | ||
setCode(await executeCommandChain(node.then.line.chain, opts, state)); | ||
} | ||
} | ||
else { | ||
return code; | ||
break; | ||
case `||`: | ||
{ | ||
if (code !== 0) { | ||
setCode(await executeCommandChain(node.then.line.chain, opts, state)); | ||
} | ||
} | ||
} | ||
break; | ||
case `||`: | ||
{ | ||
if (code !== 0) { | ||
return await executeCommandLine(node.then.line, opts, state); | ||
break; | ||
default: | ||
{ | ||
throw new Error(`Unsupported command type: "${node.then.type}"`); | ||
} | ||
else { | ||
return code; | ||
} | ||
} | ||
break; | ||
default: | ||
{ | ||
throw new Error(`Unsupported command type: "${node.then.type}"`); | ||
} | ||
break; | ||
break; | ||
} | ||
node = node.then.line; | ||
} | ||
return code; | ||
} | ||
@@ -512,5 +577,10 @@ async function executeShellLine(node, opts, state) { | ||
{ | ||
return segment.name === `@` || segment.name === `#` || segment.name === `*` || Number.isFinite(parseInt(segment.name, 10)) || (!!segment.defaultValue && segment.defaultValue.some(arg => locateArgsVariableInArgument(arg))); | ||
return segment.name === `@` || segment.name === `#` || segment.name === `*` || Number.isFinite(parseInt(segment.name, 10)) || (`defaultValue` in segment && !!segment.defaultValue && segment.defaultValue.some(arg => locateArgsVariableInArgument(arg))); | ||
} | ||
break; | ||
case `arithmetic`: | ||
{ | ||
return locateArgsVariableInArithmetic(segment.arithmetic); | ||
} | ||
break; | ||
case `shell`: | ||
@@ -544,2 +614,18 @@ { | ||
} | ||
function locateArgsVariableInArithmetic(arg) { | ||
switch (arg.type) { | ||
case `variable`: | ||
{ | ||
return locateArgsVariableInSegment(arg); | ||
} | ||
break; | ||
case `number`: | ||
{ | ||
return false; | ||
} | ||
break; | ||
default: | ||
return locateArgsVariableInArithmetic(arg.left) || locateArgsVariableInArithmetic(arg.right); | ||
} | ||
} | ||
function locateArgsVariable(node) { | ||
@@ -584,3 +670,3 @@ return node.some(command => { | ||
cwd: fslib_1.npath.fromPortablePath(cwd), | ||
// @ts-ignore: `fs` is wrapped in `PosixFS` | ||
// @ts-expect-error: `fs` is wrapped in `PosixFS` | ||
fs: new fslib_1.PosixFS(fs), | ||
@@ -638,3 +724,3 @@ }), | ||
stderr, | ||
variables: Object.assign(Object.create(variables), { | ||
variables: Object.assign({}, variables, { | ||
[`?`]: 0, | ||
@@ -641,0 +727,0 @@ }), |
/// <reference types="node" /> | ||
import { Readable, Writable } from 'stream'; | ||
import { ShellOptions } from './index'; | ||
declare enum Pipe { | ||
export declare enum Pipe { | ||
STDOUT = 1, | ||
@@ -6,0 +6,0 @@ STDERR = 2 |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.start = exports.Handle = exports.ProtectedStream = exports.makeBuiltin = exports.makeProcess = void 0; | ||
exports.start = exports.Handle = exports.ProtectedStream = exports.makeBuiltin = exports.makeProcess = exports.Pipe = void 0; | ||
const tslib_1 = require("tslib"); | ||
@@ -11,3 +11,3 @@ const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn")); | ||
Pipe[Pipe["STDERR"] = 2] = "STDERR"; | ||
})(Pipe || (Pipe = {})); | ||
})(Pipe = exports.Pipe || (exports.Pipe = {})); | ||
function sigintHandler() { | ||
@@ -51,3 +51,3 @@ // We don't want SIGINT to kill our process; we want it to kill the | ||
process.off(`SIGINT`, sigintHandler); | ||
// @ts-ignore | ||
// @ts-expect-error | ||
switch (error.code) { | ||
@@ -54,0 +54,0 @@ case `ENOENT`: |
{ | ||
"name": "@yarnpkg/shell", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"license": "BSD-2-Clause", | ||
@@ -8,5 +8,5 @@ "main": "./lib/index.js", | ||
"dependencies": { | ||
"@yarnpkg/fslib": "^2.1.0", | ||
"@yarnpkg/parsers": "^2.1.0", | ||
"clipanion": "^2.4.2", | ||
"@yarnpkg/fslib": "^2.2.0", | ||
"@yarnpkg/parsers": "^2.2.0", | ||
"clipanion": "^2.4.4", | ||
"cross-spawn": "7.0.3", | ||
@@ -13,0 +13,0 @@ "fast-glob": "^3.2.2", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
45190
1171
Updated@yarnpkg/fslib@^2.2.0
Updated@yarnpkg/parsers@^2.2.0
Updatedclipanion@^2.4.4