66 * Use of this source code is governed by an MIT-style license that can be
77 * found in the LICENSE file at https://angular.io/license
88 */
9-
10- import 'symbol-observable' ;
11- // symbol polyfill must go first
12- // tslint:disable-next-line:ordered-imports import-groups
13- import { Architect } from '@angular-devkit/architect' ;
14- import { dirname , experimental , normalize , tags } from '@angular-devkit/core' ;
9+ import { index2 } from '@angular-devkit/architect' ;
10+ import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node' ;
11+ import {
12+ dirname ,
13+ experimental ,
14+ json ,
15+ logging ,
16+ normalize ,
17+ schema ,
18+ tags , terminal ,
19+ } from '@angular-devkit/core' ;
1520import { NodeJsSyncHost , createConsoleLogger } from '@angular-devkit/core/node' ;
1621import { existsSync , readFileSync } from 'fs' ;
1722import * as minimist from 'minimist' ;
1823import * as path from 'path' ;
19- import { throwError } from 'rxjs' ;
20- import { concatMap } from 'rxjs/operators' ;
24+ import { MultiProgressBar } from '../src/progress' ;
2125
2226
2327function findUp ( names : string | string [ ] , from : string ) {
@@ -44,7 +48,7 @@ function findUp(names: string | string[], from: string) {
4448/**
4549 * Show usage of the CLI tool, and exit the process.
4650 */
47- function usage ( exitCode = 0 ) : never {
51+ function usage ( logger : logging . Logger , exitCode = 0 ) : never {
4852 logger . info ( tags . stripIndent `
4953 architect [project][:target][:configuration] [options, ...]
5054
@@ -63,86 +67,165 @@ function usage(exitCode = 0): never {
6367 throw 0 ; // The node typing sometimes don't have a never type for process.exit().
6468}
6569
66- /** Parse the command line. */
67- const argv = minimist ( process . argv . slice ( 2 ) , { boolean : [ 'help' ] } ) ;
70+ function _targetStringFromTarget ( { project, target, configuration} : index2 . Target ) {
71+ return `${project } :${target } ${configuration !== undefined ? ':' + configuration : ''} `;
72+ }
6873
69- /** Create the DevKit Logger used through the CLI. */
70- const logger = createConsoleLogger ( argv [ 'verbose' ] ) ;
7174
72- // Check the target.
73- const targetStr = argv . _ . shift ( ) ;
74- if ( ! targetStr && argv . help ) {
75- // Show architect usage if there's no target.
76- usage ( ) ;
75+ interface BarInfo {
76+ status?: string;
77+ builder: index2.BuilderInfo;
78+ target?: index2.Target;
7779}
7880
79- // Split a target into its parts.
80- let project : string , targetName : string , configuration : string ;
81- if ( targetStr ) {
82- [ project , targetName , configuration ] = targetStr . split ( ':' ) ;
83- }
8481
85- // Load workspace configuration file.
86- const currentPath = process . cwd ( ) ;
87- const configFileNames = [
88- 'angular.json' ,
89- '.angular.json' ,
90- 'workspace.json' ,
91- '.workspace.json' ,
92- ] ;
93-
94- const configFilePath = findUp ( configFileNames , currentPath ) ;
95-
96- if ( ! configFilePath ) {
97- logger . fatal ( `Workspace configuration file (${ configFileNames . join ( ', ' ) } ) cannot be found in `
98- + `'${ currentPath } ' or in parent directories.` ) ;
99- process . exit ( 3 ) ;
100- throw 3 ; // TypeScript doesn't know that process.exit() never returns.
82+ async function _executeTarget(
83+ parentLogger: logging.Logger,
84+ workspace: experimental.workspace.Workspace,
85+ root: string,
86+ argv: minimist.ParsedArgs,
87+ registry: json.schema.SchemaRegistry,
88+ ) {
89+ const architectHost = new WorkspaceNodeModulesArchitectHost(workspace, root);
90+ const architect = new index2.Architect(architectHost, registry);
91+
92+ // Split a target into its parts.
93+ const targetStr = argv._.shift() || '';
94+ const [project, target, configuration] = targetStr.split(':');
95+ const targetSpec = { project, target, configuration };
96+
97+ delete argv['help'];
98+ delete argv['_'];
99+
100+ const logger = new logging.Logger('jobs');
101+ const logs: logging.LogEntry[] = [];
102+ logger.subscribe(entry => logs.push({ ...entry, message: ` $ { entry . name } : ` + entry.message }));
103+
104+ const run = await architect.scheduleTarget(targetSpec, argv, { logger });
105+ const bars = new MultiProgressBar<number, BarInfo>(':name :bar (:current/:total) :status');
106+
107+ run.progress.subscribe(
108+ update => {
109+ const data = bars.get(update.id) || {
110+ id: update.id,
111+ builder: update.builder,
112+ target: update.target,
113+ status: update.status || '',
114+ name: ((update.target ? _targetStringFromTarget(update.target) : update.builder.name)
115+ + ' '.repeat(80)
116+ ).substr(0, 40),
117+ };
118+
119+ if (update.status !== undefined) {
120+ data.status = update.status;
121+ }
122+
123+ switch (update.state) {
124+ case index2.BuilderProgressState.Error:
125+ data.status = 'Error: ' + update.error;
126+ bars.update(update.id, data);
127+ break;
128+
129+ case index2.BuilderProgressState.Stopped:
130+ data.status = 'Done.';
131+ bars.complete(update.id);
132+ bars.update(update.id, data, update.total, update.total);
133+ break;
134+
135+ case index2.BuilderProgressState.Waiting:
136+ bars.update(update.id, data);
137+ break;
138+
139+ case index2.BuilderProgressState.Running:
140+ bars.update(update.id, data, update.current, update.total);
141+ break;
142+ }
143+
144+ bars.render();
145+ },
146+ );
147+
148+ // Wait for full completion of the builder.
149+ try {
150+ const result = await run.result;
151+
152+ if (result.success) {
153+ parentLogger.info(terminal.green('SUCCESS'));
154+ } else {
155+ parentLogger.info(terminal.yellow('FAILURE'));
156+ }
157+
158+ parentLogger.info('\nLogs:');
159+ logs.forEach(l => parentLogger.next(l));
160+
161+ await run.stop();
162+ bars.terminate();
163+
164+ return result.success ? 0 : 1;
165+ } catch (err) {
166+ parentLogger.info(terminal.red('ERROR'));
167+ parentLogger.info('\nLogs:');
168+ logs.forEach(l => parentLogger.next(l));
169+
170+ parentLogger.fatal('Exception:');
171+ parentLogger.fatal(err.stack);
172+
173+ return 2;
174+ }
101175}
102176
103- const root = dirname ( normalize ( configFilePath ) ) ;
104- const configContent = readFileSync ( configFilePath , 'utf-8' ) ;
105- const workspaceJson = JSON . parse ( configContent ) ;
106177
107- const host = new NodeJsSyncHost ( ) ;
108- const workspace = new experimental . workspace . Workspace ( root , host ) ;
178+ async function main(args: string[]): Promise<number> {
179+ /** Parse the command line. */
180+ const argv = minimist(args, { boolean: ['help'] });
109181
110- let lastBuildEvent = { success : true } ;
182+ /** Create the DevKit Logger used through the CLI. */
183+ const logger = createConsoleLogger(argv['verbose']);
111184
112- workspace . loadWorkspaceFromJson ( workspaceJson ) . pipe (
113- concatMap ( ws => new Architect ( ws ) . loadArchitect ( ) ) ,
114- concatMap ( architect => {
185+ // Check the target.
186+ const targetStr = argv._[0] || '';
187+ if (!targetStr || argv.help) {
188+ // Show architect usage if there's no target.
189+ usage(logger);
190+ }
115191
116- const overrides = { ...argv } ;
117- delete overrides [ 'help' ] ;
118- delete overrides [ '_' ] ;
192+ // Load workspace configuration file.
193+ const currentPath = process.cwd();
194+ const configFileNames = [
195+ 'angular.json',
196+ '.angular.json',
197+ 'workspace.json',
198+ '.workspace.json',
199+ ];
119200
120- const targetSpec = {
121- project,
122- target : targetName ,
123- configuration,
124- overrides,
125- } ;
201+ const configFilePath = findUp(configFileNames, currentPath);
126202
127- // TODO: better logging of what's happening.
128- if ( argv . help ) {
129- // TODO: add target help
130- return throwError ( 'Target help NYI.' ) ;
131- // architect.help(targetOptions, logger);
132- } else {
133- const builderConfig = architect . getBuilderConfiguration ( targetSpec ) ;
203+ if (!configFilePath) {
204+ logger.fatal(` Workspace configuration file ( $ { configFileNames . join ( ', ' ) } ) cannot be found in `
205+ + ` '${currentPath}' or in parent directories . `) ;
134206
135- return architect . run ( builderConfig , { logger } ) ;
136- }
137- } ) ,
138- ) . subscribe ( {
139- next : ( buildEvent => lastBuildEvent = buildEvent ) ,
140- complete : ( ) => process . exit ( lastBuildEvent . success ? 0 : 1 ) ,
141- error : ( err : Error ) => {
142- logger . fatal ( err . message ) ;
143- if ( err . stack ) {
144- logger . fatal ( err . stack ) ;
145- }
146- process . exit ( 1 ) ;
147- } ,
148- } ) ;
207+ return 3 ;
208+ }
209+
210+ const root = dirname ( normalize ( configFilePath ) ) ;
211+ const configContent = readFileSync ( configFilePath , 'utf-8' ) ;
212+ const workspaceJson = JSON . parse ( configContent ) ;
213+
214+ const registry = new schema . CoreSchemaRegistry ( ) ;
215+ registry . addPostTransform ( schema . transforms . addUndefinedDefaults ) ;
216+
217+ const host = new NodeJsSyncHost ( ) ;
218+ const workspace = new experimental . workspace . Workspace ( root , host ) ;
219+
220+ await workspace . loadWorkspaceFromJson ( workspaceJson ) . toPromise ( ) ;
221+
222+ return await _executeTarget ( logger , workspace , root , argv , registry ) ;
223+ }
224+
225+ main ( process . argv . slice ( 2 ) )
226+ . then ( code => {
227+ process . exit ( code ) ;
228+ } , err => {
229+ console . error ( 'Error: ' + err . stack || err . message || err ) ;
230+ process . exit ( - 1 ) ;
231+ } ) ;
0 commit comments