/
githubmirror
/
lerna
Обзор
Документация
Войти
/
githubmirror
/
lerna
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
libs/commands/exec/src/index.ts
205 строк
6 KB
James Henry
chore(repo): migrate from jest to vitest (#4389)
13 июл 2026, 20:06
Не верифицирован
13 июл 2026, 20:06
1f7e109
Код
Авторство
О чём код?
import { Arguments, Command, CommandConfigOptions, filterProjects, getPackage, Package, Profiler, ProjectGraphProjectNodeWithPackage, runProjectsTopologically, ValidationError, } from "@lerna/core"; import pMap from "p-map"; import * as childProcess from "@lerna/child-process"; export function factory(argv: Arguments<ExecCommandConfigOptions>) { return new ExecCommand(argv); } interface ExecCommandConfigOptions extends CommandConfigOptions { cmd?: string; args?: string[]; bail?: boolean; prefix?: boolean; parallel?: boolean; profile?: boolean; profileLocation?: string; rejectCycles?: boolean; "--"?: string[]; } export class ExecCommand extends Command<ExecCommandConfigOptions> { command?: string; args?: string[]; bail?: boolean; prefix?: boolean; env?: NodeJS.ProcessEnv; filteredProjects: ProjectGraphProjectNodeWithPackage[] = []; count?: number; packagePlural?: string; joinedCommand = ""; override get requiresGit() { return false; } override async initialize() { const dashedArgs = this.options["--"] || []; this.command = this.options.cmd || dashedArgs.shift(); this.args = (this.options.args || []).concat(dashedArgs); if (!this.command) { throw new ValidationError("ENOCOMMAND", "A command to execute is required"); } // inverted boolean options this.bail = this.options.bail !== false; this.prefix = this.options.prefix !== false; // accessing properties of process.env can be expensive, // so cache it here to reduce churn during tighter loops this.env = Object.assign({}, process.env); this.filteredProjects = filterProjects(this.projectGraph, this.execOpts, this.options); this.count = this.filteredProjects.length; this.packagePlural = this.count === 1 ? "package" : "packages"; this.joinedCommand = [this.command].concat(this.args).join(" "); } override async execute() { this.logger.info( "", "Executing command in %d %s: %j", this.count, this.packagePlural, this.joinedCommand ); let runCommand: () => Promise<unknown>; if (this.options.parallel) { runCommand = () => this.runCommandInPackagesParallel(); } else if (this.toposort) { runCommand = () => this.runCommandInPackagesTopological(); } else { runCommand = () => this.runCommandInPackagesLexical(); } if (this.bail) { // only the first error is caught try { await runCommand(); } catch (err: any) { process.exitCode = err.exitCode; // rethrow to halt chain and log properly throw err; } } else { const results = (await runCommand()) as { failed: boolean; exitCode: number }[]; // detect error (if any) from collected results if (results.some((result) => result.failed)) { // propagate "highest" error code, it's probably the most useful const codes = results.filter((result) => result.failed).map((result) => result.exitCode); const exitCode = Math.max(...codes, 1); this.logger.error("", "Received non-zero exit code %d during execution", exitCode); process.exitCode = exitCode; } } this.logger.success( "exec", "Executed command in %d %s: %j", this.count, this.packagePlural, this.joinedCommand ); } private getOpts(pkg: Package) { // these options are passed _directly_ to execa return { cwd: pkg.location, shell: true, extendEnv: false, env: Object.assign({}, this.env, { LERNA_PACKAGE_NAME: pkg.name, LERNA_ROOT_PATH: this.project.rootPath, }), reject: this.bail, pkg, }; } private getRunner() { return this.options.stream ? (pkg: Package) => this.runCommandInPackageStreaming(pkg) : (pkg: Package) => this.runCommandInPackageCapturing(pkg); } private getShellCommand(): [string, string[]] { // Node.js 24 deprecates passing separate arguments when shell is enabled because // they are concatenated without escaping. Preserve the existing shell semantics // explicitly by passing the already-joined command as a single string. return [this.joinedCommand, []]; } private runCommandInPackagesTopological() { let profiler: Profiler | undefined; let runner: (pkg: Package) => Promise<unknown>; if (this.options.profile) { profiler = new Profiler({ concurrency: this.concurrency, log: this.logger, outputDirectory: this.options.profileLocation || this.project.rootPath, }); const callback = this.getRunner(); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion runner = (pkg) => profiler!.run(() => callback(pkg), pkg.name); } else { runner = this.getRunner(); } let chain = runProjectsTopologically( this.filteredProjects, this.projectGraph, (p) => runner(getPackage(p)), { concurrency: this.concurrency, rejectCycles: this.options.rejectCycles, } ); if (profiler) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion chain = chain.then((results) => profiler!.output().then(() => results)); } return chain; } runCommandInPackagesParallel() { return pMap(this.filteredProjects, (p) => this.runCommandInPackageStreaming(getPackage(p))); } runCommandInPackagesLexical() { return pMap(this.filteredProjects, (p) => this.getRunner()(getPackage(p)), { concurrency: this.concurrency, }); } runCommandInPackageStreaming(pkg: Package) { const [command, args] = this.getShellCommand(); return childProcess.spawnStreaming(command, args, this.getOpts(pkg), (this.prefix && pkg.name) as string); } runCommandInPackageCapturing(pkg: Package) { const [command, args] = this.getShellCommand(); return childProcess.spawn(command, args, this.getOpts(pkg)); } }