Skip to content

Commit f9bd3b8

Browse files
committed
refactor(@angular/cli): use package manager abstraction for update command dependencies
This commit refactors the `ng update` command to use the `PackageManager` abstraction for discovering installed dependencies, replacing the previous file-system-based resolution logic. This change improves correctness (respecting package manager resolution strategies like PnP and workspaces) and performance (reducing initial file I/O).
1 parent 90217f3 commit f9bd3b8

File tree

1 file changed

+56
-30
lines changed
  • packages/angular/cli/src/commands/update

1 file changed

+56
-30
lines changed

packages/angular/cli/src/commands/update/cli.ts

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ import {
2020
Options,
2121
} from '../../command-builder/command-module';
2222
import { SchematicEngineHost } from '../../command-builder/utilities/schematic-engine-host';
23-
import { PackageManager, PackageManifest, createPackageManager } from '../../package-managers';
23+
import {
24+
InstalledPackage,
25+
PackageManager,
26+
PackageManifest,
27+
createPackageManager,
28+
} from '../../package-managers';
2429
import { colors } from '../../utilities/color';
2530
import { disableVersionCheck } from '../../utilities/environment-options';
2631
import { assertIsError } from '../../utilities/error';
27-
import {
28-
PackageTreeNode,
29-
findPackageJson,
30-
getProjectDependencies,
31-
readPackageJson,
32-
} from '../../utilities/package-tree';
32+
import { findPackageJson } from '../../utilities/package-tree';
3333
import {
3434
checkCLIVersion,
3535
coerceVersionNumber,
@@ -242,7 +242,7 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
242242
logger.info(`Using package manager: ${colors.gray(packageManager.name)}`);
243243
logger.info('Collecting installed dependencies...');
244244

245-
const rootDependencies = await getProjectDependencies(this.context.root);
245+
const rootDependencies = await packageManager.getProjectDependencies();
246246
logger.info(`Found ${rootDependencies.size} dependencies.`);
247247

248248
const workflow = new NodeWorkflow(this.context.root, {
@@ -275,7 +275,13 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
275275
}
276276

277277
return options.migrateOnly
278-
? this.migrateOnly(workflow, (options.packages ?? [])[0], rootDependencies, options)
278+
? this.migrateOnly(
279+
workflow,
280+
(options.packages ?? [])[0],
281+
rootDependencies,
282+
options,
283+
packageManager,
284+
)
279285
: this.updatePackagesAndMigrate(
280286
workflow,
281287
rootDependencies,
@@ -288,25 +294,35 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
288294
private async migrateOnly(
289295
workflow: NodeWorkflow,
290296
packageName: string,
291-
rootDependencies: Map<string, PackageTreeNode>,
297+
rootDependencies: Map<string, InstalledPackage>,
292298
options: Options<UpdateCommandArgs>,
299+
packageManager: PackageManager,
293300
): Promise<number | void> {
294301
const { logger } = this.context;
295-
const packageDependency = rootDependencies.get(packageName);
302+
let packageDependency = rootDependencies.get(packageName);
296303
let packagePath = packageDependency?.path;
297-
let packageNode = packageDependency?.package;
298-
if (packageDependency && !packageNode) {
299-
logger.error('Package found in package.json but is not installed.');
304+
let packageNode: PackageManifest | undefined;
300305

301-
return 1;
302-
} else if (!packageDependency) {
303-
// Allow running migrations on transitively installed dependencies
304-
// There can technically be nested multiple versions
305-
// TODO: If multiple, this should find all versions and ask which one to use
306-
const packageJson = findPackageJson(this.context.root, packageName);
307-
if (packageJson) {
308-
packagePath = path.dirname(packageJson);
309-
packageNode = await readPackageJson(packageJson);
306+
if (!packageDependency) {
307+
const installed = await packageManager.getInstalledPackage(packageName);
308+
if (installed) {
309+
packageDependency = installed;
310+
packagePath = installed.path;
311+
}
312+
}
313+
314+
if (packagePath) {
315+
packageNode = await readPackageManifest(path.join(packagePath, 'package.json'));
316+
}
317+
318+
if (!packageNode) {
319+
const jsonPath = findPackageJson(this.context.root, packageName);
320+
if (jsonPath) {
321+
packageNode = await readPackageManifest(jsonPath);
322+
323+
if (!packagePath) {
324+
packagePath = path.dirname(jsonPath);
325+
}
310326
}
311327
}
312328

@@ -399,7 +415,7 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
399415
// eslint-disable-next-line max-lines-per-function
400416
private async updatePackagesAndMigrate(
401417
workflow: NodeWorkflow,
402-
rootDependencies: Map<string, PackageTreeNode>,
418+
rootDependencies: Map<string, InstalledPackage>,
403419
options: Options<UpdateCommandArgs>,
404420
packages: npa.Result[],
405421
packageManager: PackageManager,
@@ -414,21 +430,21 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
414430

415431
const requests: {
416432
identifier: npa.Result;
417-
node: PackageTreeNode;
433+
node: InstalledPackage;
418434
}[] = [];
419435

420436
// Validate packages actually are part of the workspace
421437
for (const pkg of packages) {
422438
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
423439
const node = rootDependencies.get(pkg.name!);
424-
if (!node?.package) {
440+
if (!node) {
425441
logger.error(`Package '${pkg.name}' is not a dependency.`);
426442

427443
return 1;
428444
}
429445

430446
// If a specific version is requested and matches the installed version, skip.
431-
if (pkg.type === 'version' && node.package.version === pkg.fetchSpec) {
447+
if (pkg.type === 'version' && node.version === pkg.fetchSpec) {
432448
logger.info(`Package '${pkg.name}' is already at '${pkg.fetchSpec}'.`);
433449
continue;
434450
}
@@ -464,13 +480,13 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
464480
return 1;
465481
}
466482

467-
if (manifest.version === node.package?.version) {
483+
if (manifest.version === node.version) {
468484
logger.info(`Package '${packageName}' is already up to date.`);
469485
continue;
470486
}
471487

472-
if (node.package && ANGULAR_PACKAGES_REGEXP.test(node.package.name)) {
473-
const { name, version } = node.package;
488+
if (ANGULAR_PACKAGES_REGEXP.test(node.name)) {
489+
const { name, version } = node;
474490
const toBeInstalledMajorVersion = +manifest.version.split('.')[0];
475491
const currentMajorVersion = +version.split('.')[0];
476492

@@ -668,3 +684,13 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
668684
return success ? 0 : 1;
669685
}
670686
}
687+
688+
async function readPackageManifest(manifestPath: string): Promise<PackageManifest | undefined> {
689+
try {
690+
const content = await fs.readFile(manifestPath, 'utf8');
691+
692+
return JSON.parse(content) as PackageManifest;
693+
} catch {
694+
return undefined;
695+
}
696+
}

0 commit comments

Comments
 (0)