Skip to content

Commit f06493c

Browse files
committed
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.
1 parent 61adf9a commit f06493c

File tree

4 files changed

+118
-23
lines changed

4 files changed

+118
-23
lines changed

packages/angular_devkit/architect/src/api.ts

+24
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ export interface BuilderRun {
9999
stop(): Promise<void>;
100100
}
101101

102+
/**
103+
* Additional optional scheduling options.
104+
*/
105+
export interface ScheduleOptions {
106+
/**
107+
* Logger to pass to the builder. Note that messages will stop being forwarded, and if you want
108+
* to log a builder scheduled from your builder you should forward log events yourself.
109+
*/
110+
logger?: logging.Logger;
111+
}
112+
102113
/**
103114
* The context received as a second argument in your builder.
104115
*/
@@ -148,25 +159,38 @@ export interface BuilderContext {
148159
* Targets are considered the same if the project, the target AND the configuration are the same.
149160
* @param target The target to schedule.
150161
* @param overrides A set of options to override the workspace set of options.
162+
* @param scheduleOptions Additional optional scheduling options.
151163
* @return A promise of a run. It will resolve when all the members of the run are available.
152164
*/
153165
scheduleTarget(
154166
target: Target,
155167
overrides?: json.JsonObject,
168+
scheduleOptions?: ScheduleOptions,
156169
): Promise<BuilderRun>;
157170

158171
/**
159172
* Schedule a builder by its name. This can be the same builder that is being executed.
160173
* @param builderName The name of the builder, ie. its `packageName:builderName` tuple.
161174
* @param options All options to use for the builder (by default empty object). There is no
162175
* additional options added, e.g. from the workspace.
176+
* @param scheduleOptions Additional optional scheduling options.
163177
* @return A promise of a run. It will resolve when all the members of the run are available.
164178
*/
165179
scheduleBuilder(
166180
builderName: string,
167181
options?: json.JsonObject,
182+
scheduleOptions?: ScheduleOptions,
168183
): Promise<BuilderRun>;
169184

185+
/**
186+
* Resolve and return options for a specified target. If the target isn't defined in the
187+
* workspace this will reject the promise. This object will be read directly from the workspace
188+
* but not validated against the builder of the target.
189+
* @param target The target to resolve the options of.
190+
* @return A non-validated object resolved from the workspace.
191+
*/
192+
getTargetOptions(target: Target): Promise<json.JsonObject>;
193+
170194
/**
171195
* Set the builder to running. This should be used if an external event triggered a re-run,
172196
* e.g. a file watched was changed.

packages/angular_devkit/architect/src/architect.ts

+36-17
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export interface ScheduleOptions {
108108
/**
109109
* A JobRegistry that resolves builder targets from the host.
110110
*/
111-
export class ArchitectBuilderJobRegistry implements BuilderRegistry {
111+
class ArchitectBuilderJobRegistry implements BuilderRegistry {
112112
constructor(
113113
protected _host: ArchitectHost,
114114
protected _registry: json.schema.SchemaRegistry,
@@ -172,18 +172,16 @@ export class ArchitectBuilderJobRegistry implements BuilderRegistry {
172172
return result;
173173
}
174174

175-
get<
176-
A extends json.JsonObject,
177-
I extends BuilderInput,
178-
O extends BuilderOutput,
179-
>(name: string): Observable<experimental.jobs.JobHandler<A, I, O> | null> {
175+
get<A extends json.JsonObject, I extends BuilderInput, O extends BuilderOutput>(
176+
name: string,
177+
): Observable<experimental.jobs.JobHandler<A, I, O> | null> {
180178
const m = name.match(/^([^:]+):([^:]+)$/i);
181179
if (!m) {
182180
return of(null);
183181
}
184182

185183
return from(this._resolveBuilder(name)).pipe(
186-
concatMap(builderInfo => builderInfo ? this._createBuilder(builderInfo) : of(null)),
184+
concatMap(builderInfo => (builderInfo ? this._createBuilder(builderInfo) : of(null))),
187185
first(null, null),
188186
) as Observable<experimental.jobs.JobHandler<A, I, O> | null>;
189187
}
@@ -192,12 +190,10 @@ export class ArchitectBuilderJobRegistry implements BuilderRegistry {
192190
/**
193191
* A JobRegistry that resolves targets from the host.
194192
*/
195-
export class ArchitectTargetJobRegistry extends ArchitectBuilderJobRegistry {
196-
get<
197-
A extends json.JsonObject,
198-
I extends BuilderInput,
199-
O extends BuilderOutput,
200-
>(name: string): Observable<experimental.jobs.JobHandler<A, I, O> | null> {
193+
class ArchitectTargetJobRegistry extends ArchitectBuilderJobRegistry {
194+
get<A extends json.JsonObject, I extends BuilderInput, O extends BuilderOutput>(
195+
name: string,
196+
): Observable<experimental.jobs.JobHandler<A, I, O> | null> {
201197
const m = name.match(/^{([^:]+):([^:]+)(?::([^:]*))?}$/i);
202198
if (!m) {
203199
return of(null);
@@ -209,10 +205,12 @@ export class ArchitectTargetJobRegistry extends ArchitectBuilderJobRegistry {
209205
configuration: m[3],
210206
};
211207

212-
return from(Promise.all([
213-
this._host.getBuilderNameForTarget(target),
214-
this._host.getOptionsForTarget(target),
215-
])).pipe(
208+
return from(
209+
Promise.all([
210+
this._host.getBuilderNameForTarget(target),
211+
this._host.getOptionsForTarget(target),
212+
]),
213+
).pipe(
216214
concatMap(([builderStr, options]) => {
217215
if (builderStr === null || options === null) {
218216
return of(null);
@@ -234,6 +232,22 @@ export class ArchitectTargetJobRegistry extends ArchitectBuilderJobRegistry {
234232
}
235233

236234

235+
function _getTargetOptionsFactory(host: ArchitectHost) {
236+
return experimental.jobs.createJobHandler<Target, json.JsonValue, json.JsonObject>(
237+
target => {
238+
return host.getOptionsForTarget(target).then(options => {
239+
return options || {};
240+
});
241+
},
242+
{
243+
name: '..getTargetOptions',
244+
output: { type: 'object' },
245+
argument: inputSchema.properties.target,
246+
},
247+
);
248+
}
249+
250+
237251
export class Architect {
238252
private readonly _scheduler: experimental.jobs.Scheduler;
239253
private readonly _jobCache = new Map<string, Observable<BuilderJobHandler>>();
@@ -244,9 +258,14 @@ export class Architect {
244258
private _registry: json.schema.SchemaRegistry = new json.schema.CoreSchemaRegistry(),
245259
additionalJobRegistry?: experimental.jobs.Registry,
246260
) {
261+
const privateArchitectJobRegistry = new experimental.jobs.SimpleJobRegistry();
262+
// Create private jobs.
263+
privateArchitectJobRegistry.register(_getTargetOptionsFactory(_host));
264+
247265
const jobRegistry = new experimental.jobs.FallbackRegistry([
248266
new ArchitectTargetJobRegistry(_host, _registry, this._jobCache, this._infoCache),
249267
new ArchitectBuilderJobRegistry(_host, _registry, this._jobCache, this._infoCache),
268+
privateArchitectJobRegistry,
250269
...(additionalJobRegistry ? [additionalJobRegistry] : []),
251270
] as experimental.jobs.Registry[]);
252271

packages/angular_devkit/architect/src/create-builder.ts

+19-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
BuilderOutput,
1717
BuilderOutputLike,
1818
BuilderProgressState,
19+
ScheduleOptions,
1920
Target,
2021
TypedBuilderProgress,
2122
targetStringFromTarget,
@@ -96,10 +97,14 @@ export function createBuilder<
9697
target: i.target as Target,
9798
logger: logger,
9899
id: i.id,
99-
async scheduleTarget(target: Target, overrides: json.JsonObject = {}) {
100+
async scheduleTarget(
101+
target: Target,
102+
overrides: json.JsonObject = {},
103+
scheduleOptions: ScheduleOptions = {},
104+
) {
100105
const run = await scheduleByTarget(target, overrides, {
101106
scheduler,
102-
logger: logger.createChild(''),
107+
logger: scheduleOptions.logger || logger.createChild(''),
103108
workspaceRoot: i.workspaceRoot,
104109
currentDirectory: i.currentDirectory,
105110
});
@@ -109,10 +114,14 @@ export function createBuilder<
109114

110115
return run;
111116
},
112-
async scheduleBuilder(builderName: string, options: json.JsonObject = {}) {
117+
async scheduleBuilder(
118+
builderName: string,
119+
options: json.JsonObject = {},
120+
scheduleOptions: ScheduleOptions = {},
121+
) {
113122
const run = await scheduleByName(builderName, options, {
114123
scheduler,
115-
logger: logger.createChild(''),
124+
logger: scheduleOptions.logger || logger.createChild(''),
116125
workspaceRoot: i.workspaceRoot,
117126
currentDirectory: i.currentDirectory,
118127
});
@@ -122,18 +131,22 @@ export function createBuilder<
122131

123132
return run;
124133
},
134+
async getTargetOptions(target: Target) {
135+
return scheduler.schedule<Target, json.JsonValue, json.JsonObject>(
136+
'..getTargetOptions', target).output.toPromise();
137+
},
125138
reportRunning() {
126139
switch (currentState) {
127140
case BuilderProgressState.Waiting:
128141
case BuilderProgressState.Stopped:
129-
progress({state: BuilderProgressState.Running, current: 0, total}, context);
142+
progress({ state: BuilderProgressState.Running, current: 0, total }, context);
130143
break;
131144
}
132145
},
133146
reportStatus(status: string) {
134147
switch (currentState) {
135148
case BuilderProgressState.Running:
136-
progress({ state: currentState, status, current, total }, context);
149+
progress({ state: currentState, status, current, total }, context);
137150
break;
138151
case BuilderProgressState.Waiting:
139152
progress({ state: currentState, status }, context);

packages/angular_devkit/architect/src/index2_spec.ts

+39
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { BuilderOutput, BuilderRun } from './api';
1313
import { Architect } from './architect';
1414
import { createBuilder } from './create-builder';
1515

16+
// tslint:disable-next-line:no-big-function
1617
describe('architect', () => {
1718
let testArchitectHost: TestingArchitectHost;
1819
let architect: Architect;
@@ -185,4 +186,42 @@ describe('architect', () => {
185186
await run.stop();
186187
}
187188
});
189+
190+
it('exposes getTargetOptions() properly', async () => {
191+
const goldenOptions = {
192+
value: 'value',
193+
};
194+
let options = {} as object;
195+
196+
const target = {
197+
project: 'project',
198+
target: 'target',
199+
};
200+
testArchitectHost.addTarget(target, 'package:target', goldenOptions);
201+
202+
testArchitectHost.addBuilder('package:getTargetOptions', createBuilder(async (_, context) => {
203+
options = await context.getTargetOptions(target);
204+
205+
return { success: true };
206+
}));
207+
208+
const run = await architect.scheduleBuilder('package:getTargetOptions', {});
209+
const output = await run.output.toPromise();
210+
expect(output.success).toBe(true);
211+
expect(options).toEqual(goldenOptions);
212+
await run.stop();
213+
214+
// Use an invalid target and check for error.
215+
target.target = 'invalid';
216+
options = {};
217+
218+
// This should not error.
219+
const run2 = await architect.scheduleBuilder('package:getTargetOptions', {});
220+
221+
// But this should.
222+
try {
223+
await run2.output.toPromise();
224+
} catch {}
225+
await run2.stop();
226+
});
188227
});

0 commit comments

Comments
 (0)