diff --git a/packages/angular/cli/src/package-managers/package-manager.ts b/packages/angular/cli/src/package-managers/package-manager.ts index a5ebfad62553..afa2aa6b4c57 100644 --- a/packages/angular/cli/src/package-managers/package-manager.ts +++ b/packages/angular/cli/src/package-managers/package-manager.ts @@ -93,6 +93,9 @@ export class PackageManager { readonly #initializationError?: Error; #dependencyCache: Map | null = null; #version: string | undefined; + #activeTasks = 0; + readonly #pendingTasks: (() => void)[] = []; + readonly #maxConcurrent = 5; /** * Creates a new `PackageManager` instance. @@ -159,49 +162,72 @@ export class PackageManager { * @param options Options for the child process. * @returns A promise that resolves with the standard output and standard error of the command. */ + async #runWithThrottle(action: () => Promise): Promise { + if (this.#activeTasks >= this.#maxConcurrent) { + await new Promise((resolve) => { + this.#pendingTasks.push(resolve); + }); + } else { + this.#activeTasks++; + } + + try { + return await action(); + } finally { + const next = this.#pendingTasks.shift(); + if (next) { + next(); + } else { + this.#activeTasks--; + } + } + } + async #run( args: readonly string[], options: { timeout?: number; registry?: string; cwd?: string } = {}, ): Promise<{ stdout: string; stderr: string }> { - this.ensureInstalled(); + return this.#runWithThrottle(async () => { + this.ensureInstalled(); + + const { registry, cwd, ...runOptions } = options; + const finalArgs = [...args]; + let finalEnv: Record | undefined; + + if (registry) { + const registryOptions = this.descriptor.getRegistryOptions?.(registry); + if (!registryOptions) { + throw new Error( + `The configured package manager, '${this.descriptor.binary}', does not support a custom registry.`, + ); + } - const { registry, cwd, ...runOptions } = options; - const finalArgs = [...args]; - let finalEnv: Record | undefined; + if (registryOptions.args) { + finalArgs.push(...registryOptions.args); + } + if (registryOptions.env) { + finalEnv = registryOptions.env; + } + } - if (registry) { - const registryOptions = this.descriptor.getRegistryOptions?.(registry); - if (!registryOptions) { - throw new Error( - `The configured package manager, '${this.descriptor.binary}', does not support a custom registry.`, + const executionDirectory = cwd ?? this.cwd; + if (this.options.dryRun) { + this.options.logger?.info( + `[DRY RUN] Would execute in [${executionDirectory}]: ${this.descriptor.binary} ${finalArgs.join(' ')}`, ); - } - if (registryOptions.args) { - finalArgs.push(...registryOptions.args); - } - if (registryOptions.env) { - finalEnv = registryOptions.env; + return { stdout: '', stderr: '' }; } - } - const executionDirectory = cwd ?? this.cwd; - if (this.options.dryRun) { - this.options.logger?.info( - `[DRY RUN] Would execute in [${executionDirectory}]: ${this.descriptor.binary} ${finalArgs.join(' ')}`, - ); + const commandResult = await this.host.runCommand(this.descriptor.binary, finalArgs, { + ...runOptions, + cwd: executionDirectory, + stdio: 'pipe', + env: finalEnv, + }); - return { stdout: '', stderr: '' }; - } - - const commandResult = await this.host.runCommand(this.descriptor.binary, finalArgs, { - ...runOptions, - cwd: executionDirectory, - stdio: 'pipe', - env: finalEnv, + return { stdout: commandResult.stdout.trim(), stderr: commandResult.stderr.trim() }; }); - - return { stdout: commandResult.stdout.trim(), stderr: commandResult.stderr.trim() }; } /**