UNPKG

lerna

Version:

Lerna is a fast, modern build system for managing and publishing multiple JavaScript/TypeScript packages from the same repository

320 lines (317 loc) 11.2 kB
import { Command, Profiler, ValidationError, filterProjects, generateProfileOutputPath, getPackage, npmRunScript, npmRunScriptStreaming, output, runProjectsTopologically, timer } from "./chunk-WC2B4V4E.js"; // libs/commands/run/src/index.ts import fs from "fs-extra"; import { runMany } from "nx/src/command-line/run-many/run-many"; import { runOne } from "nx/src/command-line/run/run-one"; import pMap from "p-map"; import { createRequire } from "node:module"; import path from "path"; import { performance } from "perf_hooks"; var require2 = createRequire(import.meta.url); function factory(argv) { return new RunCommand(argv); } var RunCommand = class extends Command { script = ""; args; npmClient; bail; prefix; projectsWithScript = []; count; packagePlural; joinedCommand; get requiresGit() { return false; } async initialize() { const { script, npmClient = "npm", bail, prefix } = this.options; this.script = script; this.args = this.options["--"] || []; this.npmClient = npmClient; if (!this.script) { throw new ValidationError("ENOSCRIPT", "You must specify a lifecycle script to run"); } if (this.argv.npmClient && this.options.useNx !== false) { throw new ValidationError( "run", "The legacy task runner option `--npm-client` is not currently supported. Please open an issue on https://github.com/lerna/lerna if you require this feature." ); } if (Array.isArray(this.script) && this.options.useNx === false) { throw new ValidationError( "run", "The legacy task runner does not support running multiple scripts concurrently. Please update to the latest version of lerna and ensure you do not have useNx set to false in your lerna.json." ); } this.bail = bail !== false; this.prefix = prefix !== false; const filteredProjects = filterProjects(this.projectGraph, this.execOpts, this.options); this.projectsWithScript = script === "env" ? filteredProjects : filteredProjects.filter((project) => { if (Array.isArray(this.script)) { return this.script.some((scriptName) => project.data.targets?.[scriptName]); } return project.data.targets?.[this.script ?? ""]; }); this.count = this.projectsWithScript.length; this.packagePlural = this.count === 1 ? "package" : "packages"; this.joinedCommand = [this.npmClient, "run", this.script].concat(this.args).join(" "); if (!this.count) { this.logger.success("run", `No packages found with the lifecycle script '${script}'`); return false; } return true; } async execute() { if (this.options.useNx === false) { this.logger.info( "", "Executing command in %d %s: %j", this.count, this.packagePlural, this.joinedCommand ); } const getElapsed = timer(); let runScripts; if (this.options.useNx !== false) { runScripts = () => this.runScriptsUsingNx(); } else if (this.options.parallel) { runScripts = () => this.runScriptInPackagesParallel(); } else if (this.toposort) { runScripts = () => this.runScriptInPackagesTopological(); } else { runScripts = () => this.runScriptInPackagesLexical(); } if (this.bail) { try { await runScripts(); } catch (err) { process.exitCode = err.exitCode; throw err; } } else { const results = await runScripts(); if (results.some((result) => result.failed)) { 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( "run", "Ran npm script '%s' in %d %s in %ss:", this.script, this.count, this.packagePlural, (getElapsed() / 1e3).toFixed(1) ); this.logger.success("", this.projectsWithScript.map((p) => `- ${getPackage(p).name}`).join("\n")); } getOpts(pkg) { return { args: this.args, npmClient: this.npmClient, prefix: this.prefix, reject: this.bail, pkg }; } getRunner() { return this.options.stream ? (pkg) => this.runScriptInPackageStreaming(pkg) : (pkg) => this.runScriptInPackageCapturing(pkg); } runScriptInPackagesTopological() { let profiler; let runner; if (this.options.profile) { profiler = new Profiler({ concurrency: this.concurrency, log: this.logger, outputDirectory: this.options.profileLocation }); const callback = this.getRunner(); runner = (pkg) => profiler.run(() => callback(pkg), pkg.name); } else { runner = this.getRunner(); } let chain = runProjectsTopologically( this.projectsWithScript, this.projectGraph, (p) => runner(getPackage(p)), { concurrency: this.concurrency, rejectCycles: this.options.rejectCycles } ); if (profiler) { chain = chain.then((results) => profiler.output().then(() => results)); } return chain; } runScriptInPackagesParallel() { return pMap(this.projectsWithScript, (p) => this.runScriptInPackageStreaming(getPackage(p))); } runScriptInPackagesLexical() { return pMap(this.projectsWithScript, (p) => this.getRunner()(getPackage(p)), { concurrency: this.concurrency }); } runScriptInPackageStreaming(pkg) { return npmRunScriptStreaming(this.script, this.getOpts(pkg)); } runScriptInPackageCapturing(pkg) { const getElapsed = timer(); return npmRunScript(this.script, this.getOpts(pkg)).then((result) => { this.logger.info( "run", "Ran npm script '%s' in '%s' in %ss:", this.script, pkg.name, (getElapsed() / 1e3).toFixed(1) ); output(result.stdout); return result; }); } async runScriptsUsingNx() { if (this.options.ci) { process.env["CI"] = "true"; } if (this.options.profile) { const absolutePath = generateProfileOutputPath(this.options.profileLocation); process.env["NX_PROFILE"] = path.relative(this.project.rootPath, absolutePath); } performance.mark("init-local"); this.configureNxOutput(); const { targetDependencies, options, extraOptions } = await this.prepNxOptions(); if (this.projectsWithScript.length === 1 && !Array.isArray(this.script)) { const fullQualifiedTarget = this.projectsWithScript.map((p) => p.name)[0] + ":" + this.addQuotesAroundScriptNameIfItHasAColon(this.script); return runOne( process.cwd(), { "project:target:configuration": fullQualifiedTarget, ...options }, targetDependencies, extraOptions ); } else { const projects = this.projectsWithScript.map((p) => p.name).join(","); return runMany( { projects, targets: Array.isArray(this.script) ? this.script : [this.script], ...options }, targetDependencies, extraOptions ); } } addQuotesAroundScriptNameIfItHasAColon(scriptName) { if (scriptName.includes(":")) { return `"${scriptName}"`; } else { return scriptName; } } async prepNxOptions() { const nxJsonExists = fs.existsSync(path.join(this.project.rootPath, "nx.json")); const { readNxJson } = require2("nx/src/config/configuration"); const nxJson = readNxJson(); const targetDependenciesAreDefined = Object.keys(nxJson.targetDependencies || nxJson.targetDefaults || {}).length > 0; const hasProjectSpecificNxConfiguration = this.projectsWithScript.some((p) => !!getPackage(p).get("nx")); const hasCustomizedNxConfiguration = nxJsonExists && targetDependenciesAreDefined || hasProjectSpecificNxConfiguration; const mimicLernaDefaultBehavior = !hasCustomizedNxConfiguration; const targetDependencies = this.toposort && !this.options.parallel && mimicLernaDefaultBehavior && !Array.isArray(this.script) ? { [this.script]: [ { dependencies: true, target: this.script } ] } : {}; if (this.options.prefix === false && !this.options.stream) { this.logger.warn(this.name, `"no-prefix" is ignored when not using streaming output.`); } const outputStyle = this.options.stream ? this.prefix ? "stream" : "stream-without-prefixes" : "dynamic"; const options = { outputStyle, /** * To match lerna's own behavior (via pMap's default concurrency), we set parallel to a very large number if * the flag has been set (we can't use Infinity because that would cause issues with the task runner). */ parallel: this.options.parallel && mimicLernaDefaultBehavior ? 999 : this.concurrency, nxBail: this.bail, nxIgnoreCycles: !this.options.rejectCycles, skipNxCache: this.options.skipNxCache, verbose: this.options.verbose, __overrides__: this.args?.map((t) => t.toString()) }; if (hasCustomizedNxConfiguration) { this.logger.verbose( this.name, "Nx target configuration was found. Task dependencies will be automatically included." ); if (this.options.parallel || this.options.sort !== void 0) { this.logger.warn( this.name, `"parallel", "sort", and "no-sort" are ignored when Nx targets are configured. See https://lerna.js.org/docs/lerna6-obsolete-options for details.` ); } if (this.options.includeDependencies) { this.logger.info( this.name, `Using the "include-dependencies" option when Nx targets are configured will include both task dependencies detected by Nx and project dependencies detected by Lerna. See https://lerna.js.org/docs/lerna6-obsolete-options#--include-dependencies for details.` ); } if (this.options.ignore) { this.logger.info( this.name, `Using the "ignore" option when Nx targets are configured will exclude only tasks that are not determined to be required by Nx. See https://lerna.js.org/docs/lerna6-obsolete-options#--ignore for details.` ); } } else { this.logger.verbose( this.name, "Nx target configuration was not found. Task dependencies will not be automatically included." ); } const extraOptions = { excludeTaskDependencies: mimicLernaDefaultBehavior, loadDotEnvFiles: this.options.loadEnvFiles ?? true }; return { targetDependencies, options, extraOptions }; } configureNxOutput() { try { const nxOutput = require2("nx/src/utils/output"); nxOutput.output.cliName = "Lerna (powered by Nx)"; nxOutput.output.formatCommand = (taskId) => taskId; return nxOutput; } catch (err) { this.logger.error( "\n", "There was a critical error when configuring the task runner, please report this on https://github.com/lerna/lerna" ); throw err; } } }; export { factory, RunCommand };