Files
languageservices/workflow-parser/src/model/converter/steps.ts
T

148 lines
4.3 KiB
TypeScript
Raw Normal View History

2023-04-12 11:32:38 -04:00
import {TemplateContext} from "../../templates/template-context";
import {BasicExpressionToken, MappingToken, ScalarToken, StringToken, TemplateToken} from "../../templates/tokens";
import {isSequence} from "../../templates/tokens/type-guards";
import {isActionStep} from "../type-guards";
import {convertToIfCondition, STEP_IF_CONTEXT} from "./if-condition";
2023-04-12 11:32:38 -04:00
import {ActionStep, Step} from "../workflow-template";
import {handleTemplateTokenErrors} from "./handle-errors";
import {IdBuilder} from "./id-builder";
2023-01-09 19:02:19 -05:00
export function convertSteps(context: TemplateContext, steps: TemplateToken): Step[] {
if (!isSequence(steps)) {
2023-01-09 19:02:19 -05:00
context.error(steps, "Invalid format for steps");
return [];
}
2023-01-09 19:02:19 -05:00
const idBuilder = new IdBuilder();
2023-01-09 19:02:19 -05:00
const result: Step[] = [];
for (const item of steps) {
2023-01-09 19:02:19 -05:00
const step = handleTemplateTokenErrors(steps, context, undefined, () => convertStep(context, idBuilder, item));
if (step) {
2023-01-09 19:02:19 -05:00
result.push(step);
}
}
for (const step of result) {
if (step.id) {
2023-01-09 19:02:19 -05:00
continue;
}
2023-01-09 19:02:19 -05:00
let id = "";
if (isActionStep(step)) {
2023-01-09 19:02:19 -05:00
id = createActionStepId(step);
}
if (!id) {
2023-01-09 19:02:19 -05:00
id = "run";
}
2023-01-09 19:02:19 -05:00
idBuilder.appendSegment(`__${id}`);
step.id = idBuilder.build();
}
2023-01-09 19:02:19 -05:00
return result;
}
2023-01-09 19:02:19 -05:00
function convertStep(context: TemplateContext, idBuilder: IdBuilder, step: TemplateToken): Step | undefined {
const mapping = step.assertMapping("steps item");
let run: ScalarToken | undefined;
let id: StringToken | undefined;
let name: ScalarToken | undefined;
let uses: StringToken | undefined;
2023-04-11 13:54:53 -04:00
let continueOnError: boolean | ScalarToken | undefined;
2023-01-09 19:02:19 -05:00
let env: MappingToken | undefined;
let ifCondition: BasicExpressionToken | undefined;
for (const item of mapping) {
2023-01-09 19:02:19 -05:00
const key = item.key.assertString("steps item key");
switch (key.value) {
case "id":
2023-01-09 19:02:19 -05:00
id = item.value.assertString("steps item id");
if (id) {
2023-01-09 19:02:19 -05:00
const error = idBuilder.tryAddKnownId(id.value);
if (error) {
2023-01-09 19:02:19 -05:00
context.error(id, error);
}
}
2023-01-09 19:02:19 -05:00
break;
case "name":
2023-01-09 19:02:19 -05:00
name = item.value.assertScalar("steps item name");
break;
case "run":
2023-01-09 19:02:19 -05:00
run = item.value.assertScalar("steps item run");
break;
case "uses":
2023-01-09 19:02:19 -05:00
uses = item.value.assertString("steps item uses");
break;
case "env":
2023-01-09 19:02:19 -05:00
env = item.value.assertMapping("step env");
break;
case "if":
ifCondition = convertToIfCondition(context, item.value, STEP_IF_CONTEXT);
break;
case "continue-on-error":
2023-04-11 10:05:30 -04:00
if (!item.value.isExpression) {
continueOnError = item.value.assertBoolean("steps item continue-on-error").value;
2023-04-11 13:54:53 -04:00
} else {
continueOnError = item.value.assertScalar("steps item continue-on-error");
2023-04-11 10:05:30 -04:00
}
}
}
if (run) {
return {
id: id?.value || "",
name,
if: ifCondition || new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined),
"continue-on-error": continueOnError,
env,
2023-01-09 19:02:19 -05:00
run
};
}
if (uses) {
return {
id: id?.value || "",
name,
if: ifCondition || new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined),
"continue-on-error": continueOnError,
env,
2023-01-09 19:02:19 -05:00
uses
};
}
2023-01-09 19:02:19 -05:00
context.error(step, "Expected uses or run to be defined");
}
function createActionStepId(step: ActionStep): string {
2023-01-09 19:02:19 -05:00
const uses = step.uses.value;
if (uses.startsWith("docker://")) {
2023-01-09 19:02:19 -05:00
return uses.substring("docker://".length);
}
if (uses.startsWith("./") || uses.startsWith(".\\")) {
2023-01-09 19:02:19 -05:00
return "self";
}
2023-01-09 19:02:19 -05:00
const segments = uses.split("@");
if (segments.length != 2) {
2023-01-09 19:02:19 -05:00
return "";
}
2023-01-09 19:02:19 -05:00
const pathSegments = segments[0].split(/[\\/]/).filter(s => s.length > 0);
const gitRef = segments[1];
2023-01-09 19:02:19 -05:00
if (pathSegments.length >= 2 && pathSegments[0] && pathSegments[1] && gitRef) {
return `${pathSegments[0]}/${pathSegments[1]}`;
}
2023-01-09 19:02:19 -05:00
return "";
}
/**
* Converts an if condition token to a BasicExpressionToken.
* Similar to Go's convertToIfCondition - treats the value as a string and parses it as an expression.
* Wraps the condition in success() && (...) if it doesn't already contain a status function.
* This allows both 'if: success()' and 'if: ${{ success() }}' to work correctly.
*/