From 66d6b314d199538411fc45eb1fdc2a8e9fb2ef92 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Tue, 5 Mar 2019 14:51:06 -0800 Subject: [PATCH 1/3] refactor: builders can return non-observable output --- packages/angular_devkit/architect/builders/false.ts | 3 +-- packages/angular_devkit/architect/builders/true.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/angular_devkit/architect/builders/false.ts b/packages/angular_devkit/architect/builders/false.ts index 4c980ae57752..b919d9d58d6d 100644 --- a/packages/angular_devkit/architect/builders/false.ts +++ b/packages/angular_devkit/architect/builders/false.ts @@ -5,10 +5,9 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { of } from 'rxjs'; import { createBuilder } from '../src/index2'; -export default createBuilder(() => of({ +export default createBuilder(() => ({ success: false, error: 'False builder always errors.', })); diff --git a/packages/angular_devkit/architect/builders/true.ts b/packages/angular_devkit/architect/builders/true.ts index cbb1e2ee55a3..0837cce4e08d 100644 --- a/packages/angular_devkit/architect/builders/true.ts +++ b/packages/angular_devkit/architect/builders/true.ts @@ -5,7 +5,6 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { of } from 'rxjs'; import { createBuilder } from '../src/index2'; -export default createBuilder(() => of({ success: true })); +export default createBuilder(() => ({ success: true })); From a50bc0a66a2bde82cecbee220d610c71bd815c32 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Tue, 5 Mar 2019 14:52:02 -0800 Subject: [PATCH 2/3] refactor(@angular-devkit/build-angular): remove useless variable rename --- .../build_angular/src/tslint/index2.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/tslint/index2.ts b/packages/angular_devkit/build_angular/src/tslint/index2.ts index c301628db64e..ae1299321b34 100644 --- a/packages/angular_devkit/build_angular/src/tslint/index2.ts +++ b/packages/angular_devkit/build_angular/src/tslint/index2.ts @@ -37,15 +37,17 @@ async function _loadTslint() { } -async function _run(config: TslintBuilderOptions, context: BuilderContext): Promise { +async function _run( + options: TslintBuilderOptions, + context: BuilderContext, +): Promise { const systemRoot = context.workspaceRoot; process.chdir(context.currentDirectory); - const options = config; - const projectName = context.target && context.target.project || ''; + const projectName = (context.target && context.target.project) || ''; // Print formatter output only for non human-readable formats. - const printInfo = ['prose', 'verbose', 'stylish'].includes(options.format || '') - && !options.silent; + const printInfo = + ['prose', 'verbose', 'stylish'].includes(options.format || '') && !options.silent; context.reportStatus(`Linting ${JSON.stringify(projectName)}...`); if (printInfo) { @@ -72,8 +74,14 @@ async function _run(config: TslintBuilderOptions, context: BuilderContext): Prom let i = 0; for (const program of allPrograms) { - const partial - = await _lint(projectTslint, systemRoot, tslintConfigPath, options, program, allPrograms); + const partial = await _lint( + projectTslint, + systemRoot, + tslintConfigPath, + options, + program, + allPrograms, + ); if (result === undefined) { result = partial; } else { From c9facc93209ad7dbba0ecf576386de2e0f0f04d5 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Tue, 5 Mar 2019 14:59:33 -0800 Subject: [PATCH 3/3] feat(@angular-devkit/architect): QoL changes for builders Add a scheduling options to scheduleTarget and Builder on the context so builders can schedule sub-builds and override the logger. Add a getTargetOptions() for builders to get access to options from the host for a specific target. This allows builders to get options, override some, then scheduleBuilder with those new options, for example. --- packages/angular_devkit/architect/src/api.ts | 24 +++++++++ .../angular_devkit/architect/src/architect.ts | 53 +++++++++++++------ .../architect/src/create-builder.ts | 25 ++++++--- .../architect/src/index2_spec.ts | 39 ++++++++++++++ 4 files changed, 118 insertions(+), 23 deletions(-) diff --git a/packages/angular_devkit/architect/src/api.ts b/packages/angular_devkit/architect/src/api.ts index 6aabb6160448..fb600e07e5bc 100644 --- a/packages/angular_devkit/architect/src/api.ts +++ b/packages/angular_devkit/architect/src/api.ts @@ -99,6 +99,17 @@ export interface BuilderRun { stop(): Promise; } +/** + * Additional optional scheduling options. + */ +export interface ScheduleOptions { + /** + * Logger to pass to the builder. Note that messages will stop being forwarded, and if you want + * to log a builder scheduled from your builder you should forward log events yourself. + */ + logger?: logging.Logger; +} + /** * The context received as a second argument in your builder. */ @@ -148,11 +159,13 @@ export interface BuilderContext { * Targets are considered the same if the project, the target AND the configuration are the same. * @param target The target to schedule. * @param overrides A set of options to override the workspace set of options. + * @param scheduleOptions Additional optional scheduling options. * @return A promise of a run. It will resolve when all the members of the run are available. */ scheduleTarget( target: Target, overrides?: json.JsonObject, + scheduleOptions?: ScheduleOptions, ): Promise; /** @@ -160,13 +173,24 @@ export interface BuilderContext { * @param builderName The name of the builder, ie. its `packageName:builderName` tuple. * @param options All options to use for the builder (by default empty object). There is no * additional options added, e.g. from the workspace. + * @param scheduleOptions Additional optional scheduling options. * @return A promise of a run. It will resolve when all the members of the run are available. */ scheduleBuilder( builderName: string, options?: json.JsonObject, + scheduleOptions?: ScheduleOptions, ): Promise; + /** + * Resolve and return options for a specified target. If the target isn't defined in the + * workspace this will reject the promise. This object will be read directly from the workspace + * but not validated against the builder of the target. + * @param target The target to resolve the options of. + * @return A non-validated object resolved from the workspace. + */ + getTargetOptions(target: Target): Promise; + /** * Set the builder to running. This should be used if an external event triggered a re-run, * e.g. a file watched was changed. diff --git a/packages/angular_devkit/architect/src/architect.ts b/packages/angular_devkit/architect/src/architect.ts index 36f46a7eca1e..a76dfbff9bc2 100644 --- a/packages/angular_devkit/architect/src/architect.ts +++ b/packages/angular_devkit/architect/src/architect.ts @@ -108,7 +108,7 @@ export interface ScheduleOptions { /** * A JobRegistry that resolves builder targets from the host. */ -export class ArchitectBuilderJobRegistry implements BuilderRegistry { +class ArchitectBuilderJobRegistry implements BuilderRegistry { constructor( protected _host: ArchitectHost, protected _registry: json.schema.SchemaRegistry, @@ -172,18 +172,16 @@ export class ArchitectBuilderJobRegistry implements BuilderRegistry { return result; } - get< - A extends json.JsonObject, - I extends BuilderInput, - O extends BuilderOutput, - >(name: string): Observable | null> { + get( + name: string, + ): Observable | null> { const m = name.match(/^([^:]+):([^:]+)$/i); if (!m) { return of(null); } return from(this._resolveBuilder(name)).pipe( - concatMap(builderInfo => builderInfo ? this._createBuilder(builderInfo) : of(null)), + concatMap(builderInfo => (builderInfo ? this._createBuilder(builderInfo) : of(null))), first(null, null), ) as Observable | null>; } @@ -192,12 +190,10 @@ export class ArchitectBuilderJobRegistry implements BuilderRegistry { /** * A JobRegistry that resolves targets from the host. */ -export class ArchitectTargetJobRegistry extends ArchitectBuilderJobRegistry { - get< - A extends json.JsonObject, - I extends BuilderInput, - O extends BuilderOutput, - >(name: string): Observable | null> { +class ArchitectTargetJobRegistry extends ArchitectBuilderJobRegistry { + get( + name: string, + ): Observable | null> { const m = name.match(/^{([^:]+):([^:]+)(?::([^:]*))?}$/i); if (!m) { return of(null); @@ -209,10 +205,12 @@ export class ArchitectTargetJobRegistry extends ArchitectBuilderJobRegistry { configuration: m[3], }; - return from(Promise.all([ - this._host.getBuilderNameForTarget(target), - this._host.getOptionsForTarget(target), - ])).pipe( + return from( + Promise.all([ + this._host.getBuilderNameForTarget(target), + this._host.getOptionsForTarget(target), + ]), + ).pipe( concatMap(([builderStr, options]) => { if (builderStr === null || options === null) { return of(null); @@ -234,6 +232,22 @@ export class ArchitectTargetJobRegistry extends ArchitectBuilderJobRegistry { } +function _getTargetOptionsFactory(host: ArchitectHost) { + return experimental.jobs.createJobHandler( + target => { + return host.getOptionsForTarget(target).then(options => { + return options || {}; + }); + }, + { + name: '..getTargetOptions', + output: { type: 'object' }, + argument: inputSchema.properties.target, + }, + ); +} + + export class Architect { private readonly _scheduler: experimental.jobs.Scheduler; private readonly _jobCache = new Map>(); @@ -244,9 +258,14 @@ export class Architect { private _registry: json.schema.SchemaRegistry = new json.schema.CoreSchemaRegistry(), additionalJobRegistry?: experimental.jobs.Registry, ) { + const privateArchitectJobRegistry = new experimental.jobs.SimpleJobRegistry(); + // Create private jobs. + privateArchitectJobRegistry.register(_getTargetOptionsFactory(_host)); + const jobRegistry = new experimental.jobs.FallbackRegistry([ new ArchitectTargetJobRegistry(_host, _registry, this._jobCache, this._infoCache), new ArchitectBuilderJobRegistry(_host, _registry, this._jobCache, this._infoCache), + privateArchitectJobRegistry, ...(additionalJobRegistry ? [additionalJobRegistry] : []), ] as experimental.jobs.Registry[]); diff --git a/packages/angular_devkit/architect/src/create-builder.ts b/packages/angular_devkit/architect/src/create-builder.ts index 8e3e5e7bcfc8..9613bd5cbcc5 100644 --- a/packages/angular_devkit/architect/src/create-builder.ts +++ b/packages/angular_devkit/architect/src/create-builder.ts @@ -16,6 +16,7 @@ import { BuilderOutput, BuilderOutputLike, BuilderProgressState, + ScheduleOptions, Target, TypedBuilderProgress, targetStringFromTarget, @@ -96,10 +97,14 @@ export function createBuilder< target: i.target as Target, logger: logger, id: i.id, - async scheduleTarget(target: Target, overrides: json.JsonObject = {}) { + async scheduleTarget( + target: Target, + overrides: json.JsonObject = {}, + scheduleOptions: ScheduleOptions = {}, + ) { const run = await scheduleByTarget(target, overrides, { scheduler, - logger: logger.createChild(''), + logger: scheduleOptions.logger || logger.createChild(''), workspaceRoot: i.workspaceRoot, currentDirectory: i.currentDirectory, }); @@ -109,10 +114,14 @@ export function createBuilder< return run; }, - async scheduleBuilder(builderName: string, options: json.JsonObject = {}) { + async scheduleBuilder( + builderName: string, + options: json.JsonObject = {}, + scheduleOptions: ScheduleOptions = {}, + ) { const run = await scheduleByName(builderName, options, { scheduler, - logger: logger.createChild(''), + logger: scheduleOptions.logger || logger.createChild(''), workspaceRoot: i.workspaceRoot, currentDirectory: i.currentDirectory, }); @@ -122,18 +131,22 @@ export function createBuilder< return run; }, + async getTargetOptions(target: Target) { + return scheduler.schedule( + '..getTargetOptions', target).output.toPromise(); + }, reportRunning() { switch (currentState) { case BuilderProgressState.Waiting: case BuilderProgressState.Stopped: - progress({state: BuilderProgressState.Running, current: 0, total}, context); + progress({ state: BuilderProgressState.Running, current: 0, total }, context); break; } }, reportStatus(status: string) { switch (currentState) { case BuilderProgressState.Running: - progress({ state: currentState, status, current, total }, context); + progress({ state: currentState, status, current, total }, context); break; case BuilderProgressState.Waiting: progress({ state: currentState, status }, context); diff --git a/packages/angular_devkit/architect/src/index2_spec.ts b/packages/angular_devkit/architect/src/index2_spec.ts index 1e64b06a2beb..e339e1b6933e 100644 --- a/packages/angular_devkit/architect/src/index2_spec.ts +++ b/packages/angular_devkit/architect/src/index2_spec.ts @@ -13,6 +13,7 @@ import { BuilderOutput, BuilderRun } from './api'; import { Architect } from './architect'; import { createBuilder } from './create-builder'; +// tslint:disable-next-line:no-big-function describe('architect', () => { let testArchitectHost: TestingArchitectHost; let architect: Architect; @@ -185,4 +186,42 @@ describe('architect', () => { await run.stop(); } }); + + it('exposes getTargetOptions() properly', async () => { + const goldenOptions = { + value: 'value', + }; + let options = {} as object; + + const target = { + project: 'project', + target: 'target', + }; + testArchitectHost.addTarget(target, 'package:target', goldenOptions); + + testArchitectHost.addBuilder('package:getTargetOptions', createBuilder(async (_, context) => { + options = await context.getTargetOptions(target); + + return { success: true }; + })); + + const run = await architect.scheduleBuilder('package:getTargetOptions', {}); + const output = await run.output.toPromise(); + expect(output.success).toBe(true); + expect(options).toEqual(goldenOptions); + await run.stop(); + + // Use an invalid target and check for error. + target.target = 'invalid'; + options = {}; + + // This should not error. + const run2 = await architect.scheduleBuilder('package:getTargetOptions', {}); + + // But this should. + try { + await run2.output.toPromise(); + } catch {} + await run2.stop(); + }); });