6
6
* Use of this source code is governed by an MIT-style license that can be
7
7
* found in the LICENSE file at https://angular.io/license
8
8
*/
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' ;
15
20
import { NodeJsSyncHost , createConsoleLogger } from '@angular-devkit/core/node' ;
16
21
import { existsSync , readFileSync } from 'fs' ;
17
22
import * as minimist from 'minimist' ;
18
23
import * as path from 'path' ;
19
- import { throwError } from 'rxjs' ;
20
- import { concatMap } from 'rxjs/operators' ;
24
+ import { MultiProgressBar } from '../src/progress' ;
21
25
22
26
23
27
function findUp ( names : string | string [ ] , from : string ) {
@@ -44,7 +48,7 @@ function findUp(names: string | string[], from: string) {
44
48
/**
45
49
* Show usage of the CLI tool, and exit the process.
46
50
*/
47
- function usage ( exitCode = 0 ) : never {
51
+ function usage ( logger : logging . Logger , exitCode = 0 ) : never {
48
52
logger . info ( tags . stripIndent `
49
53
architect [project][:target][:configuration] [options, ...]
50
54
@@ -63,86 +67,165 @@ function usage(exitCode = 0): never {
63
67
throw 0 ; // The node typing sometimes don't have a never type for process.exit().
64
68
}
65
69
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
+ }
68
73
69
- /** Create the DevKit Logger used through the CLI. */
70
- const logger = createConsoleLogger ( argv [ 'verbose' ] ) ;
71
74
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;
77
79
}
78
80
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
- }
84
81
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
+ }
101
175
}
102
176
103
- const root = dirname ( normalize ( configFilePath ) ) ;
104
- const configContent = readFileSync ( configFilePath , 'utf-8' ) ;
105
- const workspaceJson = JSON . parse ( configContent ) ;
106
177
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'] });
109
181
110
- let lastBuildEvent = { success : true } ;
182
+ /** Create the DevKit Logger used through the CLI. */
183
+ const logger = createConsoleLogger(argv['verbose']);
111
184
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
+ }
115
191
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
+ ];
119
200
120
- const targetSpec = {
121
- project,
122
- target : targetName ,
123
- configuration,
124
- overrides,
125
- } ;
201
+ const configFilePath = findUp(configFileNames, currentPath);
126
202
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 . `) ;
134
206
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