UNPKG

11.9 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright Google LLC All Rights Reserved.
5 *
6 * Use of this source code is governed by an MIT-style license that can be
7 * found in the LICENSE file at https://angular.io/license
8 */
9Object.defineProperty(exports, "__esModule", { value: true });
10exports.Architect = void 0;
11const core_1 = require("@angular-devkit/core");
12const rxjs_1 = require("rxjs");
13const operators_1 = require("rxjs/operators");
14const api_1 = require("./api");
15const jobs_1 = require("./jobs");
16const schedule_by_name_1 = require("./schedule-by-name");
17const inputSchema = require('./input-schema.json');
18const outputSchema = require('./output-schema.json');
19function _createJobHandlerFromBuilderInfo(info, target, host, registry, baseOptions) {
20 const jobDescription = {
21 name: target ? `{${(0, api_1.targetStringFromTarget)(target)}}` : info.builderName,
22 argument: { type: 'object' },
23 input: inputSchema,
24 output: outputSchema,
25 info,
26 };
27 function handler(argument, context) {
28 // Add input validation to the inbound bus.
29 const inboundBusWithInputValidation = context.inboundBus.pipe((0, operators_1.concatMap)((message) => {
30 if (message.kind === jobs_1.JobInboundMessageKind.Input) {
31 const v = message.value;
32 const options = {
33 ...baseOptions,
34 ...v.options,
35 };
36 // Validate v against the options schema.
37 return registry.compile(info.optionSchema).pipe((0, operators_1.concatMap)((validation) => validation(options)), (0, operators_1.map)((validationResult) => {
38 const { data, success, errors } = validationResult;
39 if (success) {
40 return { ...v, options: data };
41 }
42 throw new core_1.json.schema.SchemaValidationException(errors);
43 }), (0, operators_1.map)((value) => ({ ...message, value })));
44 }
45 else {
46 return (0, rxjs_1.of)(message);
47 }
48 }),
49 // Using a share replay because the job might be synchronously sending input, but
50 // asynchronously listening to it.
51 (0, operators_1.shareReplay)(1));
52 // Make an inboundBus that completes instead of erroring out.
53 // We'll merge the errors into the output instead.
54 const inboundBus = (0, rxjs_1.onErrorResumeNext)(inboundBusWithInputValidation);
55 const output = (0, rxjs_1.from)(host.loadBuilder(info)).pipe((0, operators_1.concatMap)((builder) => {
56 if (builder === null) {
57 throw new Error(`Cannot load builder for builderInfo ${JSON.stringify(info, null, 2)}`);
58 }
59 return builder.handler(argument, { ...context, inboundBus }).pipe((0, operators_1.map)((output) => {
60 if (output.kind === jobs_1.JobOutboundMessageKind.Output) {
61 // Add target to it.
62 return {
63 ...output,
64 value: {
65 ...output.value,
66 ...(target ? { target } : 0),
67 },
68 };
69 }
70 else {
71 return output;
72 }
73 }));
74 }),
75 // Share subscriptions to the output, otherwise the the handler will be re-run.
76 (0, operators_1.shareReplay)());
77 // Separate the errors from the inbound bus into their own observable that completes when the
78 // builder output does.
79 const inboundBusErrors = inboundBusWithInputValidation.pipe((0, operators_1.ignoreElements)(), (0, operators_1.takeUntil)((0, rxjs_1.onErrorResumeNext)(output.pipe((0, operators_1.last)()))));
80 // Return the builder output plus any input errors.
81 return (0, rxjs_1.merge)(inboundBusErrors, output);
82 }
83 return (0, rxjs_1.of)(Object.assign(handler, { jobDescription }));
84}
85/**
86 * A JobRegistry that resolves builder targets from the host.
87 */
88class ArchitectBuilderJobRegistry {
89 constructor(_host, _registry, _jobCache, _infoCache) {
90 this._host = _host;
91 this._registry = _registry;
92 this._jobCache = _jobCache;
93 this._infoCache = _infoCache;
94 }
95 _resolveBuilder(name) {
96 const cache = this._infoCache;
97 if (cache) {
98 const maybeCache = cache.get(name);
99 if (maybeCache !== undefined) {
100 return maybeCache;
101 }
102 const info = (0, rxjs_1.from)(this._host.resolveBuilder(name)).pipe((0, operators_1.shareReplay)(1));
103 cache.set(name, info);
104 return info;
105 }
106 return (0, rxjs_1.from)(this._host.resolveBuilder(name));
107 }
108 _createBuilder(info, target, options) {
109 const cache = this._jobCache;
110 if (target) {
111 const maybeHit = cache && cache.get((0, api_1.targetStringFromTarget)(target));
112 if (maybeHit) {
113 return maybeHit;
114 }
115 }
116 else {
117 const maybeHit = cache && cache.get(info.builderName);
118 if (maybeHit) {
119 return maybeHit;
120 }
121 }
122 const result = _createJobHandlerFromBuilderInfo(info, target, this._host, this._registry, options || {});
123 if (cache) {
124 if (target) {
125 cache.set((0, api_1.targetStringFromTarget)(target), result.pipe((0, operators_1.shareReplay)(1)));
126 }
127 else {
128 cache.set(info.builderName, result.pipe((0, operators_1.shareReplay)(1)));
129 }
130 }
131 return result;
132 }
133 get(name) {
134 const m = name.match(/^([^:]+):([^:]+)$/i);
135 if (!m) {
136 return (0, rxjs_1.of)(null);
137 }
138 return (0, rxjs_1.from)(this._resolveBuilder(name)).pipe((0, operators_1.concatMap)((builderInfo) => (builderInfo ? this._createBuilder(builderInfo) : (0, rxjs_1.of)(null))), (0, operators_1.first)(null, null));
139 }
140}
141/**
142 * A JobRegistry that resolves targets from the host.
143 */
144class ArchitectTargetJobRegistry extends ArchitectBuilderJobRegistry {
145 get(name) {
146 const m = name.match(/^{([^:]+):([^:]+)(?::([^:]*))?}$/i);
147 if (!m) {
148 return (0, rxjs_1.of)(null);
149 }
150 const target = {
151 project: m[1],
152 target: m[2],
153 configuration: m[3],
154 };
155 return (0, rxjs_1.from)(Promise.all([
156 this._host.getBuilderNameForTarget(target),
157 this._host.getOptionsForTarget(target),
158 ])).pipe((0, operators_1.concatMap)(([builderStr, options]) => {
159 if (builderStr === null || options === null) {
160 return (0, rxjs_1.of)(null);
161 }
162 return this._resolveBuilder(builderStr).pipe((0, operators_1.concatMap)((builderInfo) => {
163 if (builderInfo === null) {
164 return (0, rxjs_1.of)(null);
165 }
166 return this._createBuilder(builderInfo, target, options);
167 }));
168 }), (0, operators_1.first)(null, null));
169 }
170}
171function _getTargetOptionsFactory(host) {
172 return (0, jobs_1.createJobHandler)((target) => {
173 return host.getOptionsForTarget(target).then((options) => {
174 if (options === null) {
175 throw new Error(`Invalid target: ${JSON.stringify(target)}.`);
176 }
177 return options;
178 });
179 }, {
180 name: '..getTargetOptions',
181 output: { type: 'object' },
182 argument: inputSchema.properties.target,
183 });
184}
185function _getProjectMetadataFactory(host) {
186 return (0, jobs_1.createJobHandler)((target) => {
187 return host.getProjectMetadata(target).then((options) => {
188 if (options === null) {
189 throw new Error(`Invalid target: ${JSON.stringify(target)}.`);
190 }
191 return options;
192 });
193 }, {
194 name: '..getProjectMetadata',
195 output: { type: 'object' },
196 argument: {
197 oneOf: [{ type: 'string' }, inputSchema.properties.target],
198 },
199 });
200}
201function _getBuilderNameForTargetFactory(host) {
202 return (0, jobs_1.createJobHandler)(async (target) => {
203 const builderName = await host.getBuilderNameForTarget(target);
204 if (!builderName) {
205 throw new Error(`No builder were found for target ${(0, api_1.targetStringFromTarget)(target)}.`);
206 }
207 return builderName;
208 }, {
209 name: '..getBuilderNameForTarget',
210 output: { type: 'string' },
211 argument: inputSchema.properties.target,
212 });
213}
214function _validateOptionsFactory(host, registry) {
215 return (0, jobs_1.createJobHandler)(async ([builderName, options]) => {
216 // Get option schema from the host.
217 const builderInfo = await host.resolveBuilder(builderName);
218 if (!builderInfo) {
219 throw new Error(`No builder info were found for builder ${JSON.stringify(builderName)}.`);
220 }
221 return registry
222 .compile(builderInfo.optionSchema)
223 .pipe((0, operators_1.concatMap)((validation) => validation(options)), (0, operators_1.switchMap)(({ data, success, errors }) => {
224 if (success) {
225 return (0, rxjs_1.of)(data);
226 }
227 throw new core_1.json.schema.SchemaValidationException(errors);
228 }))
229 .toPromise();
230 }, {
231 name: '..validateOptions',
232 output: { type: 'object' },
233 argument: {
234 type: 'array',
235 items: [{ type: 'string' }, { type: 'object' }],
236 },
237 });
238}
239class Architect {
240 constructor(_host, registry = new core_1.json.schema.CoreSchemaRegistry(), additionalJobRegistry) {
241 this._host = _host;
242 this._jobCache = new Map();
243 this._infoCache = new Map();
244 const privateArchitectJobRegistry = new jobs_1.SimpleJobRegistry();
245 // Create private jobs.
246 privateArchitectJobRegistry.register(_getTargetOptionsFactory(_host));
247 privateArchitectJobRegistry.register(_getBuilderNameForTargetFactory(_host));
248 privateArchitectJobRegistry.register(_validateOptionsFactory(_host, registry));
249 privateArchitectJobRegistry.register(_getProjectMetadataFactory(_host));
250 const jobRegistry = new jobs_1.FallbackRegistry([
251 new ArchitectTargetJobRegistry(_host, registry, this._jobCache, this._infoCache),
252 new ArchitectBuilderJobRegistry(_host, registry, this._jobCache, this._infoCache),
253 privateArchitectJobRegistry,
254 ...(additionalJobRegistry ? [additionalJobRegistry] : []),
255 ]);
256 this._scheduler = new jobs_1.SimpleScheduler(jobRegistry, registry);
257 }
258 has(name) {
259 return this._scheduler.has(name);
260 }
261 scheduleBuilder(name, options, scheduleOptions = {}) {
262 // The below will match 'project:target:configuration'
263 if (!/^[^:]+:[^:]+(:[^:]+)?$/.test(name)) {
264 throw new Error('Invalid builder name: ' + JSON.stringify(name));
265 }
266 return (0, schedule_by_name_1.scheduleByName)(name, options, {
267 scheduler: this._scheduler,
268 logger: scheduleOptions.logger || new core_1.logging.NullLogger(),
269 currentDirectory: this._host.getCurrentDirectory(),
270 workspaceRoot: this._host.getWorkspaceRoot(),
271 });
272 }
273 scheduleTarget(target, overrides = {}, scheduleOptions = {}) {
274 return (0, schedule_by_name_1.scheduleByTarget)(target, overrides, {
275 scheduler: this._scheduler,
276 logger: scheduleOptions.logger || new core_1.logging.NullLogger(),
277 currentDirectory: this._host.getCurrentDirectory(),
278 workspaceRoot: this._host.getWorkspaceRoot(),
279 });
280 }
281}
282exports.Architect = Architect;