diff --git a/packages/angular/cli/src/commands/add/cli.ts b/packages/angular/cli/src/commands/add/cli.ts index 0dae016fba12..a5fba05c725d 100644 --- a/packages/angular/cli/src/commands/add/cli.ts +++ b/packages/angular/cli/src/commands/add/cli.ts @@ -33,6 +33,7 @@ import { import { assertIsError } from '../../utilities/error'; import { isTTY } from '../../utilities/tty'; import { VERSION } from '../../utilities/version'; +import { getCacheConfig } from '../cache/utilities'; class CommandError extends Error {} @@ -299,7 +300,8 @@ export default class AddCommandModule task: AddCommandTaskWrapper, ): Promise { context.packageManager = await createPackageManager({ - cwd: this.context.root, + cacheDirectory: getCacheConfig(this.context.workspace).path, + root: this.context.root, logger: this.context.logger, dryRun: context.dryRun, }); diff --git a/packages/angular/cli/src/commands/update/cli.ts b/packages/angular/cli/src/commands/update/cli.ts index 9b926cc079a2..581b4de97bc8 100644 --- a/packages/angular/cli/src/commands/update/cli.ts +++ b/packages/angular/cli/src/commands/update/cli.ts @@ -30,6 +30,7 @@ import { getProjectDependencies, readPackageJson, } from '../../utilities/package-tree'; +import { getCacheConfig } from '../cache/utilities'; import { checkCLIVersion, coerceVersionNumber, @@ -172,7 +173,8 @@ export default class UpdateCommandModule extends CommandModule { - const { cwd, configuredPackageManager, logger, dryRun } = options; - const host = NodeJS_HOST; + const { root: cwd, cacheDirectory, configuredPackageManager, logger, dryRun } = options; + const host = createNodeJsHost(cwd, cacheDirectory); const { name, source } = await determinePackageManager( host, diff --git a/packages/angular/cli/src/package-managers/host.ts b/packages/angular/cli/src/package-managers/host.ts index 82d61031d147..a8024463e15c 100644 --- a/packages/angular/cli/src/package-managers/host.ts +++ b/packages/angular/cli/src/package-managers/host.ts @@ -15,8 +15,8 @@ import { type SpawnOptions, spawn } from 'node:child_process'; import { Stats } from 'node:fs'; -import { mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises'; -import { platform, tmpdir } from 'node:os'; +import { copyFile, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises'; +import { platform } from 'node:os'; import { join } from 'node:path'; import { PackageManagerError } from './error'; @@ -88,67 +88,80 @@ export interface Host { /** * A concrete implementation of the `Host` interface that uses the Node.js APIs. + * @param root The root directory of the project. + * @param cacheDirectory The directory to use for caching. + * @returns A host that uses the Node.js APIs. */ -export const NodeJS_HOST: Host = { - stat, - readdir, - readFile: (path: string) => readFile(path, { encoding: 'utf8' }), - writeFile, - createTempDirectory: () => mkdtemp(join(tmpdir(), 'angular-cli-')), - deleteDirectory: (path: string) => rm(path, { recursive: true, force: true }), - runCommand: async ( - command: string, - args: readonly string[], - options: { - timeout?: number; - stdio?: 'pipe' | 'ignore'; - cwd?: string; - env?: Record; - } = {}, - ): Promise<{ stdout: string; stderr: string }> => { - const signal = options.timeout ? AbortSignal.timeout(options.timeout) : undefined; - const isWin32 = platform() === 'win32'; - - return new Promise((resolve, reject) => { - const spawnOptions = { - shell: isWin32, - stdio: options.stdio ?? 'pipe', - signal, - cwd: options.cwd, - env: { - ...process.env, - ...options.env, - }, - } satisfies SpawnOptions; - const childProcess = isWin32 - ? spawn(`${command} ${args.join(' ')}`, spawnOptions) - : spawn(command, args, spawnOptions); - - let stdout = ''; - childProcess.stdout?.on('data', (data) => (stdout += data.toString())); - - let stderr = ''; - childProcess.stderr?.on('data', (data) => (stderr += data.toString())); - - childProcess.on('close', (code) => { - if (code === 0) { - resolve({ stdout, stderr }); - } else { - const message = `Process exited with code ${code}.`; - reject(new PackageManagerError(message, stdout, stderr, code)); - } - }); - - childProcess.on('error', (err) => { - if (err.name === 'AbortError') { - const message = `Process timed out.`; +export function createNodeJsHost(root: string, cacheDirectory: string): Host { + return { + stat, + readdir, + readFile: (path: string) => readFile(path, { encoding: 'utf8' }), + writeFile, + createTempDirectory: async () => { + await mkdir(cacheDirectory, { recursive: true }); + const tmpDir = await mkdtemp(join(cacheDirectory, 'package-manager-temp-')); + + // Copy the .npmrc file to the temp directory if it exists. + await copyFile(`${root}/.npmrc`, `${tmpDir}/.npmrc`).catch(() => {}); + + return tmpDir; + }, + deleteDirectory: (path: string) => rm(path, { recursive: true, force: true }), + runCommand: async ( + command: string, + args: readonly string[], + options: { + timeout?: number; + stdio?: 'pipe' | 'ignore'; + cwd?: string; + env?: Record; + } = {}, + ): Promise<{ stdout: string; stderr: string }> => { + const signal = options.timeout ? AbortSignal.timeout(options.timeout) : undefined; + const isWin32 = platform() === 'win32'; + + return new Promise((resolve, reject) => { + const spawnOptions = { + shell: isWin32, + stdio: options.stdio ?? 'pipe', + signal, + cwd: options.cwd, + env: { + ...process.env, + ...options.env, + }, + } satisfies SpawnOptions; + const childProcess = isWin32 + ? spawn(`${command} ${args.join(' ')}`, spawnOptions) + : spawn(command, args, spawnOptions); + + let stdout = ''; + childProcess.stdout?.on('data', (data) => (stdout += data.toString())); + + let stderr = ''; + childProcess.stderr?.on('data', (data) => (stderr += data.toString())); + + childProcess.on('close', (code) => { + if (code === 0) { + resolve({ stdout, stderr }); + } else { + const message = `Process exited with code ${code}.`; + reject(new PackageManagerError(message, stdout, stderr, code)); + } + }); + + childProcess.on('error', (err) => { + if (err.name === 'AbortError') { + const message = `Process timed out.`; + reject(new PackageManagerError(message, stdout, stderr, null)); + + return; + } + const message = `Process failed with error: ${err.message}`; reject(new PackageManagerError(message, stdout, stderr, null)); - - return; - } - const message = `Process failed with error: ${err.message}`; - reject(new PackageManagerError(message, stdout, stderr, null)); + }); }); - }); - }, -}; + }, + }; +}