Skip to content

Commit f04f507

Browse files
committed
feat(@angular-devkit/schematics): add lifecycle hooks to workflow
This will allow us to show logs at each new phases, for example, instead of only at the end. Reference: angular#10155
1 parent 3ae5b01 commit f04f507

File tree

3 files changed

+50
-5
lines changed

3 files changed

+50
-5
lines changed

packages/angular_devkit/schematics/src/workflow/interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ export interface WorkflowExecutionContext extends RequiredWorkflowExecutionConte
2121
allowPrivate?: boolean;
2222
}
2323

24+
export interface LifeCycleEvent {
25+
kind: 'start' | 'end' // Start and end of the full workflow execution.
26+
| 'workflow-start' | 'workflow-end' // Start and end of a workflow execution. Can be more.
27+
| 'post-tasks-start' | 'post-tasks-end'; // Start and end of the post tasks execution.
28+
}
29+
2430
export interface Workflow {
2531
readonly context: Readonly<WorkflowExecutionContext>;
2632

packages/angular_devkit/schematics/tools/workflow/node-workflow.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
workflow,
1818
} from '@angular-devkit/schematics'; // tslint:disable-line:no-implicit-dependencies
1919
import { EMPTY, Observable, Subject, concat, of, throwError } from 'rxjs';
20+
import { reduce, tap } from 'rxjs/internal/operators';
2021
import { concatMap, ignoreElements, map } from 'rxjs/operators';
2122
import { NodeModulesEngineHost, validateOptionsWithSchema } from '..';
2223
import { DryRunEvent } from '../../src/sink/dryrun';
@@ -28,6 +29,7 @@ export class NodeWorkflow implements workflow.Workflow {
2829
protected _registry: schema.CoreSchemaRegistry;
2930

3031
protected _reporter: Subject<DryRunEvent> = new Subject();
32+
protected _lifeCycle: Subject<workflow.LifeCycleEvent> = new Subject();
3133

3234
protected _context: workflow.WorkflowExecutionContext[];
3335

@@ -85,12 +87,19 @@ export class NodeWorkflow implements workflow.Workflow {
8587
get reporter(): Observable<DryRunEvent> {
8688
return this._reporter.asObservable();
8789
}
90+
get lifeCycle(): Observable<workflow.LifeCycleEvent> {
91+
return this._lifeCycle.asObservable();
92+
}
8893

8994
execute(
9095
options: Partial<workflow.WorkflowExecutionContext> & workflow.RequiredWorkflowExecutionContext,
9196
): Observable<void> {
9297
const parentContext = this._context[this._context.length - 1];
9398

99+
if (!parentContext) {
100+
this._lifeCycle.next({ kind: 'start' });
101+
}
102+
94103
/** Create the collection and the schematic. */
95104
const collection = this._engine.createCollection(options.collection);
96105
// Only allow private schematics if called from the same collection.
@@ -110,6 +119,8 @@ export class NodeWorkflow implements workflow.Workflow {
110119
error = error || (event.kind == 'error');
111120
});
112121

122+
this._lifeCycle.next({ kind: 'workflow-start' });
123+
113124
const context = {
114125
...options,
115126
debug: options.debug || false,
@@ -145,15 +156,28 @@ export class NodeWorkflow implements workflow.Workflow {
145156
),
146157
concat(new Observable<void>(obs => {
147158
if (!this._options.dryRun) {
148-
this._engine.executePostTasks().subscribe(obs);
159+
this._lifeCycle.next({ kind: 'post-tasks-start' });
160+
this._engine.executePostTasks()
161+
.pipe(
162+
reduce(() => {}),
163+
tap(() => this._lifeCycle.next({ kind: 'post-tasks-end' })),
164+
)
165+
.subscribe(obs);
149166
} else {
150167
obs.complete();
151168
}
152169
})),
153170
concat(new Observable(obs => {
171+
this._lifeCycle.next({ kind: 'workflow-end' });
154172
this._context.pop();
173+
174+
if (this._context.length == 0) {
175+
this._lifeCycle.next({ kind: 'end' });
176+
}
177+
155178
obs.complete();
156179
})),
157-
).pipe(ignoreElements());
180+
reduce(() => {}),
181+
);
158182
}
159183
}

packages/angular_devkit/schematics_cli/bin/schematics.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ let nothingDone = true;
138138
// Logging queue that receives all the messages to show the users. This only get shown when no
139139
// errors happened.
140140
const loggingQueue: string[] = [];
141+
let error = false;
141142

142143
/**
143144
* Logs out dry run events.
@@ -154,6 +155,8 @@ workflow.reporter.subscribe((event: DryRunEvent) => {
154155

155156
switch (event.kind) {
156157
case 'error':
158+
error = true;
159+
157160
const desc = event.description == 'alreadyExist' ? 'already exists' : 'does not exist.';
158161
logger.warn(`ERROR! ${event.path} ${desc}.`);
159162
break;
@@ -177,6 +180,21 @@ workflow.reporter.subscribe((event: DryRunEvent) => {
177180
});
178181

179182

183+
/**
184+
* Listen to lifecycle events of the workflow to flush the logs between each phases.
185+
*/
186+
workflow.lifeCycle.subscribe(event => {
187+
if (event.kind == 'workflow-end' || event.kind == 'post-tasks-start') {
188+
if (!error) {
189+
// Flush the log queue and clean the error state.
190+
loggingQueue.forEach(log => logger.info(log));
191+
}
192+
193+
error = false;
194+
}
195+
});
196+
197+
180198
/**
181199
* Remove every options from argv that we support in schematics itself.
182200
*/
@@ -235,9 +253,6 @@ workflow.execute({
235253
process.exit(1);
236254
},
237255
complete() {
238-
// Output the logging queue, no error happened.
239-
loggingQueue.forEach(log => logger.info(log));
240-
241256
if (nothingDone) {
242257
logger.info('Nothing to be done.');
243258
}

0 commit comments

Comments
 (0)