// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. import { InternalError } from '@rushstack/node-core-library'; import { HeftPluginConfiguration } from '../configuration/HeftPluginConfiguration'; import type { HeftTaskPluginDefinition, HeftPluginDefinitionBase } from '../configuration/HeftPluginDefinition'; import type { HeftPhase, IHeftPhase } from './HeftPhase'; import type { IHeftConfigurationJsonTaskSpecifier, IHeftConfigurationJsonPluginSpecifier } from '../utilities/CoreConfigFiles'; import type { IHeftTaskPlugin } from './IHeftPlugin'; import type { IScopedLogger } from './logging/ScopedLogger'; const RESERVED_TASK_NAMES: Set = new Set(['clean']); /** * @public */ export interface IHeftTask { readonly parentPhase: IHeftPhase; readonly taskName: string; readonly consumingTasks: ReadonlySet; readonly dependencyTasks: ReadonlySet; } /** * @internal */ export class HeftTask implements IHeftTask { #parentPhase: HeftPhase; #taskName: string; #taskSpecifier: IHeftConfigurationJsonTaskSpecifier; #consumingTasks: Set | undefined; #dependencyTasks: Set | undefined; #taskPluginDefinition: HeftTaskPluginDefinition | undefined; #taskPlugin: IHeftTaskPlugin | undefined; public get parentPhase(): HeftPhase { return this.#parentPhase; } public get taskName(): string { return this.#taskName; } public get consumingTasks(): ReadonlySet { if (!this.#consumingTasks) { // Force initialize all dependency relationships // This needs to operate on every phase in the set because the relationships are only specified // in the consuming phase. const { tasks } = this.#parentPhase; for (const task of tasks) { task.#consumingTasks = new Set(); } for (const task of tasks) { for (const dependency of task.dependencyTasks) { dependency.#consumingTasks!.add(task); } } } return this.#consumingTasks!; } public get pluginDefinition(): HeftTaskPluginDefinition { if (!this.#taskPluginDefinition) { throw new InternalError( 'HeftTask.ensureInitializedAsync() must be called before accessing HeftTask.pluginDefinition.' ); } return this.#taskPluginDefinition; } public get pluginOptions(): object | undefined { return this.#taskSpecifier.taskPlugin.options; } public get dependencyTasks(): Set { if (!this.#dependencyTasks) { this.#dependencyTasks = new Set(); const dependencyNamesSet: Set = new Set(this.#taskSpecifier.taskDependencies || []); for (const dependencyName of dependencyNamesSet) { // Skip if we can't find the dependency const dependencyTask: HeftTask | undefined = this.#parentPhase.tasksByName.get(dependencyName); if (!dependencyTask) { throw new Error( `Could not find dependency task ${JSON.stringify(dependencyName)} within phase ` + `${JSON.stringify(this.#parentPhase.phaseName)}.` ); } this.#dependencyTasks.add(dependencyTask); } } return this.#dependencyTasks!; } public constructor( parentPhase: HeftPhase, taskName: string, taskSpecifier: IHeftConfigurationJsonTaskSpecifier ) { this.#parentPhase = parentPhase; this.#taskName = taskName; this.#taskSpecifier = taskSpecifier; this.#validate(); } public async ensureInitializedAsync(): Promise { if (!this.#taskPluginDefinition) { this.#taskPluginDefinition = await this.#loadTaskPluginDefinitionAsync(); this.pluginDefinition.validateOptions(this.pluginOptions); } } public async getPluginAsync(logger: IScopedLogger): Promise> { await this.ensureInitializedAsync(); if (!this.#taskPlugin) { this.#taskPlugin = await this.#taskPluginDefinition!.loadPluginAsync(logger); } return this.#taskPlugin; } async #loadTaskPluginDefinitionAsync(): Promise { // taskPlugin.pluginPackage should already be resolved to the package root. // See CoreConfigFiles.heftConfigFileLoader const pluginSpecifier: IHeftConfigurationJsonPluginSpecifier = this.#taskSpecifier.taskPlugin; const pluginConfiguration: HeftPluginConfiguration = await HeftPluginConfiguration.loadFromPackageAsync( pluginSpecifier.pluginPackageRoot, pluginSpecifier.pluginPackage ); const pluginDefinition: HeftPluginDefinitionBase = pluginConfiguration.getPluginDefinitionBySpecifier(pluginSpecifier); const isTaskPluginDefinition: boolean = pluginConfiguration.isTaskPluginDefinition(pluginDefinition); if (!isTaskPluginDefinition) { throw new Error( `Plugin ${JSON.stringify(pluginSpecifier.pluginName)} specified by task ` + `${JSON.stringify(this.#taskName)} is not a task plugin.` ); } return pluginDefinition; } #validate(): void { if (RESERVED_TASK_NAMES.has(this.taskName)) { throw new Error( `Task name ${JSON.stringify(this.taskName)} is reserved and cannot be used as a task name.` ); } if (!this.#taskSpecifier.taskPlugin) { throw new Error(`Task ${JSON.stringify(this.taskName)} has no specified task plugin.`); } } }