Remove isExpression flag and implement convertToIfCondition to align with Go parser architecture (#217)
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import {TemplateContext} from "../../templates/template-context";
|
||||
import {BasicExpressionToken, ExpressionToken, TemplateToken} from "../../templates/tokens";
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param context The template context for error reporting
|
||||
* @param token The token containing the if condition
|
||||
* @param allowedContext The allowed expression contexts (varies by job-if vs step-if vs snapshot-if)
|
||||
* @returns A BasicExpressionToken with the processed condition, or undefined on error
|
||||
*/
|
||||
export function convertToIfCondition(
|
||||
context: TemplateContext,
|
||||
token: TemplateToken,
|
||||
allowedContext: string[]
|
||||
): BasicExpressionToken | undefined {
|
||||
const scalar = token.assertScalar("if condition");
|
||||
|
||||
// If it's already an expression, use its value
|
||||
let condition: string;
|
||||
let source: string | undefined;
|
||||
|
||||
if (scalar instanceof BasicExpressionToken) {
|
||||
condition = scalar.expression;
|
||||
source = scalar.source;
|
||||
} else {
|
||||
// Otherwise, treat it as a string
|
||||
const stringToken = scalar.assertString("if condition");
|
||||
condition = stringToken.value.trim();
|
||||
source = stringToken.source;
|
||||
}
|
||||
|
||||
if (!condition) {
|
||||
// Empty condition defaults to success()
|
||||
return new BasicExpressionToken(token.file, token.range, "success()", token.definitionInfo, undefined, undefined);
|
||||
}
|
||||
|
||||
// Check if the condition already contains a status function (success, failure, cancelled, always)
|
||||
// This is a simple check - just look for these function names
|
||||
const hasStatusFunction = /\b(success|failure|cancelled|always)\s*\(/.test(condition);
|
||||
|
||||
let finalCondition: string;
|
||||
if (hasStatusFunction) {
|
||||
finalCondition = condition;
|
||||
} else {
|
||||
// Wrap in success() && (condition)
|
||||
finalCondition = `success() && (${condition})`;
|
||||
}
|
||||
|
||||
// Validate the expression before creating the token
|
||||
try {
|
||||
ExpressionToken.validateExpression(finalCondition, allowedContext);
|
||||
} catch (err) {
|
||||
context.error(token, err as Error);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Create a BasicExpressionToken with the final condition
|
||||
return new BasicExpressionToken(token.file, token.range, finalCondition, token.definitionInfo, undefined, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowed context for job-level if conditions
|
||||
*/
|
||||
export const JOB_IF_CONTEXT = [
|
||||
"github",
|
||||
"inputs",
|
||||
"vars",
|
||||
"needs",
|
||||
"always(0,0)",
|
||||
"failure(0,MAX)",
|
||||
"cancelled(0,0)",
|
||||
"success(0,MAX)"
|
||||
];
|
||||
|
||||
/**
|
||||
* Allowed context for step-level if conditions
|
||||
*/
|
||||
export const STEP_IF_CONTEXT = [
|
||||
"github",
|
||||
"inputs",
|
||||
"vars",
|
||||
"needs",
|
||||
"strategy",
|
||||
"matrix",
|
||||
"steps",
|
||||
"job",
|
||||
"runner",
|
||||
"env",
|
||||
"always(0,0)",
|
||||
"failure(0,0)",
|
||||
"cancelled(0,0)",
|
||||
"success(0,0)",
|
||||
"hashFiles(1,255)"
|
||||
];
|
||||
|
||||
/**
|
||||
* Allowed context for snapshot-level if conditions
|
||||
*/
|
||||
export const SNAPSHOT_IF_CONTEXT = [
|
||||
"github",
|
||||
"inputs",
|
||||
"vars",
|
||||
"needs",
|
||||
"strategy",
|
||||
"matrix",
|
||||
"steps",
|
||||
"job",
|
||||
"runner",
|
||||
"env",
|
||||
"always(0,0)",
|
||||
"failure(0,0)",
|
||||
"cancelled(0,0)",
|
||||
"success(0,0)",
|
||||
"hashFiles(1,255)"
|
||||
];
|
||||
@@ -2,6 +2,7 @@ import {TemplateContext} from "../../templates/template-context";
|
||||
import {BasicExpressionToken, MappingToken, ScalarToken, StringToken, TemplateToken} from "../../templates/tokens";
|
||||
import {isSequence, isString} from "../../templates/tokens/type-guards";
|
||||
import {Step, WorkflowJob} from "../workflow-template";
|
||||
import {convertToIfCondition, JOB_IF_CONTEXT} from "./if-condition";
|
||||
import {convertConcurrency} from "./concurrency";
|
||||
import {convertToJobContainer, convertToJobServices} from "./container";
|
||||
import {handleTemplateTokenErrors} from "./handle-errors";
|
||||
@@ -20,6 +21,7 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
|
||||
container,
|
||||
env,
|
||||
environment,
|
||||
ifCondition,
|
||||
name,
|
||||
outputs,
|
||||
runsOn,
|
||||
@@ -59,6 +61,10 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
|
||||
environment = item.value;
|
||||
break;
|
||||
|
||||
case "if":
|
||||
ifCondition = convertToIfCondition(context, item.value, JOB_IF_CONTEXT);
|
||||
break;
|
||||
|
||||
case "name":
|
||||
name = item.value.assertScalar("job name");
|
||||
break;
|
||||
@@ -134,7 +140,7 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
|
||||
id: jobKey,
|
||||
name: jobName(name, jobKey),
|
||||
needs: needs || [],
|
||||
if: new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined),
|
||||
if: ifCondition || new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined),
|
||||
ref: workflowJobRef,
|
||||
"input-definitions": undefined,
|
||||
"input-values": workflowJobInputs,
|
||||
@@ -151,7 +157,7 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
|
||||
id: jobKey,
|
||||
name: jobName(name, jobKey),
|
||||
needs,
|
||||
if: new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined),
|
||||
if: ifCondition || new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined),
|
||||
env,
|
||||
concurrency,
|
||||
environment,
|
||||
|
||||
@@ -2,6 +2,7 @@ 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";
|
||||
import {ActionStep, Step} from "../workflow-template";
|
||||
import {handleTemplateTokenErrors} from "./handle-errors";
|
||||
import {IdBuilder} from "./id-builder";
|
||||
@@ -52,7 +53,7 @@ function convertStep(context: TemplateContext, idBuilder: IdBuilder, step: Templ
|
||||
let uses: StringToken | undefined;
|
||||
let continueOnError: boolean | ScalarToken | undefined;
|
||||
let env: MappingToken | undefined;
|
||||
const ifCondition = new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined);
|
||||
let ifCondition: BasicExpressionToken | undefined;
|
||||
for (const item of mapping) {
|
||||
const key = item.key.assertString("steps item key");
|
||||
switch (key.value) {
|
||||
@@ -77,6 +78,9 @@ function convertStep(context: TemplateContext, idBuilder: IdBuilder, step: Templ
|
||||
case "env":
|
||||
env = item.value.assertMapping("step env");
|
||||
break;
|
||||
case "if":
|
||||
ifCondition = convertToIfCondition(context, item.value, STEP_IF_CONTEXT);
|
||||
break;
|
||||
case "continue-on-error":
|
||||
if (!item.value.isExpression) {
|
||||
continueOnError = item.value.assertBoolean("steps item continue-on-error").value;
|
||||
@@ -90,7 +94,7 @@ function convertStep(context: TemplateContext, idBuilder: IdBuilder, step: Templ
|
||||
return {
|
||||
id: id?.value || "",
|
||||
name,
|
||||
if: ifCondition,
|
||||
if: ifCondition || new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined),
|
||||
"continue-on-error": continueOnError,
|
||||
env,
|
||||
run
|
||||
@@ -101,7 +105,7 @@ function convertStep(context: TemplateContext, idBuilder: IdBuilder, step: Templ
|
||||
return {
|
||||
id: id?.value || "",
|
||||
name,
|
||||
if: ifCondition,
|
||||
if: ifCondition || new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined),
|
||||
"continue-on-error": continueOnError,
|
||||
env,
|
||||
uses
|
||||
@@ -134,3 +138,10 @@ function createActionStepId(step: ActionStep): string {
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user