@yarnpkg/shell
Advanced tools
Comparing version 3.0.0-rc.2 to 3.0.0-rc.3
@@ -6,3 +6,3 @@ #!/usr/bin/env node | ||
const clipanion_1 = require("clipanion"); | ||
const entry_1 = (0, tslib_1.__importDefault)(require("./commands/entry")); | ||
const entry_1 = tslib_1.__importDefault(require("./commands/entry")); | ||
const cli = new clipanion_1.Cli({ | ||
@@ -9,0 +9,0 @@ binaryLabel: `Yarn Shell`, |
@@ -22,3 +22,3 @@ "use strict"; | ||
: this.commandName; | ||
return await (0, index_1.execute)(command, [], { | ||
return await index_1.execute(command, [], { | ||
cwd: fslib_1.npath.toPortablePath(this.cwd), | ||
@@ -25,0 +25,0 @@ stdin: this.context.stdin, |
@@ -6,5 +6,5 @@ "use strict"; | ||
const fslib_1 = require("@yarnpkg/fslib"); | ||
const fast_glob_1 = (0, tslib_1.__importDefault)(require("fast-glob")); | ||
const fs_1 = (0, tslib_1.__importDefault)(require("fs")); | ||
const micromatch_1 = (0, tslib_1.__importDefault)(require("micromatch")); | ||
const fast_glob_1 = tslib_1.__importDefault(require("fast-glob")); | ||
const fs_1 = tslib_1.__importDefault(require("fs")); | ||
const micromatch_1 = tslib_1.__importDefault(require("micromatch")); | ||
exports.micromatchOptions = { | ||
@@ -38,6 +38,6 @@ // This is required because we don't want ")/*" to be a valid shell glob pattern. | ||
function match(pattern, { cwd, baseFs }) { | ||
return (0, fast_glob_1.default)(pattern, { | ||
return fast_glob_1.default(pattern, { | ||
...exports.fastGlobOptions, | ||
cwd: fslib_1.npath.fromPortablePath(cwd), | ||
fs: (0, fslib_1.extendFs)(fs_1.default, new fslib_1.PosixFS(baseFs)), | ||
fs: fslib_1.extendFs(fs_1.default, new fslib_1.PosixFS(baseFs)), | ||
}); | ||
@@ -44,0 +44,0 @@ } |
@@ -51,3 +51,5 @@ /// <reference types="node" /> | ||
}; | ||
nextBackgroundJobIndex: number; | ||
backgroundJobs: Array<Promise<unknown>>; | ||
}; | ||
export declare function execute(command: string, args?: Array<string>, { baseFs, builtins, cwd, env, stdin, stdout, stderr, variables, glob, }?: Partial<UserOptions>): Promise<number>; |
@@ -7,10 +7,13 @@ "use strict"; | ||
const parsers_1 = require("@yarnpkg/parsers"); | ||
const chalk_1 = tslib_1.__importDefault(require("chalk")); | ||
const os_1 = require("os"); | ||
const stream_1 = require("stream"); | ||
const util_1 = require("util"); | ||
const errors_1 = require("./errors"); | ||
Object.defineProperty(exports, "ShellError", { enumerable: true, get: function () { return errors_1.ShellError; } }); | ||
const globUtils = (0, tslib_1.__importStar)(require("./globUtils")); | ||
const globUtils = tslib_1.__importStar(require("./globUtils")); | ||
exports.globUtils = globUtils; | ||
const pipe_1 = require("./pipe"); | ||
const pipe_2 = require("./pipe"); | ||
const setTimeoutPromise = util_1.promisify(setTimeout); | ||
var StreamType; | ||
@@ -64,3 +67,3 @@ (function (StreamType) { | ||
const BUILTINS = new Map([ | ||
[`cd`, async ([target = (0, os_1.homedir)(), ...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)); | ||
@@ -97,5 +100,18 @@ const stat = await opts.baseFs.statPromise(resolvedTarget); | ||
}], | ||
[`sleep`, async ([time], opts, state) => { | ||
if (typeof time === `undefined`) { | ||
state.stderr.write(`sleep: missing operand\n`); | ||
return 1; | ||
} | ||
// TODO: make it support unit suffixes | ||
const seconds = Number(time); | ||
if (Number.isNaN(seconds)) { | ||
state.stderr.write(`sleep: invalid time interval '${time}'\n`); | ||
return 1; | ||
} | ||
return await setTimeoutPromise(1000 * seconds, 0); | ||
}], | ||
[`__ysh_run_procedure`, async (args, opts, state) => { | ||
const procedure = state.procedures[args[0]]; | ||
const exitCode = await (0, pipe_2.start)(procedure, { | ||
const exitCode = await pipe_2.start(procedure, { | ||
stdin: new pipe_2.ProtectedStream(state.stdin), | ||
@@ -197,3 +213,3 @@ stdout: new pipe_2.ProtectedStream(state.stdout), | ||
} | ||
const exitCode = await (0, pipe_2.start)(makeCommandAction(args.slice(t + 1), opts, state), { | ||
const exitCode = await pipe_2.start(makeCommandAction(args.slice(t + 1), opts, state), { | ||
stdin: new pipe_2.ProtectedStream(stdin), | ||
@@ -206,3 +222,6 @@ stdout: new pipe_2.ProtectedStream(stdout), | ||
// Wait until the output got flushed to the disk | ||
return new Promise(resolve => { | ||
return new Promise((resolve, reject) => { | ||
output.on(`error`, error => { | ||
reject(error); | ||
}); | ||
output.on(`close`, () => { | ||
@@ -473,3 +492,3 @@ resolve(); | ||
if (name === `command`) { | ||
return (0, pipe_1.makeProcess)(rest[0], rest.slice(1), opts, { | ||
return pipe_1.makeProcess(rest[0], rest.slice(1), opts, { | ||
cwd: nativeCwd, | ||
@@ -482,3 +501,3 @@ env, | ||
throw new Error(`Assertion failed: A builtin should exist for "${name}"`); | ||
return (0, pipe_1.makeBuiltin)(async ({ stdin, stdout, stderr }) => { | ||
return pipe_1.makeBuiltin(async ({ stdin, stdout, stderr }) => { | ||
state.stdin = stdin; | ||
@@ -518,3 +537,3 @@ state.stdout = stdout; | ||
} | ||
async function executeCommandChain(node, opts, state) { | ||
async function executeCommandChainImpl(node, opts, state) { | ||
let current = node; | ||
@@ -569,3 +588,3 @@ let pipeType = null; | ||
// new execution pipeline | ||
execution = (0, pipe_2.start)(action, { | ||
execution = pipe_2.start(action, { | ||
stdin: new pipe_2.ProtectedStream(activeState.stdin), | ||
@@ -606,2 +625,25 @@ stdout: new pipe_2.ProtectedStream(activeState.stdout), | ||
} | ||
async function executeCommandChain(node, opts, state, { background = false } = {}) { | ||
function getColorizer(index) { | ||
const colors = [`#2E86AB`, `#A23B72`, `#F18F01`, `#C73E1D`, `#CCE2A3`]; | ||
const colorName = colors[index % colors.length]; | ||
return chalk_1.default.hex(colorName); | ||
} | ||
if (background) { | ||
const index = state.nextBackgroundJobIndex++; | ||
const colorizer = getColorizer(index); | ||
const rawPrefix = `[${index}]`; | ||
const prefix = colorizer(rawPrefix); | ||
const { stdout, stderr } = pipe_1.createOutputStreamsWithPrefix(state, { prefix }); | ||
state.backgroundJobs.push(executeCommandChainImpl(node, opts, cloneState(state, { stdout, stderr })) | ||
.catch(error => stderr.write(`${error.message}\n`)) | ||
.finally(() => { | ||
if (state.stdout.isTTY) { | ||
state.stdout.write(`Job ${prefix}, '${colorizer(parsers_1.stringifyCommandChain(node))}' has ended\n`); | ||
} | ||
})); | ||
return 0; | ||
} | ||
return await executeCommandChainImpl(node, opts, state); | ||
} | ||
/** | ||
@@ -611,3 +653,3 @@ * Execute a command line. A command line is a list of command shells linked | ||
*/ | ||
async function executeCommandLine(node, opts, state) { | ||
async function executeCommandLine(node, opts, state, { background = false } = {}) { | ||
let code; | ||
@@ -620,5 +662,5 @@ const setCode = (newCode) => { | ||
}; | ||
const executeChain = async (chain) => { | ||
const executeChain = async (line) => { | ||
try { | ||
return await executeCommandChain(chain, opts, state); | ||
return await executeCommandChain(line.chain, opts, state, { background: background && typeof line.then === `undefined` }); | ||
} | ||
@@ -632,3 +674,3 @@ catch (error) { | ||
}; | ||
setCode(await executeChain(node.chain)); | ||
setCode(await executeChain(node)); | ||
// We use a loop because we must make sure that we respect | ||
@@ -646,3 +688,3 @@ // the left associativity of lists, as per the bash spec. | ||
if (code === 0) { | ||
setCode(await executeChain(node.then.line.chain)); | ||
setCode(await executeChain(node.then.line)); | ||
} | ||
@@ -654,3 +696,3 @@ } | ||
if (code !== 0) { | ||
setCode(await executeChain(node.then.line.chain)); | ||
setCode(await executeChain(node.then.line)); | ||
} | ||
@@ -670,5 +712,7 @@ } | ||
async function executeShellLine(node, opts, state) { | ||
const originalBackgroundJobs = state.backgroundJobs; | ||
state.backgroundJobs = []; | ||
let rightMostExitCode = 0; | ||
for (const command of node) { | ||
rightMostExitCode = await executeCommandLine(command, opts, state); | ||
for (const { command, type } of node) { | ||
rightMostExitCode = await executeCommandLine(command, opts, state, { background: type === `&` }); | ||
// If the execution aborted (usually through "exit"), we must bailout | ||
@@ -681,2 +725,4 @@ if (state.exitCode !== null) | ||
} | ||
await Promise.all(state.backgroundJobs); | ||
state.backgroundJobs = originalBackgroundJobs; | ||
return rightMostExitCode; | ||
@@ -741,3 +787,3 @@ } | ||
function locateArgsVariable(node) { | ||
return node.some(command => { | ||
return node.some(({ command }) => { | ||
while (command) { | ||
@@ -789,7 +835,7 @@ let chain = command.chain; | ||
} | ||
const ast = (0, parsers_1.parseShell)(command, glob); | ||
const ast = parsers_1.parseShell(command, glob); | ||
// If the shell line doesn't use the args, inject it at the end of the | ||
// right-most command | ||
if (!locateArgsVariable(ast) && ast.length > 0 && args.length > 0) { | ||
let command = ast[ast.length - 1]; | ||
let { command } = ast[ast.length - 1]; | ||
while (command.then) | ||
@@ -831,4 +877,6 @@ command = command.then.line; | ||
}), | ||
nextBackgroundJobIndex: 1, | ||
backgroundJobs: [], | ||
}); | ||
} | ||
exports.execute = execute; |
/// <reference types="node" /> | ||
import { Readable, Writable } from 'stream'; | ||
import { ShellOptions } from './index'; | ||
import { PassThrough, Readable, Writable } from 'stream'; | ||
import { ShellOptions, ShellState } from './index'; | ||
export declare enum Pipe { | ||
@@ -49,2 +49,8 @@ STDIN = 0, | ||
export declare function start(p: ProcessImplementation, opts: StartOptions): Handle; | ||
export declare function createOutputStreamsWithPrefix(state: ShellState, { prefix }: { | ||
prefix: string | null; | ||
}): { | ||
stdout: PassThrough; | ||
stderr: PassThrough; | ||
}; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.start = exports.Handle = exports.ProtectedStream = exports.makeBuiltin = exports.makeProcess = exports.Pipe = void 0; | ||
exports.createOutputStreamsWithPrefix = exports.start = exports.Handle = exports.ProtectedStream = exports.makeBuiltin = exports.makeProcess = exports.Pipe = void 0; | ||
const tslib_1 = require("tslib"); | ||
const cross_spawn_1 = (0, tslib_1.__importDefault)(require("cross-spawn")); | ||
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn")); | ||
const stream_1 = require("stream"); | ||
const string_decoder_1 = require("string_decoder"); | ||
var Pipe; | ||
@@ -34,3 +35,3 @@ (function (Pipe) { | ||
: stdio[2]; | ||
const child = (0, cross_spawn_1.default)(name, args, { ...spawnOpts, stdio: [ | ||
const child = cross_spawn_1.default(name, args, { ...spawnOpts, stdio: [ | ||
stdin, | ||
@@ -239,1 +240,44 @@ stdout, | ||
exports.start = start; | ||
function createStreamReporter(reportFn, prefix = null) { | ||
const stream = new stream_1.PassThrough(); | ||
const decoder = new string_decoder_1.StringDecoder(); | ||
let buffer = ``; | ||
stream.on(`data`, chunk => { | ||
let chunkStr = decoder.write(chunk); | ||
let lineIndex; | ||
do { | ||
lineIndex = chunkStr.indexOf(`\n`); | ||
if (lineIndex !== -1) { | ||
const line = buffer + chunkStr.substr(0, lineIndex); | ||
chunkStr = chunkStr.substr(lineIndex + 1); | ||
buffer = ``; | ||
if (prefix !== null) { | ||
reportFn(`${prefix} ${line}`); | ||
} | ||
else { | ||
reportFn(line); | ||
} | ||
} | ||
} while (lineIndex !== -1); | ||
buffer += chunkStr; | ||
}); | ||
stream.on(`end`, () => { | ||
const last = decoder.end(); | ||
if (last !== ``) { | ||
if (prefix !== null) { | ||
reportFn(`${prefix} ${last}`); | ||
} | ||
else { | ||
reportFn(last); | ||
} | ||
} | ||
}); | ||
return stream; | ||
} | ||
function createOutputStreamsWithPrefix(state, { prefix }) { | ||
return { | ||
stdout: createStreamReporter(text => state.stdout.write(`${text}\n`), state.stdout.isTTY ? prefix : null), | ||
stderr: createStreamReporter(text => state.stderr.write(`${text}\n`), state.stderr.isTTY ? prefix : null), | ||
}; | ||
} | ||
exports.createOutputStreamsWithPrefix = createOutputStreamsWithPrefix; |
{ | ||
"name": "@yarnpkg/shell", | ||
"version": "3.0.0-rc.2", | ||
"version": "3.0.0-rc.3", | ||
"license": "BSD-2-Clause", | ||
@@ -8,4 +8,5 @@ "main": "./lib/index.js", | ||
"dependencies": { | ||
"@yarnpkg/fslib": "^2.5.0-rc.2", | ||
"@yarnpkg/parsers": "^2.3.1-rc.2", | ||
"@yarnpkg/fslib": "^2.5.0-rc.3", | ||
"@yarnpkg/parsers": "^2.4.0-rc.1", | ||
"chalk": "^3.0.0", | ||
"clipanion": "^3.0.0-rc.10", | ||
@@ -21,3 +22,4 @@ "cross-spawn": "7.0.3", | ||
"@types/micromatch": "^4.0.1", | ||
"@yarnpkg/monorepo": "0.0.0" | ||
"@yarnpkg/monorepo": "0.0.0", | ||
"strip-ansi": "^6.0.0" | ||
}, | ||
@@ -44,3 +46,3 @@ "scripts": { | ||
"engines": { | ||
"node": ">=10.19.0" | ||
"node": ">=12 <14 || 14.2 - 14.9 || >14.10.0" | ||
}, | ||
@@ -47,0 +49,0 @@ "stableVersion": "2.4.1", |
@@ -10,3 +10,3 @@ # `@yarnpkg/shell` | ||
process.exitCode = await execute(`ls "$1" | wc -l`, [process.cwd()]); | ||
process.exitCode = await execute(`ls "$0" | wc -l`, [process.cwd()]); | ||
``` | ||
@@ -26,2 +26,3 @@ | ||
- Supports argc/argv | ||
- Supports background jobs with color-coded output | ||
- Supports the most classic builtins | ||
@@ -28,0 +29,0 @@ - Doesn't necessarily need to access the fs |
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
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
55685
1428
39
9
4
1
+ Addedchalk@^3.0.0
+ Addedansi-styles@4.3.0(transitive)
+ Addedchalk@3.0.0(transitive)
+ Addedcolor-convert@2.0.1(transitive)
+ Addedcolor-name@1.1.4(transitive)
+ Addedhas-flag@4.0.0(transitive)
+ Addedsupports-color@7.2.0(transitive)
Updated@yarnpkg/fslib@^2.5.0-rc.3
Updated@yarnpkg/parsers@^2.4.0-rc.1