Skip to content

Commit ca70a18

Browse files
committed
refactor(@angular/cli): simplify update command schematic execution
1 parent dc11188 commit ca70a18

File tree

1 file changed

+117
-42
lines changed

1 file changed

+117
-42
lines changed

packages/angular/cli/commands/update-impl.ts

+117-42
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8+
import { normalize, virtualFs } from '@angular-devkit/core';
9+
import { NodeJsSyncHost } from '@angular-devkit/core/node';
10+
import { UnsuccessfulWorkflowExecution } from '@angular-devkit/schematics';
11+
import { NodeWorkflow, validateOptionsWithSchema } from '@angular-devkit/schematics/tools';
812
import { execSync } from 'child_process';
913
import * as fs from 'fs';
1014
import * as path from 'path';
1115
import * as semver from 'semver';
12-
import { Arguments, Option } from '../models/interface';
13-
import { SchematicCommand } from '../models/schematic-command';
16+
import { Command } from '../models/command';
17+
import { Arguments } from '../models/interface';
18+
import { colors } from '../utilities/color';
1419
import { getPackageManager } from '../utilities/package-manager';
1520
import {
1621
PackageIdentifier,
@@ -28,11 +33,96 @@ const npa = require('npm-package-arg');
2833

2934
const oldConfigFileNames = ['.angular-cli.json', 'angular-cli.json'];
3035

31-
export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
36+
export class UpdateCommand extends Command<UpdateCommandSchema> {
3237
public readonly allowMissingWorkspace = true;
3338

34-
async parseArguments(_schematicOptions: string[], _schema: Option[]): Promise<Arguments> {
35-
return {};
39+
private workflow: NodeWorkflow;
40+
41+
async initialize() {
42+
this.workflow = new NodeWorkflow(
43+
new virtualFs.ScopedHost(new NodeJsSyncHost(), normalize(this.workspace.root)),
44+
{
45+
packageManager: await getPackageManager(this.workspace.root),
46+
root: normalize(this.workspace.root),
47+
},
48+
);
49+
50+
this.workflow.engineHost.registerOptionsTransform(
51+
validateOptionsWithSchema(this.workflow.registry),
52+
);
53+
}
54+
55+
async executeSchematic(
56+
collection: string,
57+
schematic: string,
58+
options = {},
59+
): Promise<{ success: boolean; files: Set<string> }> {
60+
let error = false;
61+
const logs: string[] = [];
62+
const files = new Set<string>();
63+
64+
const reporterSubscription = this.workflow.reporter.subscribe(event => {
65+
// Strip leading slash to prevent confusion.
66+
const eventPath = event.path.startsWith('/') ? event.path.substr(1) : event.path;
67+
68+
switch (event.kind) {
69+
case 'error':
70+
error = true;
71+
const desc = event.description == 'alreadyExist' ? 'already exists' : 'does not exist.';
72+
this.logger.warn(`ERROR! ${eventPath} ${desc}.`);
73+
break;
74+
case 'update':
75+
logs.push(`${colors.whiteBright('UPDATE')} ${eventPath} (${event.content.length} bytes)`);
76+
files.add(eventPath);
77+
break;
78+
case 'create':
79+
logs.push(`${colors.green('CREATE')} ${eventPath} (${event.content.length} bytes)`);
80+
files.add(eventPath);
81+
break;
82+
case 'delete':
83+
logs.push(`${colors.yellow('DELETE')} ${eventPath}`);
84+
files.add(eventPath);
85+
break;
86+
case 'rename':
87+
logs.push(`${colors.blue('RENAME')} ${eventPath} => ${event.to}`);
88+
files.add(eventPath);
89+
break;
90+
}
91+
});
92+
93+
const lifecycleSubscription = this.workflow.lifeCycle.subscribe(event => {
94+
if (event.kind == 'end' || event.kind == 'post-tasks-start') {
95+
if (!error) {
96+
// Output the logging queue, no error happened.
97+
logs.forEach(log => this.logger.info(log));
98+
}
99+
}
100+
});
101+
102+
// TODO: Allow passing a schematic instance directly
103+
try {
104+
await this.workflow
105+
.execute({
106+
collection,
107+
schematic,
108+
options,
109+
logger: this.logger,
110+
})
111+
.toPromise();
112+
113+
reporterSubscription.unsubscribe();
114+
lifecycleSubscription.unsubscribe();
115+
116+
return { success: true, files };
117+
} catch (e) {
118+
if (e instanceof UnsuccessfulWorkflowExecution) {
119+
this.logger.error('The update failed. See above.');
120+
} else {
121+
this.logger.fatal(e.message);
122+
}
123+
124+
return { success: !error, files };
125+
}
36126
}
37127

38128
async executeMigrations(
@@ -41,11 +131,11 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
41131
range: semver.Range,
42132
commit = false,
43133
) {
44-
const collection = this.getEngine().createCollection(collectionPath);
134+
const collection = this.workflow.engine.createCollection(collectionPath);
45135

46136
const migrations = [];
47137
for (const name of collection.listSchematicNames()) {
48-
const schematic = this.getEngine().createSchematic(name, collection);
138+
const schematic = this.workflow.engine.createSchematic(name, collection);
49139
const description = schematic.description as typeof schematic.description & {
50140
version?: string;
51141
};
@@ -71,16 +161,8 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
71161
`** Executing migrations for version ${migration.version} of package '${packageName}' **`,
72162
);
73163

74-
// TODO: run the schematic directly; most of the logic in the following is unneeded
75-
const result = await this.runSchematic({
76-
collectionName: migration.collection.name,
77-
schematicName: migration.name,
78-
force: false,
79-
showNothingDone: false,
80-
});
81-
82-
// 1 = failure
83-
if (result === 1) {
164+
const result = await this.executeSchematic(migration.collection.name, migration.name);
165+
if (!result.success) {
84166
if (startingGitSha !== null) {
85167
const currentGitSha = this.findCurrentGitSha();
86168
if (currentGitSha !== startingGitSha) {
@@ -97,7 +179,8 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
97179
if (migration.description) {
98180
message += '\n' + migration.description;
99181
}
100-
this.createCommit([], message);
182+
// TODO: Use result.files once package install tasks are accounted
183+
this.createCommit(message, []);
101184
}
102185
}
103186
}
@@ -192,19 +275,15 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
192275

193276
if (options.all || packages.length === 0) {
194277
// Either update all packages or show status
195-
return this.runSchematic({
196-
collectionName: '@schematics/update',
197-
schematicName: 'update',
198-
dryRun: !!options.dryRun,
199-
showNothingDone: false,
200-
additionalOptions: {
201-
force: options.force || false,
202-
next: options.next || false,
203-
verbose: options.verbose || false,
204-
packageManager,
205-
packages: options.all ? Object.keys(rootDependencies) : [],
206-
},
278+
const { success } = await this.executeSchematic('@schematics/update', 'update', {
279+
force: options.force || false,
280+
next: options.next || false,
281+
verbose: options.verbose || false,
282+
packageManager,
283+
packages: options.all ? Object.keys(rootDependencies) : [],
207284
});
285+
286+
return success ? 0 : 1;
208287
}
209288

210289
if (options.migrateOnly) {
@@ -408,18 +487,14 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
408487
return 0;
409488
}
410489

411-
return this.runSchematic({
412-
collectionName: '@schematics/update',
413-
schematicName: 'update',
414-
dryRun: !!options.dryRun,
415-
showNothingDone: false,
416-
additionalOptions: {
417-
verbose: options.verbose || false,
418-
force: options.force || false,
419-
packageManager,
420-
packages: packagesToUpdate,
421-
},
490+
const { success } = await this.executeSchematic('@schematics/update', 'update', {
491+
verbose: options.verbose || false,
492+
force: options.force || false,
493+
packageManager,
494+
packages: packagesToUpdate,
422495
});
496+
497+
return success ? 0 : 1;
423498
}
424499

425500
checkCleanGit() {
@@ -445,7 +520,7 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
445520
return true;
446521
}
447522

448-
createCommit(files: string[], message: string) {
523+
createCommit(message: string, files: string[]) {
449524
try {
450525
execSync('git add -A ' + files.join(' '), { encoding: 'utf8', stdio: 'pipe' });
451526

0 commit comments

Comments
 (0)