Skip to content

Commit dd5afde

Browse files
committed
refactor(@angular/cli): move update version resolution directly to CLI command
Removes the @schematics/update:update schematic and moves the package version resolution, group expansion, and peer dependency validation logic directly into the CLI's update command. This simplifies the command execution flow, eliminates sharing state via global variables, and enables direct unit testing of the resolution plan in isolated temporary directories without host monorepo package leakage.
1 parent 7786de1 commit dd5afde

8 files changed

Lines changed: 1342 additions & 1695 deletions

File tree

packages/angular/cli/BUILD.bazel

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ RUNTIME_ASSETS = glob(
2929
include = [
3030
"bin/**/*",
3131
"src/**/*.md",
32-
"src/**/*.json",
3332
],
3433
exclude = [
3534
"lib/config/workspace-schema.json",
@@ -53,7 +52,6 @@ ts_project(
5352
) + [
5453
# These files are generated from the JSON schema
5554
"//packages/angular/cli:lib/config/workspace-schema.ts",
56-
"//packages/angular/cli:src/commands/update/schematic/schema.ts",
5755
],
5856
data = RUNTIME_ASSETS,
5957
deps = [
@@ -105,11 +103,6 @@ ts_json_schema(
105103
data = CLI_SCHEMA_DATA,
106104
)
107105

108-
ts_json_schema(
109-
name = "update_schematic_schema",
110-
src = "src/commands/update/schematic/schema.json",
111-
)
112-
113106
ts_project(
114107
name = "angular-cli_test_lib",
115108
testonly = True,

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

Lines changed: 120 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ import type { InstalledPackage, PackageManager, PackageManifest } from '../../pa
2424
import { colors } from '../../utilities/color';
2525
import { disableVersionCheck } from '../../utilities/environment-options';
2626
import { assertIsError } from '../../utilities/error';
27+
import {
28+
UpdatePlan,
29+
applyUpdatePlan,
30+
printUpdateUsageMessage,
31+
resolveUserUpdatePlan,
32+
} from './update-resolver';
2733
import {
2834
checkCLIVersion,
2935
coerceVersionNumber,
@@ -32,12 +38,7 @@ import {
3238
} from './utilities/cli-version';
3339
import { ANGULAR_PACKAGES_REGEXP } from './utilities/constants';
3440
import { checkCleanGit } from './utilities/git';
35-
import {
36-
commitChanges,
37-
executeMigration,
38-
executeMigrations,
39-
executeSchematic,
40-
} from './utilities/migration';
41+
import { commitChanges, executeMigration, executeMigrations } from './utilities/migration';
4142

4243
interface UpdateCommandArgs {
4344
packages?: string[];
@@ -54,8 +55,6 @@ interface UpdateCommandArgs {
5455

5556
class CommandError extends Error {}
5657

57-
const UPDATE_SCHEMATIC_COLLECTION = path.join(__dirname, 'schematic/collection.json');
58-
5958
export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs> {
6059
override scope = CommandScope.In;
6160
protected override shouldReportAnalytics = false;
@@ -244,23 +243,28 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
244243
});
245244

246245
if (packages.length === 0) {
247-
// Show status
248-
const { success } = await executeSchematic(
249-
workflow,
250-
logger,
251-
UPDATE_SCHEMATIC_COLLECTION,
252-
'update',
253-
{
254-
force: options.force,
255-
next: options.next,
256-
verbose: options.verbose,
257-
packageManager: packageManager.name,
258-
packages: [],
259-
workspaceRoot: this.context.root,
260-
},
261-
);
246+
try {
247+
const plan = await resolveUserUpdatePlan(
248+
{
249+
force: options.force,
250+
next: options.next,
251+
verbose: options.verbose,
252+
packageManager: packageManager.name,
253+
packages: [],
254+
workspaceRoot: this.context.root,
255+
},
256+
logger,
257+
);
258+
259+
printUpdateUsageMessage(plan.packageInfoMap, logger, options.next);
262260

263-
return success ? 0 : 1;
261+
return 0;
262+
} catch (error) {
263+
assertIsError(error);
264+
logger.error(error.message);
265+
266+
return 1;
267+
}
264268
}
265269

266270
return options.migrateOnly
@@ -509,88 +513,93 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
509513
return 0;
510514
}
511515

512-
const { success } = await executeSchematic(
513-
workflow,
514-
logger,
515-
UPDATE_SCHEMATIC_COLLECTION,
516-
'update',
517-
{
518-
verbose: options.verbose,
519-
force: options.force,
520-
next: options.next,
521-
packageManager: this.context.packageManager.name,
522-
packages: packagesToUpdate,
523-
workspaceRoot: this.context.root,
524-
},
525-
);
526-
527-
if (success) {
528-
const { root: commandRoot } = this.context;
529-
const ignorePeerDependencies = await shouldForcePackageManager(
530-
packageManager,
516+
let plan: UpdatePlan;
517+
try {
518+
plan = await resolveUserUpdatePlan(
519+
{
520+
packages: packagesToUpdate,
521+
force: options.force,
522+
next: options.next,
523+
packageManager: packageManager.name,
524+
verbose: options.verbose,
525+
workspaceRoot: this.context.root,
526+
},
531527
logger,
532-
options.verbose,
533528
);
534-
const tasks = new Listr([
535-
{
536-
title: 'Cleaning node modules directory',
537-
async task(_, task) {
538-
try {
539-
await fs.rm(path.join(commandRoot, 'node_modules'), {
540-
force: true,
541-
recursive: true,
542-
maxRetries: 3,
543-
});
544-
} catch (e) {
545-
assertIsError(e);
546-
if (e.code === 'ENOENT') {
547-
task.skip('Cleaning not required. Node modules directory not found.');
548-
}
529+
} catch (error) {
530+
assertIsError(error);
531+
logger.error(error.message);
532+
533+
return 1;
534+
}
535+
536+
try {
537+
await applyUpdatePlan(this.context.root, plan, logger);
538+
} catch (error) {
539+
assertIsError(error);
540+
logger.error(`Error updating package.json: ${error.message}`);
541+
542+
return 1;
543+
}
544+
545+
const { root: commandRoot } = this.context;
546+
const ignorePeerDependencies = await shouldForcePackageManager(
547+
packageManager,
548+
logger,
549+
options.verbose,
550+
);
551+
const tasks = new Listr([
552+
{
553+
title: 'Cleaning node modules directory',
554+
async task(_, task) {
555+
try {
556+
await fs.rm(path.join(commandRoot, 'node_modules'), {
557+
force: true,
558+
recursive: true,
559+
maxRetries: 3,
560+
});
561+
} catch (e) {
562+
assertIsError(e);
563+
if (e.code === 'ENOENT') {
564+
task.skip('Cleaning not required. Node modules directory not found.');
549565
}
550-
},
566+
}
551567
},
552-
{
553-
title: 'Installing packages',
554-
async task() {
555-
try {
556-
await packageManager.install({
557-
ignorePeerDependencies,
558-
});
559-
} catch (e) {
560-
throw new CommandError('Unable to install packages');
561-
}
562-
},
568+
},
569+
{
570+
title: 'Installing packages',
571+
async task() {
572+
try {
573+
await packageManager.install({
574+
ignorePeerDependencies,
575+
});
576+
} catch (e) {
577+
throw new CommandError('Unable to install packages');
578+
}
563579
},
564-
]);
565-
try {
566-
await tasks.run();
567-
} catch (e) {
568-
if (e instanceof CommandError) {
569-
return 1;
570-
}
571-
572-
throw e;
580+
},
581+
]);
582+
try {
583+
await tasks.run();
584+
} catch (e) {
585+
if (e instanceof CommandError) {
586+
return 1;
573587
}
588+
589+
throw e;
574590
}
575591

576-
if (success && options.createCommits) {
592+
if (options.createCommits) {
577593
if (
578594
!commitChanges(logger, `Angular CLI update for packages - ${packagesToUpdate.join(', ')}`)
579595
) {
580596
return 1;
581597
}
582598
}
583599

584-
// This is a temporary workaround to allow data to be passed back from the update schematic
585-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
586-
const migrations = (global as any).externalMigrations as {
587-
package: string;
588-
collection: string;
589-
from: string;
590-
to: string;
591-
}[];
600+
const migrations = plan.migrationsToRun;
592601

593-
if (success && migrations) {
602+
if (migrations) {
594603
const rootRequire = createRequire(this.context.root + '/');
595604
for (const migration of migrations) {
596605
// Resolve the package from the workspace root, as otherwise it will be resolved from the temp
@@ -631,10 +640,29 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
631640
return 1;
632641
}
633642

643+
const pkgJson = await fs.readFile(path.join(this.context.root, 'package.json'), 'utf8');
644+
logger.info(`--- DIAGNOSTIC: package.json =\n${pkgJson}`);
645+
646+
logger.info(
647+
`--- DIAGNOSTIC: package ${migration.package} -> packagePath=${packagePath}, collection=${migration.collection}`,
648+
);
649+
650+
const localMigrations = path.join(packagePath, migration.collection);
651+
logger.info(`--- DIAGNOSTIC: localMigrations path = ${localMigrations}`);
652+
logger.info(`--- DIAGNOSTIC: localMigrations exists = ${existsSync(localMigrations)}`);
653+
654+
try {
655+
const files = await fs.readdir(packagePath);
656+
logger.info(`--- DIAGNOSTIC: files in packagePath = ${files.join(', ')}`);
657+
} catch (err) {
658+
assertIsError(err);
659+
logger.info(`--- DIAGNOSTIC: failed to read packagePath: ${err.message}`);
660+
}
661+
634662
let migrations;
635663

636664
// Check if it is a package-local location
637-
const localMigrations = path.join(packagePath, migration.collection);
665+
// const localMigrations = path.join(packagePath, migration.collection);
638666
if (existsSync(localMigrations)) {
639667
migrations = localMigrations;
640668
} else {
@@ -673,7 +701,7 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
673701
}
674702
}
675703

676-
return success ? 0 : 1;
704+
return 0;
677705
}
678706
}
679707

packages/angular/cli/src/commands/update/schematic/collection.json

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)