"use strict"; /** * @license * Copyright Google LLC All Rights Reserved. * * 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 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Architect = void 0; const core_1 = require("@angular-devkit/core"); const rxjs_1 = require("rxjs"); const api_1 = require("./api"); const jobs_1 = require("./jobs"); const schedule_by_name_1 = require("./schedule-by-name"); const inputSchema = require('./input-schema.json'); const outputSchema = require('./output-schema.json'); function _createJobHandlerFromBuilderInfo(info, target, host, registry, baseOptions) { const jobDescription = { name: target ? `{${(0, api_1.targetStringFromTarget)(target)}}` : info.builderName, argument: { type: 'object' }, input: inputSchema, output: outputSchema, info, }; function handler(argument, context) { // Add input validation to the inbound bus. const inboundBusWithInputValidation = context.inboundBus.pipe((0, rxjs_1.concatMap)(async (message) => { if (message.kind === jobs_1.JobInboundMessageKind.Input) { const v = message.value; const options = { ...baseOptions, ...v.options, }; // Validate v against the options schema. const validation = await registry.compile(info.optionSchema); const validationResult = await validation(options); const { data, success, errors } = validationResult; if (!success) { throw new core_1.json.schema.SchemaValidationException(errors); } return { ...message, value: { ...v, options: data } }; } else { return message; } }), // Using a share replay because the job might be synchronously sending input, but // asynchronously listening to it. (0, rxjs_1.shareReplay)(1)); // Make an inboundBus that completes instead of erroring out. // We'll merge the errors into the output instead. const inboundBus = (0, rxjs_1.onErrorResumeNext)(inboundBusWithInputValidation); const output = (0, rxjs_1.from)(host.loadBuilder(info)).pipe((0, rxjs_1.concatMap)((builder) => { if (builder === null) { throw new Error(`Cannot load builder for builderInfo ${JSON.stringify(info, null, 2)}`); } return builder.handler(argument, { ...context, inboundBus }).pipe((0, rxjs_1.map)((output) => { if (output.kind === jobs_1.JobOutboundMessageKind.Output) { // Add target to it. return { ...output, value: { ...output.value, ...(target ? { target } : 0), }, }; } else { return output; } })); }), // Share subscriptions to the output, otherwise the handler will be re-run. (0, rxjs_1.shareReplay)()); // Separate the errors from the inbound bus into their own observable that completes when the // builder output does. const inboundBusErrors = inboundBusWithInputValidation.pipe((0, rxjs_1.ignoreElements)(), (0, rxjs_1.takeUntil)((0, rxjs_1.onErrorResumeNext)(output.pipe((0, rxjs_1.last)())))); // Return the builder output plus any input errors. return (0, rxjs_1.merge)(inboundBusErrors, output); } return (0, rxjs_1.of)(Object.assign(handler, { jobDescription })); } /** * A JobRegistry that resolves builder targets from the host. */ class ArchitectBuilderJobRegistry { _host; _registry; _jobCache; _infoCache; constructor(_host, _registry, _jobCache, _infoCache) { this._host = _host; this._registry = _registry; this._jobCache = _jobCache; this._infoCache = _infoCache; } _resolveBuilder(name) { const cache = this._infoCache; if (cache) { const maybeCache = cache.get(name); if (maybeCache !== undefined) { return maybeCache; } const info = (0, rxjs_1.from)(this._host.resolveBuilder(name)).pipe((0, rxjs_1.shareReplay)(1)); cache.set(name, info); return info; } return (0, rxjs_1.from)(this._host.resolveBuilder(name)); } _createBuilder(info, target, options) { const cache = this._jobCache; if (target) { const maybeHit = cache && cache.get((0, api_1.targetStringFromTarget)(target)); if (maybeHit) { return maybeHit; } } else { const maybeHit = cache && cache.get(info.builderName); if (maybeHit) { return maybeHit; } } const result = _createJobHandlerFromBuilderInfo(info, target, this._host, this._registry, options || {}); if (cache) { if (target) { cache.set((0, api_1.targetStringFromTarget)(target), result.pipe((0, rxjs_1.shareReplay)(1))); } else { cache.set(info.builderName, result.pipe((0, rxjs_1.shareReplay)(1))); } } return result; } get(name) { const m = name.match(/^([^:]+):([^:]+)$/i); if (!m) { return (0, rxjs_1.of)(null); } return (0, rxjs_1.from)(this._resolveBuilder(name)).pipe((0, rxjs_1.concatMap)((builderInfo) => (builderInfo ? this._createBuilder(builderInfo) : (0, rxjs_1.of)(null))), (0, rxjs_1.first)(null, null)); } } /** * A JobRegistry that resolves targets from the host. */ class ArchitectTargetJobRegistry extends ArchitectBuilderJobRegistry { get(name) { const m = name.match(/^{([^:]+):([^:]+)(?::([^:]*))?}$/i); if (!m) { return (0, rxjs_1.of)(null); } const target = { project: m[1], target: m[2], configuration: m[3], }; return (0, rxjs_1.from)(Promise.all([ this._host.getBuilderNameForTarget(target), this._host.getOptionsForTarget(target), ])).pipe((0, rxjs_1.concatMap)(([builderStr, options]) => { if (builderStr === null || options === null) { return (0, rxjs_1.of)(null); } return this._resolveBuilder(builderStr).pipe((0, rxjs_1.concatMap)((builderInfo) => { if (builderInfo === null) { return (0, rxjs_1.of)(null); } return this._createBuilder(builderInfo, target, options); })); }), (0, rxjs_1.first)(null, null)); } } function _getTargetOptionsFactory(host) { return (0, jobs_1.createJobHandler)((target) => { return host.getOptionsForTarget(target).then((options) => { if (options === null) { throw new Error(`Invalid target: ${JSON.stringify(target)}.`); } return options; }); }, { name: '..getTargetOptions', output: { type: 'object' }, argument: inputSchema.properties.target, }); } function _getProjectMetadataFactory(host) { return (0, jobs_1.createJobHandler)((target) => { return host.getProjectMetadata(target).then((options) => { if (options === null) { throw new Error(`Invalid target: ${JSON.stringify(target)}.`); } return options; }); }, { name: '..getProjectMetadata', output: { type: 'object' }, argument: { oneOf: [{ type: 'string' }, inputSchema.properties.target], }, }); } function _getBuilderNameForTargetFactory(host) { return (0, jobs_1.createJobHandler)(async (target) => { const builderName = await host.getBuilderNameForTarget(target); if (!builderName) { throw new Error(`No builder were found for target ${(0, api_1.targetStringFromTarget)(target)}.`); } return builderName; }, { name: '..getBuilderNameForTarget', output: { type: 'string' }, argument: inputSchema.properties.target, }); } function _validateOptionsFactory(host, registry) { return (0, jobs_1.createJobHandler)(async ([builderName, options]) => { // Get option schema from the host. const builderInfo = await host.resolveBuilder(builderName); if (!builderInfo) { throw new Error(`No builder info were found for builder ${JSON.stringify(builderName)}.`); } const validation = await registry.compile(builderInfo.optionSchema); const { data, success, errors } = await validation(options); if (!success) { throw new core_1.json.schema.SchemaValidationException(errors); } return data; }, { name: '..validateOptions', output: { type: 'object' }, argument: { type: 'array', items: [{ type: 'string' }, { type: 'object' }], }, }); } class Architect { _host; _scheduler; _jobCache = new Map(); _infoCache = new Map(); constructor(_host, registry = new core_1.json.schema.CoreSchemaRegistry(), additionalJobRegistry) { this._host = _host; const privateArchitectJobRegistry = new jobs_1.SimpleJobRegistry(); // Create private jobs. privateArchitectJobRegistry.register(_getTargetOptionsFactory(_host)); privateArchitectJobRegistry.register(_getBuilderNameForTargetFactory(_host)); privateArchitectJobRegistry.register(_validateOptionsFactory(_host, registry)); privateArchitectJobRegistry.register(_getProjectMetadataFactory(_host)); const jobRegistry = new jobs_1.FallbackRegistry([ new ArchitectTargetJobRegistry(_host, registry, this._jobCache, this._infoCache), new ArchitectBuilderJobRegistry(_host, registry, this._jobCache, this._infoCache), privateArchitectJobRegistry, ...(additionalJobRegistry ? [additionalJobRegistry] : []), ]); this._scheduler = new jobs_1.SimpleScheduler(jobRegistry, registry); } has(name) { return this._scheduler.has(name); } scheduleBuilder(name, options, scheduleOptions = {}) { // The below will match 'project:target:configuration' if (!/^[^:]+:[^:]+(:[^:]+)?$/.test(name)) { throw new Error('Invalid builder name: ' + JSON.stringify(name)); } return (0, schedule_by_name_1.scheduleByName)(name, options, { scheduler: this._scheduler, logger: scheduleOptions.logger || new core_1.logging.NullLogger(), currentDirectory: this._host.getCurrentDirectory(), workspaceRoot: this._host.getWorkspaceRoot(), }); } scheduleTarget(target, overrides = {}, scheduleOptions = {}) { return (0, schedule_by_name_1.scheduleByTarget)(target, overrides, { scheduler: this._scheduler, logger: scheduleOptions.logger || new core_1.logging.NullLogger(), currentDirectory: this._host.getCurrentDirectory(), workspaceRoot: this._host.getWorkspaceRoot(), }); } } exports.Architect = Architect;