@atomist/sdm
Version:
Atomist Software Delivery Machine SDK
174 lines (147 loc) • 5.25 kB
text/typescript
/*
* Copyright © 2019 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as _ from "lodash";
import { Locking } from "./common/Locking";
import {
Goal,
GoalDefinition,
GoalWithPrecondition,
} from "./Goal";
/**
* Represents goals set in response to a push
*/
export class Goals {
public readonly goals: Goal[];
/**
* Return a Goal set that contains these goals and one more goal,
* with an appropriate name
* @param {Goal} g goal to add
* @return {Goals}
*/
public and(g: Goal): Goals {
return new Goals(this.name + ", " + g.name, ...this.goals.concat(g));
}
/**
* Return a form of these goals that is locked, so that more goals cannot be
* added through contribution model
* @return {Goals}
*/
public andLock(): Goals {
return this.and(Locking);
}
// tslint:disable-next-line:no-shadowed-variable
constructor(public name: string, ...goals: Goal[]) {
this.goals = goals;
}
}
export function isGoals(a: any): a is Goals {
return !!a && !!(a as Goals).goals;
}
/**
* Builder to build Goals instances.
*/
export interface GoalsBuilder {
/**
* Plan the given goal or goals to this Goals instance.
* @param {GoalDefinition | Goal | Goals} goals
* @returns {Goals & GoalsAndPreConditionBuilder}
*/
plan(...goals: Array<GoalDefinition | Goal | Goals>): Goals & GoalsAndPreConditionBuilder;
}
/**
* Extension to GoalsBuilder allowing to add preConditions.
*/
export interface GoalsAndPreConditionBuilder extends GoalsBuilder {
/**
* Add preCondition(s) to previously planned goal or goals.
* Note:
* This only will affect the goal or goals that were planned immediately
* before calling `after`.
* Only call this once; an additional call will remove other preconditions
* that may have existed on the original goal.
* @param {Goal} goals
* @returns {Goals & GoalsBuilder}
*/
after(...goals: Array<Goals | GoalDefinition | Goal>): Goals & GoalsBuilder;
}
/**
* Create Goals instance using a fluent API.
*
* const simpleGoals = goals("Simple Goals")
* .plan(CodeInspectionGoal)
* .plan(BuildGoal, Autofix).after(CodeInspectionGoal)
* .plan(StagingEndpointGoal).after(BuildGoal)
* .plan(ProductionDeploymentGoal).after(BuildGoal, StagingEndpointGoal);
*
* @param {string} name
* @returns {Goals & GoalsBuilder}
*/
export function goals(name: string): Goals & GoalsBuilder {
return new DefaultGoalsBuilder(name);
}
class DefaultGoalsBuilder extends Goals implements GoalsBuilder, GoalsAndPreConditionBuilder {
private lastGoals: Goal[] = [];
constructor(public name: string) {
super(name);
}
public plan(...newGoals: Array<GoalDefinition | Goal | Goals>): Goals & GoalsAndPreConditionBuilder {
const currentGoals = [];
// Keep track of what goals where last added so that we can add the preConditions later
convertToGoals(...newGoals).forEach(g => {
if (isGoals(g)) {
currentGoals.push(...g.goals);
} else {
currentGoals.push(g);
}
});
this.goals.push(...currentGoals);
this.lastGoals = currentGoals;
return this;
}
public after(...newGoals: Array<Goals | GoalDefinition | Goal>): Goals & GoalsBuilder {
const lastGoalsWithPreConditions = [];
this.lastGoals.forEach(g => {
// Add the preCondition into the last added goals
const newGoal = new GoalWithPrecondition(g.definition, ...convertToGoals(...newGoals));
// Preserve the plan function if it exists as it might influence goal planning
(newGoal as any).plan = (g as any).plan;
lastGoalsWithPreConditions.push(newGoal);
// Remove the previously added goals
const ix = this.goals.indexOf(g);
if (ix >= 0) {
this.goals.splice(ix, 1);
} else {
throw new Error("Unable to remove previously planned goal");
}
});
// Add the newly created goals with preConditions
this.goals.push(...lastGoalsWithPreConditions);
this.lastGoals = lastGoalsWithPreConditions;
return this;
}
}
function convertToGoals(...pgoals: Array<GoalDefinition | Goal | Goals>): Goal[] {
return _.flatten<Goal>(pgoals.map(g => {
if (g instanceof Goal) {
return g;
} else if (isGoals(g)) {
return g.goals;
} else if (!!g) {
return new Goal(g);
}
return undefined;
})).filter(g => !!g);
}