Comparing version 1.1.1 to 1.2.0
#!/usr/bin/env node | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Promise = require("bluebird"); | ||
const fs = require("fs"); | ||
@@ -10,3 +9,2 @@ const yargs_1 = require("yargs"); | ||
const workspace_1 = require("./workspace"); | ||
const topomap_1 = require("./topomap"); | ||
const bin = yargs_1.argv.bin || 'yarn'; | ||
@@ -28,2 +26,3 @@ let mode; | ||
const addPrefix = yargs_1.argv.prefix === undefined ? true : false; | ||
const doneCriteria = yargs_1.argv.doneCriteria; | ||
const cmd = yargs_1.argv._[0]; | ||
@@ -40,43 +39,13 @@ const pkgName = yargs_1.argv._[1]; | ||
const pkgJsons = _.map(pkgs, pkg => pkg.json); | ||
function genCmd(bi) { | ||
return { pkgName: bi.name, cmd: `cd ${pkgPaths[bi.name]} && ${bin} ${cmd}` }; | ||
} | ||
// choose which packages to run the command on | ||
let sortedInstrs; | ||
if (pkgName) { | ||
if (recursive) { | ||
sortedInstrs = topomap_1.subsetBuildOrder(pkgJsons, [pkgName]); | ||
} | ||
else { | ||
sortedInstrs = [{ name: pkgName, order: 1, cycle: false }]; | ||
} | ||
} | ||
else { | ||
sortedInstrs = topomap_1.buildOrder(pkgJsons); | ||
} | ||
sortedInstrs = _.sortBy(sortedInstrs, 'order'); | ||
let runner; | ||
if (mode === 'stages' || mode === 'serial') { | ||
const runMode = mode === 'stages' ? 'parallel' : 'serial'; | ||
// generate stages | ||
const stages = []; | ||
let i = 1; | ||
while (true) { | ||
const stage = sortedInstrs.filter(pkg => pkg.order === i); | ||
if (!stage.length) | ||
break; | ||
stages.push(stage); | ||
i++; | ||
} | ||
// run in batches | ||
runner = Promise.mapSeries(stages, stg => { | ||
console.log(`-- Packages in stage: ${stg.map(p => p.name).join(', ')} --`); | ||
const cmds = stg.map(genCmd); | ||
return new parallelshell_1.RunAll(cmds, runMode, { fastExit, collectLogs, addPrefix }).finishedAll; | ||
}); | ||
} | ||
else { | ||
const cmds = sortedInstrs.map(genCmd); | ||
runner = new parallelshell_1.RunAll(cmds, 'parallel', { fastExit, collectLogs, addPrefix }).finishedAll; | ||
} | ||
let runner = new parallelshell_1.RunGraph(pkgJsons, { | ||
bin, | ||
fastExit, | ||
collectLogs, | ||
addPrefix, | ||
mode: mode, | ||
recursive, | ||
doneCriteria | ||
}, pkgPaths); | ||
let runlist = yargs_1.argv._.slice(1); | ||
runner.run(cmd, runlist.length > 0 ? runlist : undefined); | ||
//# sourceMappingURL=index.js.map |
@@ -6,41 +6,55 @@ /// <reference types="node" /> | ||
*/ | ||
import * as Promise from "bluebird"; | ||
import { ChildProcess } from "child_process"; | ||
import * as Promise from 'bluebird'; | ||
import { ChildProcess } from 'child_process'; | ||
export interface CmdOptions { | ||
rejectOnNonZeroExit?: boolean; | ||
collectLogs?: boolean; | ||
addPrefix?: boolean; | ||
doneCriteria?: string; | ||
path?: string; | ||
} | ||
export declare class CmdProcess { | ||
cmd: string; | ||
private cmd; | ||
private pkgName; | ||
private opts; | ||
cp: ChildProcess; | ||
finished: Promise<CmdProcess>; | ||
pkgName: string; | ||
opts: { | ||
rejectOnNonZeroExit?: boolean; | ||
collectLogs?: boolean; | ||
addPrefix?: boolean; | ||
}; | ||
constructor(cmd: string, pkgName: string, opts?: { | ||
rejectOnNonZeroExit?: boolean; | ||
collectLogs?: boolean; | ||
addPrefix?: boolean; | ||
}); | ||
private _closed; | ||
private _finished; | ||
private _exitCode; | ||
readonly finished: Promise<void>; | ||
readonly closed: Promise<number>; | ||
readonly exitCode: Promise<number>; | ||
doneCriteria?: RegExp; | ||
constructor(cmd: string, pkgName: string, opts?: CmdOptions); | ||
start(): void; | ||
private autoPrefix(line); | ||
private _start(cmd); | ||
} | ||
export declare class RunAll { | ||
import { PkgJson, Dict } from './workspace'; | ||
export interface GraphOptions { | ||
bin: string; | ||
fastExit: boolean; | ||
collectLogs: boolean; | ||
addPrefix: boolean; | ||
mode: 'parallel' | 'serial' | 'stages'; | ||
recursive: boolean; | ||
doneCriteria: string | undefined; | ||
} | ||
export declare class RunGraph { | ||
pkgJsons: PkgJson[]; | ||
opts: GraphOptions; | ||
pkgPaths: Dict<string>; | ||
private procmap; | ||
children: CmdProcess[]; | ||
finishedAll: Promise<CmdProcess[]>; | ||
opts: { | ||
fastExit: boolean; | ||
collectLogs: boolean; | ||
addPrefix: boolean; | ||
}; | ||
constructor(pkgCmds: { | ||
pkgName: string; | ||
cmd: string; | ||
}[], mode: "parallel" | "serial", opts: { | ||
fastExit: boolean; | ||
collectLogs: boolean; | ||
addPrefix: boolean; | ||
}); | ||
/** | ||
* kills all the children | ||
*/ | ||
closeAll(): void; | ||
private jsonMap; | ||
private runList; | ||
private throat; | ||
constructor(pkgJsons: PkgJson[], opts: GraphOptions, pkgPaths: Dict<string>); | ||
private closeAll; | ||
private lookupOrRun(cmd, pkg); | ||
private allDeps(pkg); | ||
private makeCmd(cmd, pkg); | ||
private runOne(cmd, pkg); | ||
run(cmd: string, pkgs?: string[]): Promise<undefined>; | ||
} |
@@ -9,44 +9,72 @@ "use strict"; | ||
const split = require("split"); | ||
let wait, verbose; | ||
wait = true; | ||
verbose = true; | ||
let mkThroat = require('throat')(Promise); | ||
let passThrough = f => f(); | ||
function prefixLine(pkgName, line, prefixLength = 15) { | ||
const pkgNameShort = pkgName.slice(0, prefixLength - 1); | ||
const spaces = " ".repeat(Math.max(1, prefixLength - pkgName.length)); | ||
const spaces = ' '.repeat(Math.max(1, prefixLength - pkgName.length)); | ||
return `${pkgNameShort}${spaces} | ${line}`; | ||
} | ||
function defer() { | ||
let d; | ||
let promise = new Promise((resolve, reject) => { | ||
d = { resolve, reject }; | ||
}); | ||
d.promise = promise; | ||
return d; | ||
} | ||
class CmdProcess { | ||
constructor(cmd, pkgName, opts = {}) { | ||
this.cmd = cmd; | ||
this.pkgName = pkgName; | ||
this.opts = opts; | ||
this._start(cmd); | ||
this.finished = new Promise((resolve, reject) => { | ||
this.cp.on("close", (code) => { | ||
if (code > 0) { | ||
const msg = "`" + this.cmd + "` failed with exit code " + code; | ||
console.error(msg); | ||
if (this.opts.rejectOnNonZeroExit) { | ||
reject(new Error(msg)); | ||
} | ||
else { | ||
resolve(this); | ||
} | ||
} | ||
else { | ||
resolve(this); | ||
} | ||
}); | ||
this.pkgName = pkgName; | ||
this.opts = opts; | ||
this._finished = defer(); | ||
this._exitCode = defer(); | ||
this._closed = defer(); | ||
if (this.opts.doneCriteria) | ||
this.doneCriteria = new RegExp(this.opts.doneCriteria); | ||
} | ||
get finished() { | ||
return this._finished.promise; | ||
} | ||
get closed() { | ||
return this._closed.promise; | ||
} | ||
get exitCode() { | ||
return this._exitCode.promise; | ||
} | ||
start() { | ||
this._start(this.cmd); | ||
this.cp.once('close', code => { | ||
this._closed.resolve(code); | ||
this._exitCode.resolve(code); | ||
}); | ||
this.cp.once('exit', code => this._exitCode.resolve(code)); | ||
this.exitCode.then(code => { | ||
if (code > 0) { | ||
const msg = '`' + this.cmd + '` failed with exit code ' + code; | ||
console.error(msg); | ||
if (this.opts.rejectOnNonZeroExit) | ||
return this._finished.reject(new Error(msg)); | ||
} | ||
this._finished.resolve(); | ||
}); | ||
} | ||
autoPrefix(line) { | ||
return this.opts.addPrefix ? prefixLine(this.pkgName, line) : line; | ||
} | ||
_start(cmd) { | ||
let sh; | ||
let shFlag; | ||
let args; | ||
// cross platform compatibility | ||
if (process.platform === "win32") { | ||
sh = "cmd"; | ||
shFlag = "/c"; | ||
if (process.platform === 'win32') { | ||
sh = 'cmd'; | ||
args = ['/c', cmd]; | ||
} | ||
else { | ||
sh = "bash"; | ||
shFlag = "-c"; | ||
; | ||
[sh, ...args] = cmd.split(' '); | ||
//sh = 'bash' | ||
//shFlag = '-c' | ||
} | ||
@@ -56,77 +84,105 @@ const stdOutBuffer = []; | ||
this.cmd = cmd; | ||
this.cp = child_process_1.spawn(sh, [shFlag, cmd], { | ||
cwd: (process.versions.node < "8.0.0" | ||
? process.cwd | ||
: process.cwd()), | ||
env: process.env, | ||
stdio: ["pipe"] | ||
console.log('>>>', this.pkgName, '$', cmd); | ||
this.cp = child_process_1.spawn(sh, args, { | ||
cwd: this.opts.path || | ||
(process.versions.node < '8.0.0' ? process.cwd : process.cwd()), | ||
env: Object.assign(process.env, { FORCE_COLOR: process.stdout.isTTY }), | ||
stdio: this.opts.collectLogs || this.opts.addPrefix || this.opts.doneCriteria ? 'pipe' : 'inherit' | ||
}); | ||
if (this.opts.collectLogs) { | ||
this.cp.stdout.pipe(split()).on("data", (line) => { | ||
stdOutBuffer.push(line); | ||
if (this.cp.stdout) | ||
this.cp.stdout.pipe(split()).on('data', (line) => { | ||
if (this.opts.collectLogs) | ||
stdOutBuffer.push(line); | ||
else | ||
console.log(this.autoPrefix(line)); | ||
if (this.doneCriteria && this.doneCriteria.test(line)) | ||
this._finished.resolve(); | ||
}); | ||
this.cp.stderr.pipe(split()).on("data", (line) => { | ||
stdErrBuffer.push(line); | ||
if (this.cp.stderr) | ||
this.cp.stderr.pipe(split()).on('data', (line) => { | ||
if (this.opts.collectLogs) | ||
stdErrBuffer.push(line); | ||
else | ||
console.error(this.autoPrefix(line)); | ||
if (this.doneCriteria && this.doneCriteria.test(line)) | ||
this._finished.resolve(); | ||
}); | ||
this.cp.on("close", () => { | ||
console.log(stdOutBuffer | ||
.map(line => this.opts.addPrefix ? prefixLine(this.pkgName, line) : line) | ||
.join("\n")); | ||
console.error(stdErrBuffer | ||
.map(line => this.opts.addPrefix ? prefixLine(this.pkgName, line) : line) | ||
.join("\n")); | ||
if (this.opts.collectLogs) | ||
this.closed.then(() => { | ||
console.log(stdOutBuffer.map(line => this.autoPrefix(line)).join('\n')); | ||
console.error(stdErrBuffer.map(line => this.autoPrefix(line)).join('\n')); | ||
}); | ||
} | ||
else { | ||
this.cp.stdout.pipe(split()).on("data", (line) => { | ||
console.log(this.opts.addPrefix ? prefixLine(this.pkgName, line) : line); | ||
}); | ||
this.cp.stderr.pipe(split()).on("data", (line) => { | ||
console.error(this.opts.addPrefix ? prefixLine(this.pkgName, line) : line); | ||
}); | ||
} | ||
} | ||
} | ||
exports.CmdProcess = CmdProcess; | ||
class RunAll { | ||
constructor(pkgCmds, mode, opts) { | ||
this.children = []; | ||
const lodash_1 = require("lodash"); | ||
class RunGraph { | ||
constructor(pkgJsons, opts, pkgPaths) { | ||
this.pkgJsons = pkgJsons; | ||
this.opts = opts; | ||
if (mode === "parallel") { | ||
this.finishedAll = Promise.map(pkgCmds, pkgCmd => { | ||
const child = new CmdProcess(pkgCmd.cmd, pkgCmd.pkgName, { | ||
rejectOnNonZeroExit: this.opts.fastExit, | ||
collectLogs: this.opts.collectLogs, | ||
addPrefix: this.opts.addPrefix | ||
}); | ||
child.cp.on("close", code => code > 0 && this.closeAll.bind(this)); | ||
this.children.push(child); | ||
return child.finished; | ||
this.pkgPaths = pkgPaths; | ||
this.procmap = new Map(); | ||
this.jsonMap = new Map(); | ||
this.runList = new Set(); | ||
this.throat = passThrough; | ||
this.closeAll = () => { | ||
console.log('Stopping', this.children.length, 'active children'); | ||
this.children.forEach(ch => { | ||
ch.cp.removeAllListeners('close'); | ||
ch.cp.removeAllListeners('exit'); | ||
ch.cp.kill('SIGINT'); | ||
}); | ||
}; | ||
pkgJsons.forEach(j => this.jsonMap.set(j.name, j)); | ||
this.children = []; | ||
if (this.opts.mode === 'serial') | ||
this.throat = mkThroat(1); | ||
if (this.opts.mode === 'stages') | ||
this.throat = mkThroat(16); // max 16 proc | ||
process.on('SIGINT', this.closeAll); // close all children on ctrl+c | ||
} | ||
lookupOrRun(cmd, pkg) { | ||
let proc = this.procmap.get(pkg); | ||
if (proc == null) { | ||
proc = Promise.resolve().then(() => this.runOne(cmd, pkg)); | ||
this.procmap.set(pkg, proc); | ||
} | ||
else { | ||
this.finishedAll = Promise.mapSeries(pkgCmds, pkgCmd => { | ||
const child = new CmdProcess(pkgCmd.cmd, pkgCmd.pkgName, { | ||
rejectOnNonZeroExit: this.opts.fastExit, | ||
collectLogs: this.opts.collectLogs, | ||
addPrefix: this.opts.addPrefix | ||
}); | ||
child.cp.on("close", code => code > 0 && this.closeAll.bind(this)); | ||
this.children = [child]; | ||
return proc; | ||
} | ||
allDeps(pkg) { | ||
let findMyDeps = lodash_1.uniq(Object.keys(pkg.dependencies || {}).concat(Object.keys(pkg.devDependencies || {}))).filter(d => this.jsonMap.has(d) && (this.opts.recursive || this.runList.has(d))); | ||
return findMyDeps; | ||
} | ||
makeCmd(cmd, pkg) { | ||
return `${this.opts.bin} ${cmd}`; | ||
} | ||
runOne(cmd, pkg) { | ||
let p = this.jsonMap.get(pkg); | ||
if (p == null) | ||
throw new Error('Unknown package: ' + pkg); | ||
let myDeps = Promise.all(this.allDeps(p).map(d => this.lookupOrRun(cmd, d))); | ||
return myDeps.then(() => { | ||
let cmdLine = this.makeCmd(cmd, pkg); | ||
const child = new CmdProcess(cmdLine, pkg, { | ||
rejectOnNonZeroExit: this.opts.fastExit, | ||
collectLogs: this.opts.collectLogs, | ||
addPrefix: this.opts.addPrefix, | ||
doneCriteria: this.opts.doneCriteria, | ||
path: this.pkgPaths[pkg] | ||
}); | ||
child.exitCode.then(code => code > 0 && this.closeAll.bind(this)); | ||
this.children.push(child); | ||
let finished = this.throat(() => { | ||
child.start(); | ||
return child.finished; | ||
}); | ||
} | ||
process.on("SIGINT", this.closeAll.bind(this)); // close all children on ctrl+c | ||
} | ||
/** | ||
* kills all the children | ||
*/ | ||
closeAll() { | ||
this.children.forEach(ch => { | ||
ch.cp.removeAllListeners("close"); | ||
ch.cp.kill("SIGINT"); | ||
return this.opts.mode != 'parallel' ? finished : Promise.resolve(); | ||
}); | ||
} | ||
run(cmd, pkgs = this.pkgJsons.map(p => p.name)) { | ||
this.runList = new Set(pkgs); | ||
return Promise.all(pkgs.map(pkg => this.lookupOrRun(cmd, pkg))).thenReturn(void 0); | ||
} | ||
} | ||
exports.RunAll = RunAll; | ||
exports.RunGraph = RunGraph; | ||
//# sourceMappingURL=parallelshell.js.map |
{ | ||
"name": "wsrun", | ||
"version": "1.1.1", | ||
"version": "1.2.0", | ||
"description": "executes commands on packages in parallel, but is aware of the dependencies between them", | ||
@@ -24,3 +24,5 @@ "main": "./build/index.js", | ||
}, | ||
"files": ["build/"], | ||
"files": [ | ||
"build/" | ||
], | ||
"devDependencies": { | ||
@@ -32,4 +34,4 @@ "@types/bluebird": "^3.5.18", | ||
"@types/node": "^8.0.53", | ||
"@types/split": "^0.3.28", | ||
"@types/yargs": "^8.0.2", | ||
"@types/split": "^0.3.28", | ||
"jest": "^21.2.1", | ||
@@ -50,4 +52,5 @@ "ts-jest": "^21.2.3", | ||
"split": "^1.0.1", | ||
"throat": "^4.1.0", | ||
"yargs": "^10.0.3" | ||
} | ||
} |
# Workspace command runner | ||
Usage: | ||
Run commands in a yarn workspace, like a boss. | ||
### Usage: | ||
``` | ||
@@ -22,5 +24,6 @@ wsrun cmd [<package>] [options] | ||
--bin=yarn which program should we pass the cmd to | ||
--done-criteria=regex consider the process "done" when output line matches regex | ||
``` | ||
Examples: | ||
### Examples: | ||
@@ -35,10 +38,7 @@ `yarn wsrun watch` will run `yarn watch` on every individual package, in parallel. | ||
`yarn wsrun clean` will remove the build folders in every package. | ||
`yarn wsrun watch planc -r --stages --done-criteria='Compilation complete'` will watch planc deps, | ||
in order, continuing when command outputs "Compilation complete" | ||
`yarn wsrun clean` will remove "build" folders in every package. | ||
`yarn wsrun test` will test every package. | ||
Todo: | ||
* Support for collecting stdouts | ||
* Support for stdout line prefixes | ||
* Reorganize files |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
39621
582
6
+ Addedthroat@^4.1.0
+ Addedthroat@4.1.0(transitive)