Skip to content
Closed

wip #32269

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/angular/cli/src/commands/add/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}

Expand Down Expand Up @@ -299,7 +300,8 @@ export default class AddCommandModule
task: AddCommandTaskWrapper,
): Promise<void> {
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,
});
Expand Down
4 changes: 3 additions & 1 deletion packages/angular/cli/src/commands/update/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
getProjectDependencies,
readPackageJson,
} from '../../utilities/package-tree';
import { getCacheConfig } from '../cache/utilities';
import {
checkCLIVersion,
coerceVersionNumber,
Expand Down Expand Up @@ -172,7 +173,8 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
const { logger } = this.context;
// Instantiate the package manager
const packageManager = await createPackageManager({
cwd: this.context.root,
root: this.context.root,
cacheDirectory: getCacheConfig(this.context.workspace).path,
logger,
configuredPackageManager: this.context.packageManager.name,
});
Expand Down
9 changes: 5 additions & 4 deletions packages/angular/cli/src/package-managers/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import { major } from 'semver';
import { discover } from './discovery';
import { Host, NodeJS_HOST } from './host';
import { Host, createNodeJsHost } from './host';
import { Logger } from './logger';
import { PackageManager } from './package-manager';
import { PackageManagerName, SUPPORTED_PACKAGE_MANAGERS } from './package-manager-descriptor';
Expand Down Expand Up @@ -106,13 +106,14 @@ async function determinePackageManager(
* @returns A promise that resolves to a new `PackageManager` instance.
*/
export async function createPackageManager(options: {
cwd: string;
root: string;
cacheDirectory: string;
configuredPackageManager?: PackageManagerName;
logger?: Logger;
dryRun?: boolean;
}): Promise<PackageManager> {
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,
Expand Down
139 changes: 76 additions & 63 deletions packages/angular/cli/src/package-managers/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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<string, string>;
} = {},
): 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<string, string>;
} = {},
): 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));
});
});
});
},
};
},
};
}
Loading