ultra-runner
Version:
Smart and beautiful script runner that hijacks any `npm run`, `yarn` and `npx` calls for ultra fast execution
281 lines • 13 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Runner = void 0;
const tslib_1 = require("tslib");
const chalk_1 = tslib_1.__importDefault(require("chalk"));
const fs_1 = require("fs");
const path_1 = tslib_1.__importStar(require("path"));
const perf_hooks_1 = require("perf_hooks");
const shellwords_ts_1 = tslib_1.__importDefault(require("shellwords-ts"));
const build_1 = require("./build");
const formatter_1 = require("./formatter");
const options_1 = require("./options");
const package_1 = require("./package");
const parser_1 = require("./parser");
const spawn_1 = require("./spawn");
const spinner_1 = require("./spinner");
const workspace_1 = require("./workspace");
const workspace_providers_1 = require("./workspace.providers");
const concurrency_1 = require("./concurrency");
class Runner {
constructor(_options = {}) {
this._options = _options;
this.spinner = new spinner_1.OutputSpinner();
this.buildCmd = "build";
this.deps = new Map();
this.options = Object.assign(Object.assign({}, options_1.defaults), _options);
}
formatStart(cmd, level, parentSpinner) {
if (this.options.raw)
return;
const title = this.formatCommand(cmd);
if (!this.options.pretty) {
const prefix = cmd.packageName
? `${chalk_1.default.grey.dim(` (${cmd.packageName})`)}`
: "";
if (cmd.type == parser_1.CommandType.script)
console.log(`❯ ${title}${prefix}`);
else
console.log(title + prefix);
}
else
return this.spinner.start(title, level, parentSpinner);
}
// TODO: refactor the method below. Move to its own class
runCommand(cmd, level = -2, parentSpinner) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
if (cmd.type == parser_1.CommandType.op)
return;
const isBuildScript = cmd.type == parser_1.CommandType.script && cmd.name == this.buildCmd;
const changes = isBuildScript
? yield build_1.needsBuild(cmd.cwd || process.cwd(), this.workspace, this.options.rebuild)
: undefined;
yield cmd.beforeRun();
let spinner;
if (cmd.type == parser_1.CommandType.script) {
if (level >= 0)
spinner = this.formatStart(cmd, level, parentSpinner);
}
else {
if (cmd.args.length) {
const args = shellwords_ts_1.default.split(cmd.args.join(" "));
if (cmd.bin)
args[0] = cmd.bin;
const cmdSpinner = this.formatStart(cmd, level, parentSpinner);
try {
if (!this.options.dryRun) {
const formatter = new formatter_1.CommandFormatter(args[0], level, cmdSpinner, this.options, cmd.packageName);
yield this.spawn(args[0], args.slice(1), formatter, cmd.cwd, cmd.env);
}
if (cmdSpinner) {
if (/warning/iu.test(cmdSpinner.output))
this.spinner.warning(cmdSpinner);
else
this.spinner.success(cmdSpinner);
}
}
catch (error) {
if (cmdSpinner)
this.spinner.error(cmdSpinner);
throw error;
}
}
}
const formatter = new formatter_1.CommandFormatter(cmd.name, level, spinner, this.options, cmd.packageName);
if (isBuildScript) {
if (!changes)
formatter.write("No changes. Skipping build...");
else if (!changes.isGitRepo) {
formatter.write(`${chalk_1.default.red("warning ")}Not a Git repository, so build change detection is disabled. Forcing full rebuild.`);
}
else {
formatter.write(chalk_1.default.blue("changes:\n") +
changes.changes
.map((c) => {
let str = " ";
if (c.type == build_1.ChangeType.added)
str += chalk_1.default.green("+");
else if (c.type == build_1.ChangeType.deleted)
str += chalk_1.default.red("-");
else if (c.type == build_1.ChangeType.modified)
str += chalk_1.default.green("+");
return `${str} ${c.file}`;
})
.join("\n"));
// formatter.write(chalk.red("\nWaiting for\n"))
}
}
try {
if (!isBuildScript || changes) {
const promises = [];
for (const child of cmd.children) {
if (child.isPostScript())
yield Promise.all(promises);
const promise = this.runCommand(child, level + 1, spinner);
promises.push(promise);
if (!cmd.concurrent || child.isPreScript())
yield promise;
else if (promises.length >= this.options.concurrency) {
yield Promise.all(promises);
promises.length = 0;
}
}
if (cmd.concurrent)
yield Promise.all(promises);
}
spinner && this.spinner.success(spinner);
cmd.afterRun();
if (changes)
yield changes.onBuild();
}
catch (error) {
if (spinner)
this.spinner.error(spinner);
throw error;
}
});
}
formatCommand(cmd) {
if (cmd.type == parser_1.CommandType.script)
return chalk_1.default.white.bold(`${cmd.name}`);
return `${chalk_1.default.grey(`$ ${cmd.args[0]}`)} ${cmd.args
.slice(1)
.map((x) => {
if (x.startsWith("-"))
return chalk_1.default.cyan(x);
if (fs_1.existsSync(x))
return chalk_1.default.magenta(x);
if (x.includes("*"))
return chalk_1.default.yellow(x);
return x;
})
.join(" ")}`;
}
findPnpJsFile(cwd = process.cwd()) {
if (this.pnpFile) {
return this.pnpFile;
}
const dir1 = package_1.findUp(".pnp.js", cwd);
if (dir1) {
this.pnpFile = path_1.default.resolve(dir1, ".pnp.js");
}
const dir2 = package_1.findUp(".pnp.cjs", cwd);
if (dir2) {
this.pnpFile = path_1.default.resolve(dir2, ".pnp.cjs");
}
return this.pnpFile;
}
spawn(cmd, args, formatter, cwd, env) {
// Special handling for yarn pnp binaries
if (cmd.startsWith("yarn:")) {
cmd = cmd.slice(5);
const pnpFile = this.findPnpJsFile(cwd);
if (!pnpFile) {
throw new Error(`cannot find .pnp.js file`);
}
args = ["-r", pnpFile, cmd, ...args];
// will fail with non js binaries, but yarn PnP already does not support them https://github.com/yarnpkg/berry/issues/882
cmd = "node";
}
const spawner = new spawn_1.Spawner(cmd, args, cwd, env);
if (this.options.pretty)
spawner.onData = (line) => formatter.write(line);
else
spawner.onLine = (line) => formatter.write(line);
spawner.onError = (err) => new Error(`${chalk_1.default.red("error")} Command ${chalk_1.default.white.dim(path_1.basename(cmd))} failed with ${chalk_1.default.red(err)}. Is the command on your path?`);
spawner.onExit = (code) => new Error(`${this.options.silent ? formatter.output : ""}\n${chalk_1.default.red("error")} Command ${chalk_1.default.white.dim(path_1.basename(cmd))} failed with exit code ${chalk_1.default.red(code)}`);
return spawner.spawn(this.options.raw);
}
formatDuration(duration) {
if (duration < 1)
return `${(duration * 1000).toFixed(0)}ms`;
return `${duration.toFixed(3)}s`;
}
list() {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const workspace = yield workspace_1.getWorkspace({
includeRoot: this.options.root,
type: this.options.recursive ? undefined : workspace_providers_1.WorkspaceProviderType.single,
});
if (!workspace)
throw new Error("Cannot find package.json");
let counter = 0;
workspace === null || workspace === void 0 ? void 0 : workspace.getPackages(this.options.filter).forEach((p) => {
console.log(`${chalk_1.default.bgGray.cyanBright(` ${counter++} `)} ${chalk_1.default.green(`${p.name}`)} at ${chalk_1.default.whiteBright(path_1.relative(workspace.root, p.root))}`);
Object.keys(p.scripts || {})
.sort()
.forEach((s) => {
console.log(` ❯ ${chalk_1.default.grey(s)}`);
});
});
});
}
run(cmd, pkg) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
if (!pkg) {
const root = package_1.findUp("package.json");
if (root)
pkg = package_1.getPackage(root);
}
if (!pkg)
pkg = { name: "" };
if (pkg) {
const parser = new parser_1.CommandParser(pkg);
return yield this._run(parser.parse(cmd));
}
throw new Error(`Could not find package`);
});
}
info() {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const types = yield workspace_1.Workspace.detectWorkspaceProviders();
if (!types.length)
throw new Error("No workspaces found");
if (types.length > 1)
console.log(chalk_1.default.blue("Detected workspaces: ") + chalk_1.default.magenta(types.join(", ")));
const workspace = yield workspace_1.getWorkspace({ includeRoot: true });
if (workspace) {
console.log(`${chalk_1.default.blue("Workspace ") + chalk_1.default.magenta(workspace.type)} with ${chalk_1.default.magenta(workspace.getPackageManager())}`);
let counter = 0;
workspace.getPackages(this.options.filter).forEach((p) => {
let at = path_1.relative(workspace.root, p.root);
if (!at.length)
at = ".";
console.log(`${chalk_1.default.bgGray.cyanBright(` ${counter++} `)} ${chalk_1.default.green(`${p.name}`)} at ${chalk_1.default.whiteBright(at)}`);
workspace.getDeps(p.name).forEach((s) => {
console.log(` ❯ ${chalk_1.default.grey(s)}`);
});
});
}
});
}
runRecursive(cmd) {
var _a;
return tslib_1.__awaiter(this, void 0, void 0, function* () {
this.workspace = yield workspace_1.getWorkspace({ includeRoot: this.options.root });
const workspace = this.workspace;
if (this.options.build)
this.buildCmd = cmd;
if (!workspace || !((_a = workspace === null || workspace === void 0 ? void 0 : workspace.packages) === null || _a === void 0 ? void 0 : _a.size))
throw new Error("Could not find packages in your workspace. Supported: yarn, pnpm, lerna");
const command = concurrency_1.createCommand(workspace, cmd, this.options);
return yield this._run(command, -1);
});
}
_run(command, level = -1) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
try {
yield this.runCommand(command, level);
this.spinner._stop();
if (!this.options.silent) {
console.log(chalk_1.default.green("success"), "✨", this.options.dryRun ? "Dry-run done" : "Done", `in ${this.formatDuration(perf_hooks_1.performance.nodeTiming.duration / 1000)}`);
}
}
finally {
this.spinner._stop();
}
});
}
}
exports.Runner = Runner;
//# sourceMappingURL=runner.js.map