From c37dddafe620709fd8a9ccf418aecba645bae588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Chmiela?= Date: Thu, 23 Jan 2025 15:33:24 +0100 Subject: [PATCH 1/4] Interpolate inputs --- packages/steps/src/BuildStep.ts | 11 ++--------- packages/steps/src/BuildStepContext.ts | 21 ++++++++++++++++++++- packages/steps/src/BuildStepInput.ts | 9 ++++++++- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/packages/steps/src/BuildStep.ts b/packages/steps/src/BuildStep.ts index 7d88462e..96f2bebc 100644 --- a/packages/steps/src/BuildStep.ts +++ b/packages/steps/src/BuildStep.ts @@ -347,19 +347,12 @@ export class BuildStep extends BuildStepOutputAccessor { } private getInterpolationContext(): JobInterpolationContext { - const hasAnyPreviousStepFailed = this.ctx.global.hasAnyPreviousStepFailed; - return { - ...this.ctx.global.staticContext, - always: () => true, - never: () => false, - success: () => !hasAnyPreviousStepFailed, - failure: () => hasAnyPreviousStepFailed, + ...this.ctx.global.jobInterpolationContext, env: this.getScriptEnv(), - fromJSON: (json: string) => JSON.parse(json), - toJSON: (value: unknown) => JSON.stringify(value), }; } + private async executeCommandAsync(): Promise { assert(this.command, 'Command must be defined.'); diff --git a/packages/steps/src/BuildStepContext.ts b/packages/steps/src/BuildStepContext.ts index 67b36b7f..879eec5d 100644 --- a/packages/steps/src/BuildStepContext.ts +++ b/packages/steps/src/BuildStepContext.ts @@ -1,7 +1,7 @@ import os from 'os'; import path from 'path'; -import { StaticJobInterpolationContext } from '@expo/eas-build-job'; +import { JobInterpolationContext, StaticJobInterpolationContext } from '@expo/eas-build-job'; import { bunyan } from '@expo/logger'; import { v4 as uuidv4 } from 'uuid'; @@ -107,6 +107,25 @@ export class BuildStepGlobalContext { }; } + public get jobInterpolationContext(): JobInterpolationContext { + const hasAnyPreviousStepFailed = this.hasAnyPreviousStepFailed; + + return { + ...this.staticContext, + always: () => true, + never: () => false, + success: () => !hasAnyPreviousStepFailed, + failure: () => hasAnyPreviousStepFailed, + env: Object.fromEntries( + Object.entries(this.env).flatMap(([key, value]) => + value !== undefined ? [[key, value]] : [] + ) + ), + fromJSON: (json: string) => JSON.parse(json), + toJSON: (value: unknown) => JSON.stringify(value), + }; + } + public updateEnv(updatedEnv: BuildStepEnv): void { this.provider.updateEnv(updatedEnv); } diff --git a/packages/steps/src/BuildStepInput.ts b/packages/steps/src/BuildStepInput.ts index 6a2be73f..976581e6 100644 --- a/packages/steps/src/BuildStepInput.ts +++ b/packages/steps/src/BuildStepInput.ts @@ -8,6 +8,7 @@ import { BUILD_STEP_OR_BUILD_GLOBAL_CONTEXT_REFERENCE_REGEX, interpolateWithOutputs, } from './utils/template.js'; +import { interpolateJobContext } from './interpolation.js'; export enum BuildStepInputValueTypeName { STRING = 'string', @@ -122,7 +123,13 @@ export class BuildStepInput< // `valueDoesNotRequireInterpolation` checks that `rawValue` is not undefined // so this will never be true. assert(rawValue !== undefined); - const valueInterpolatedWithGlobalContext = this.ctx.interpolate(rawValue); + const valueInterpolatedWithNewInterpolation = interpolateJobContext({ + target: rawValue, + context: this.ctx.jobInterpolationContext, + }) as string | object; + const valueInterpolatedWithGlobalContext = this.ctx.interpolate( + valueInterpolatedWithNewInterpolation + ); const valueInterpolatedWithOutputsAndGlobalContext = interpolateWithOutputs( valueInterpolatedWithGlobalContext, (path) => this.ctx.getStepOutputValue(path) ?? '' From 63d589e70d73933f974d7243464e5745fb4b6ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Chmiela?= Date: Thu, 23 Jan 2025 15:43:08 +0100 Subject: [PATCH 2/4] Use current interpolation context when getting input value --- packages/steps/src/BuildStep.ts | 16 +++++++++++----- packages/steps/src/BuildStepInput.ts | 11 +++++++---- packages/steps/src/BuildWorkflowValidator.ts | 11 +++++++---- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/steps/src/BuildStep.ts b/packages/steps/src/BuildStep.ts index 96f2bebc..c9d1f588 100644 --- a/packages/steps/src/BuildStep.ts +++ b/packages/steps/src/BuildStep.ts @@ -319,7 +319,9 @@ export class BuildStep extends BuildStepOutputAccessor { inputs: this.inputs?.reduce( (acc, input) => { - acc[input.id] = input.value; + acc[input.id] = input.getValue({ + interpolationContext: this.getInterpolationContext(), + }); return acc; }, {} as Record @@ -346,7 +348,7 @@ export class BuildStep extends BuildStepOutputAccessor { ); } - private getInterpolationContext(): JobInterpolationContext { + public getInterpolationContext(): JobInterpolationContext { return { ...this.ctx.global.jobInterpolationContext, env: this.getScriptEnv(), @@ -408,10 +410,14 @@ export class BuildStep extends BuildStepOutputAccessor { } const vars = inputs.reduce( (acc, input) => { + const inputValue = input.getValue({ + interpolationContext: this.getInterpolationContext(), + }); + acc[input.id] = - typeof input.value === 'object' - ? JSON.stringify(input.value) - : input.value?.toString() ?? ''; + typeof inputValue === 'object' + ? JSON.stringify(inputValue) + : inputValue?.toString() ?? ''; return acc; }, {} as Record diff --git a/packages/steps/src/BuildStepInput.ts b/packages/steps/src/BuildStepInput.ts index 976581e6..031650e7 100644 --- a/packages/steps/src/BuildStepInput.ts +++ b/packages/steps/src/BuildStepInput.ts @@ -1,6 +1,7 @@ import assert from 'assert'; import { bunyan } from '@expo/logger'; +import { JobInterpolationContext } from '@expo/eas-build-job'; import { BuildStepGlobalContext, SerializedBuildStepGlobalContext } from './BuildStepContext.js'; import { BuildStepRuntimeError } from './errors.js'; @@ -96,9 +97,11 @@ export class BuildStepInput< this.allowedValueTypeName = allowedValueTypeName; } - public get value(): R extends true - ? BuildStepInputValueType - : BuildStepInputValueType | undefined { + public getValue({ + interpolationContext, + }: { + interpolationContext: JobInterpolationContext; + }): R extends true ? BuildStepInputValueType : BuildStepInputValueType | undefined { const rawValue = this._value ?? this.defaultValue; if (this.required && rawValue === undefined) { throw new BuildStepRuntimeError( @@ -125,7 +128,7 @@ export class BuildStepInput< assert(rawValue !== undefined); const valueInterpolatedWithNewInterpolation = interpolateJobContext({ target: rawValue, - context: this.ctx.jobInterpolationContext, + context: interpolationContext, }) as string | object; const valueInterpolatedWithGlobalContext = this.ctx.interpolate( valueInterpolatedWithNewInterpolation diff --git a/packages/steps/src/BuildWorkflowValidator.ts b/packages/steps/src/BuildWorkflowValidator.ts index 5711cc37..7ab0f21e 100644 --- a/packages/steps/src/BuildWorkflowValidator.ts +++ b/packages/steps/src/BuildWorkflowValidator.ts @@ -57,7 +57,8 @@ export class BuildWorkflowValidator { : typeof currentStepInput.rawValue; if ( currentStepInput.rawValue !== undefined && - !currentStepInput.isRawValueStepOrContextReference() && + typeof currentStepInput.rawValue === 'string' && + currentStepInput.rawValue.includes('${') && currentType !== currentStepInput.allowedValueTypeName ) { const error = new BuildConfigError( @@ -81,9 +82,11 @@ export class BuildWorkflowValidator { const error = new BuildConfigError( `Input parameter "${currentStepInput.id}" for step "${ currentStep.displayName - }" is set to "${ - currentStepInput.value - }" which is not one of the allowed values: ${nullthrows(currentStepInput.allowedValues) + }" is set to "${currentStepInput.getValue({ + interpolationContext: currentStep.getInterpolationContext(), + })}" which is not one of the allowed values: ${nullthrows( + currentStepInput.allowedValues + ) .map((i) => `"${i}"`) .join(', ')}.` ); From 195d59dc3a73ce2ab91713298f4d3a54231126ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Chmiela?= Date: Thu, 23 Jan 2025 15:43:59 +0100 Subject: [PATCH 3/4] Revert "Interpolate inputs" This reverts commit 3978133aabf5bf6b648091b295d7c2d006e32271. # Conflicts: # packages/steps/src/BuildStep.ts # packages/steps/src/BuildStepInput.ts --- packages/steps/src/BuildStep.ts | 11 +++++++++-- packages/steps/src/BuildStepContext.ts | 21 +-------------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/packages/steps/src/BuildStep.ts b/packages/steps/src/BuildStep.ts index c9d1f588..5219ec5c 100644 --- a/packages/steps/src/BuildStep.ts +++ b/packages/steps/src/BuildStep.ts @@ -349,12 +349,19 @@ export class BuildStep extends BuildStepOutputAccessor { } public getInterpolationContext(): JobInterpolationContext { + const hasAnyPreviousStepFailed = this.ctx.global.hasAnyPreviousStepFailed; + return { - ...this.ctx.global.jobInterpolationContext, + ...this.ctx.global.staticContext, + always: () => true, + never: () => false, + success: () => !hasAnyPreviousStepFailed, + failure: () => hasAnyPreviousStepFailed, env: this.getScriptEnv(), + fromJSON: (json: string) => JSON.parse(json), + toJSON: (value: unknown) => JSON.stringify(value), }; } - private async executeCommandAsync(): Promise { assert(this.command, 'Command must be defined.'); diff --git a/packages/steps/src/BuildStepContext.ts b/packages/steps/src/BuildStepContext.ts index 879eec5d..67b36b7f 100644 --- a/packages/steps/src/BuildStepContext.ts +++ b/packages/steps/src/BuildStepContext.ts @@ -1,7 +1,7 @@ import os from 'os'; import path from 'path'; -import { JobInterpolationContext, StaticJobInterpolationContext } from '@expo/eas-build-job'; +import { StaticJobInterpolationContext } from '@expo/eas-build-job'; import { bunyan } from '@expo/logger'; import { v4 as uuidv4 } from 'uuid'; @@ -107,25 +107,6 @@ export class BuildStepGlobalContext { }; } - public get jobInterpolationContext(): JobInterpolationContext { - const hasAnyPreviousStepFailed = this.hasAnyPreviousStepFailed; - - return { - ...this.staticContext, - always: () => true, - never: () => false, - success: () => !hasAnyPreviousStepFailed, - failure: () => hasAnyPreviousStepFailed, - env: Object.fromEntries( - Object.entries(this.env).flatMap(([key, value]) => - value !== undefined ? [[key, value]] : [] - ) - ), - fromJSON: (json: string) => JSON.parse(json), - toJSON: (value: unknown) => JSON.stringify(value), - }; - } - public updateEnv(updatedEnv: BuildStepEnv): void { this.provider.updateEnv(updatedEnv); } From f172b43253b8040f67ab89ebeaab4cfb74dd2882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Chmiela?= Date: Thu, 23 Jan 2025 16:58:16 +0100 Subject: [PATCH 4/4] Interpolate job context in inputs too --- packages/steps/src/BuildStepInput.ts | 42 ++++----- packages/steps/src/BuildWorkflowValidator.ts | 5 +- .../src/__tests__/BuildConfigParser-test.ts | 43 ++++++--- .../steps/src/__tests__/BuildFunction-test.ts | 9 +- .../steps/src/__tests__/BuildStep-test.ts | 19 ++-- .../src/__tests__/BuildStepInput-test.ts | 90 +++++++++++++------ .../__tests__/BuildWorkflowValidator-test.ts | 3 - .../src/__tests__/StepsConfigParser-test.ts | 7 +- 8 files changed, 135 insertions(+), 83 deletions(-) diff --git a/packages/steps/src/BuildStepInput.ts b/packages/steps/src/BuildStepInput.ts index 031650e7..3efc582a 100644 --- a/packages/steps/src/BuildStepInput.ts +++ b/packages/steps/src/BuildStepInput.ts @@ -109,30 +109,32 @@ export class BuildStepInput< ); } + const interpolatedValue = interpolateJobContext({ + target: rawValue, + context: interpolationContext, + }); + const valueDoesNotRequireInterpolation = - rawValue === undefined || - rawValue === null || - typeof rawValue === 'boolean' || - typeof rawValue === 'number'; + interpolatedValue === undefined || + interpolatedValue === null || + typeof interpolatedValue === 'boolean' || + typeof interpolatedValue === 'number'; let returnValue; if (valueDoesNotRequireInterpolation) { - if (typeof rawValue !== this.allowedValueTypeName && rawValue !== undefined) { + if ( + typeof interpolatedValue !== this.allowedValueTypeName && + interpolatedValue !== undefined + ) { throw new BuildStepRuntimeError( `Input parameter "${this.id}" for step "${this.stepDisplayName}" must be of type "${this.allowedValueTypeName}".` ); } - returnValue = rawValue as BuildStepInputValueType; + returnValue = interpolatedValue as BuildStepInputValueType; } else { - // `valueDoesNotRequireInterpolation` checks that `rawValue` is not undefined + // `valueDoesNotRequireInterpolation` checks that `interpolatedValue` is not undefined // so this will never be true. - assert(rawValue !== undefined); - const valueInterpolatedWithNewInterpolation = interpolateJobContext({ - target: rawValue, - context: interpolationContext, - }) as string | object; - const valueInterpolatedWithGlobalContext = this.ctx.interpolate( - valueInterpolatedWithNewInterpolation - ); + assert(interpolatedValue !== undefined); + const valueInterpolatedWithGlobalContext = this.ctx.interpolate(interpolatedValue); const valueInterpolatedWithOutputsAndGlobalContext = interpolateWithOutputs( valueInterpolatedWithGlobalContext, (path) => this.ctx.getStepOutputValue(path) ?? '' @@ -218,15 +220,7 @@ export class BuildStepInput< } private parseInputValueToString(value: string): string { - let parsedValue = value; - try { - parsedValue = JSON.parse(`"${value}"`); - } catch (err) { - if (!(err instanceof SyntaxError)) { - throw err; - } - } - return parsedValue; + return `${value}`; } private parseInputValueToNumber(value: string): number { diff --git a/packages/steps/src/BuildWorkflowValidator.ts b/packages/steps/src/BuildWorkflowValidator.ts index 7ab0f21e..d4a148d2 100644 --- a/packages/steps/src/BuildWorkflowValidator.ts +++ b/packages/steps/src/BuildWorkflowValidator.ts @@ -55,10 +55,11 @@ export class BuildWorkflowValidator { typeof currentStepInput.rawValue === 'object' ? BuildStepInputValueTypeName.JSON : typeof currentStepInput.rawValue; + const rawValueMayRequireInterpolation = + typeof currentStepInput.rawValue === 'string' && currentStepInput.rawValue.includes('${'); if ( currentStepInput.rawValue !== undefined && - typeof currentStepInput.rawValue === 'string' && - currentStepInput.rawValue.includes('${') && + !rawValueMayRequireInterpolation && currentType !== currentStepInput.allowedValueTypeName ) { const error = new BuildConfigError( diff --git a/packages/steps/src/__tests__/BuildConfigParser-test.ts b/packages/steps/src/__tests__/BuildConfigParser-test.ts index 7b9da18f..fb186887 100644 --- a/packages/steps/src/__tests__/BuildConfigParser-test.ts +++ b/packages/steps/src/__tests__/BuildConfigParser-test.ts @@ -224,6 +224,7 @@ describe(BuildConfigParser, () => { // property3: value4 // command: echo "Hi, ${ inputs.name }, ${ inputs.boolean_value }!" const step1 = buildSteps[0]; + const step1InterpolationContext = step1.getInterpolationContext(); expect(step1.id).toMatch(UUID_REGEX); expect(step1.name).toBe('Say HI'); expect(step1.command).toBe('echo "Hi, ${ inputs.name }, ${ inputs.boolean_value }!"'); @@ -231,19 +232,29 @@ describe(BuildConfigParser, () => { expect(step1.shell).toBe(getDefaultShell()); expect(step1.inputs).toBeDefined(); expect(step1.inputs?.[0].id).toBe('name'); - expect(step1.inputs?.[0].value).toBe('Dominik Sokal'); + expect(step1.inputs?.[0].getValue({ interpolationContext: step1InterpolationContext })).toBe( + 'Dominik Sokal' + ); expect(step1.inputs?.[0].allowedValueTypeName).toBe(BuildStepInputValueTypeName.STRING); expect(step1.inputs?.[1].id).toBe('country'); - expect(step1.inputs?.[1].value).toBe('Poland'); + expect(step1.inputs?.[1].getValue({ interpolationContext: step1InterpolationContext })).toBe( + 'Poland' + ); expect(step1.inputs?.[1].allowedValueTypeName).toBe(BuildStepInputValueTypeName.STRING); expect(step1.inputs?.[2].id).toBe('boolean_value'); - expect(step1.inputs?.[2].value).toBe(true); + expect(step1.inputs?.[2].getValue({ interpolationContext: step1InterpolationContext })).toBe( + true + ); expect(step1.inputs?.[2].allowedValueTypeName).toBe(BuildStepInputValueTypeName.BOOLEAN); expect(step1.inputs?.[3].id).toBe('number_value'); - expect(step1.inputs?.[3].value).toBe(123); + expect(step1.inputs?.[3].getValue({ interpolationContext: step1InterpolationContext })).toBe( + 123 + ); expect(step1.inputs?.[3].allowedValueTypeName).toBe(BuildStepInputValueTypeName.NUMBER); expect(step1.inputs?.[4].id).toBe('json_value'); - expect(step1.inputs?.[4].value).toMatchObject({ + expect( + step1.inputs?.[4].getValue({ interpolationContext: step1InterpolationContext }) + ).toMatchObject({ property1: 'value1', property2: ['value2', { value3: { property3: 'value4' } }], }); @@ -328,19 +339,24 @@ describe(BuildConfigParser, () => { // - aaa // - bbb const step1 = buildSteps[0]; + const step1InterpolationContext = step1.getInterpolationContext(); expect(step1.id).toMatch(UUID_REGEX); expect(step1.name).toBe('Hi!'); expect(step1.command).toBe('echo "Hi, ${ inputs.name }!"'); expect(step1.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory); expect(step1.shell).toBe(getDefaultShell()); expect(step1.inputs?.[0].id).toBe('name'); - expect(step1.inputs?.[0].value).toBe('Dominik'); + expect(step1.inputs?.[0].getValue({ interpolationContext: step1InterpolationContext })).toBe( + 'Dominik' + ); expect(step1.inputs?.[0].allowedValueTypeName).toBe(BuildStepInputValueTypeName.STRING); expect(step1.inputs?.[1].id).toBe('build_number'); expect(step1.inputs?.[1].rawValue).toBe('${ eas.job.version.buildNumber }'); expect(step1.inputs?.[1].allowedValueTypeName).toBe(BuildStepInputValueTypeName.NUMBER); expect(step1.inputs?.[2].id).toBe('json_input'); - expect(step1.inputs?.[2].value).toMatchObject({ + expect( + step1.inputs?.[2].getValue({ interpolationContext: step1InterpolationContext }) + ).toMatchObject({ property1: 'value1', property2: ['aaa', 'bbb'], }); @@ -356,19 +372,26 @@ describe(BuildConfigParser, () => { // name: Szymon // build_number: 122 const step2 = buildSteps[1]; + const step2InterpolationContext = step2.getInterpolationContext(); expect(step2.id).toMatch(UUID_REGEX); expect(step2.name).toBe('Hi, Szymon!'); expect(step2.command).toBe('echo "Hi, ${ inputs.name }!"'); expect(step2.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory); expect(step2.shell).toBe(getDefaultShell()); expect(step2.inputs?.[0].id).toBe('name'); - expect(step2.inputs?.[0].value).toBe('Szymon'); + expect(step2.inputs?.[0].getValue({ interpolationContext: step2InterpolationContext })).toBe( + 'Szymon' + ); expect(step2.inputs?.[0].allowedValueTypeName).toBe(BuildStepInputValueTypeName.STRING); expect(step2.inputs?.[1].id).toBe('build_number'); - expect(step2.inputs?.[1].value).toBe(122); + expect(step2.inputs?.[1].getValue({ interpolationContext: step2InterpolationContext })).toBe( + 122 + ); expect(step2.inputs?.[1].allowedValueTypeName).toBe(BuildStepInputValueTypeName.NUMBER); expect(step2.inputs?.[2].id).toBe('json_input'); - expect(step2.inputs?.[2].value).toMatchObject({ + expect( + step2.inputs?.[2].getValue({ interpolationContext: step2InterpolationContext }) + ).toMatchObject({ property1: 'value1', property2: ['value2', { value3: { property3: 'value4' } }], }); diff --git a/packages/steps/src/__tests__/BuildFunction-test.ts b/packages/steps/src/__tests__/BuildFunction-test.ts index 67308519..470dffdd 100644 --- a/packages/steps/src/__tests__/BuildFunction-test.ts +++ b/packages/steps/src/__tests__/BuildFunction-test.ts @@ -252,10 +252,11 @@ describe(BuildFunction, () => { }, workingDirectory: ctx.defaultWorkingDirectory, }); - expect(step.inputs?.[0].value).toBe('abc'); - expect(step.inputs?.[1].value).toBe('def'); - expect(step.inputs?.[2].value).toBe(false); - expect(step.inputs?.[3].value).toMatchObject({ + const interpolationContext = step.getInterpolationContext(); + expect(step.inputs?.[0].getValue({ interpolationContext })).toBe('abc'); + expect(step.inputs?.[1].getValue({ interpolationContext })).toBe('def'); + expect(step.inputs?.[2].getValue({ interpolationContext })).toBe(false); + expect(step.inputs?.[3].getValue({ interpolationContext })).toMatchObject({ b: 2, }); }); diff --git a/packages/steps/src/__tests__/BuildStep-test.ts b/packages/steps/src/__tests__/BuildStep-test.ts index 5753cbd8..e8a7f931 100644 --- a/packages/steps/src/__tests__/BuildStep-test.ts +++ b/packages/steps/src/__tests__/BuildStep-test.ts @@ -5,7 +5,7 @@ import { jest } from '@jest/globals'; import { instance, mock, verify, when } from 'ts-mockito'; import { v4 as uuidv4 } from 'uuid'; -import { BuildStep, BuildStepFunction, BuildStepStatus } from '../BuildStep.js'; +import { BuildStep, BuildStepStatus } from '../BuildStep.js'; import { BuildStepInput, BuildStepInputById, @@ -623,18 +623,21 @@ describe(BuildStep, () => { }), ]; - const fn: BuildStepFunction = (_ctx, { inputs, outputs }) => { - outputs.abc.set( - `${inputs.foo1.value} ${inputs.foo2.value} ${inputs.foo3.value} ${inputs.foo4.value}` - ); - }; - const step = new BuildStep(baseStepCtx, { id, displayName, inputs, outputs, - fn, + fn: (_ctx, { inputs, outputs }) => { + const interpolationContext = step.getInterpolationContext(); + outputs.abc.set( + `${inputs.foo1.getValue({ interpolationContext })} ${inputs.foo2.getValue({ + interpolationContext, + })} ${inputs.foo3.getValue({ interpolationContext })} ${inputs.foo4.getValue({ + interpolationContext, + })}` + ); + }, }); await step.executeAsync(); diff --git a/packages/steps/src/__tests__/BuildStepInput-test.ts b/packages/steps/src/__tests__/BuildStepInput-test.ts index aef1f855..6e6d905b 100644 --- a/packages/steps/src/__tests__/BuildStepInput-test.ts +++ b/packages/steps/src/__tests__/BuildStepInput-test.ts @@ -11,6 +11,8 @@ import { import { createGlobalContextMock } from './utils/context.js'; import { createMockLogger } from './utils/logger.js'; +const emptyInterpolationContext = {} as JobInterpolationContext; + describe(BuildStepInput, () => { test('basic case string', () => { const ctx = createGlobalContextMock(); @@ -21,7 +23,7 @@ describe(BuildStepInput, () => { allowedValueTypeName: BuildStepInputValueTypeName.STRING, }); i.set('bar'); - expect(i.value).toBe('bar'); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toBe('bar'); }); test('basic case boolean', () => { @@ -33,7 +35,7 @@ describe(BuildStepInput, () => { required: true, }); i.set(false); - expect(i.value).toBe(false); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toBe(false); }); test('basic case number', () => { @@ -45,7 +47,7 @@ describe(BuildStepInput, () => { required: true, }); i.set(42); - expect(i.value).toBe(42); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toBe(42); }); test('basic case json', () => { @@ -57,7 +59,7 @@ describe(BuildStepInput, () => { required: true, }); i.set({ foo: 'bar' }); - expect(i.value).toEqual({ foo: 'bar' }); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toEqual({ foo: 'bar' }); }); test('basic case undefined', () => { @@ -69,7 +71,7 @@ describe(BuildStepInput, () => { allowedValueTypeName: BuildStepInputValueTypeName.STRING, }); i.set(undefined); - expect(i.value).toBeUndefined(); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toBeUndefined(); }); test('default value string', () => { @@ -81,7 +83,7 @@ describe(BuildStepInput, () => { allowedValueTypeName: BuildStepInputValueTypeName.STRING, required: true, }); - expect(i.value).toBe('baz'); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toBe('baz'); }); test('default value boolean', () => { @@ -93,7 +95,7 @@ describe(BuildStepInput, () => { allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN, required: true, }); - expect(i.value).toBe(true); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toBe(true); }); test('default value json', () => { @@ -105,7 +107,9 @@ describe(BuildStepInput, () => { allowedValueTypeName: BuildStepInputValueTypeName.JSON, required: true, }); - expect(i.value).toEqual({ foo: 'bar' }); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toEqual({ + foo: 'bar', + }); }); test('context value string', () => { @@ -117,7 +121,7 @@ describe(BuildStepInput, () => { required: true, allowedValueTypeName: BuildStepInputValueTypeName.STRING, }); - expect(i.value).toEqual('linux'); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toEqual('linux'); }); test('context value string with newline characters', () => { @@ -135,7 +139,9 @@ describe(BuildStepInput, () => { required: true, allowedValueTypeName: BuildStepInputValueTypeName.STRING, }); - expect(i.value).toEqual('Line 1\nLine 2\n\nLine 3'); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toEqual( + 'Line 1\nLine 2\n\nLine 3' + ); }); test('context value string with doubly escaped newline characters', () => { @@ -153,7 +159,31 @@ describe(BuildStepInput, () => { required: true, allowedValueTypeName: BuildStepInputValueTypeName.STRING, }); - expect(i.value).toEqual('Line 1\nLine 2\n\nLine 3'); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toEqual( + 'Line 1\\nLine 2\\n\\nLine 3' + ); + }); + + it('interpolates correctly', () => { + const ctx = createGlobalContextMock(); + const i = new BuildStepInput(ctx, { + id: 'foo', + stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }), + required: false, + allowedValueTypeName: BuildStepInputValueTypeName.STRING, + }); + i.set('${{ env.MY_ENV_VAR }}'); + + const step = new BuildStep(ctx, { + id: 'test1', + displayName: 'test1', + inputs: [i], + command: '', + env: { + MY_ENV_VAR: 'bar', + }, + }); + expect(i.getValue({ interpolationContext: step.getInterpolationContext() })).toEqual('bar'); }); test('context value number', () => { @@ -178,7 +208,7 @@ describe(BuildStepInput, () => { allowedValueTypeName: BuildStepInputValueTypeName.NUMBER, required: true, }); - expect(i.value).toEqual(42); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toEqual(42); }); test('context value boolean', () => { @@ -205,7 +235,7 @@ describe(BuildStepInput, () => { allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN, required: true, }); - expect(i.value).toEqual(false); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toEqual(false); }); test('context value JSON', () => { @@ -232,7 +262,9 @@ describe(BuildStepInput, () => { allowedValueTypeName: BuildStepInputValueTypeName.JSON, required: true, }); - expect(i.value).toMatchObject({ bar: [1, 2, 3, { baz: { qux: false } }] }); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toMatchObject({ + bar: [1, 2, 3, { baz: { qux: false } }], + }); }); test('invalid context value type number', () => { @@ -259,7 +291,7 @@ describe(BuildStepInput, () => { allowedValueTypeName: BuildStepInputValueTypeName.NUMBER, required: true, }); - expect(() => i.value).toThrowError( + expect(() => i.getValue({ interpolationContext: emptyInterpolationContext })).toThrowError( 'Input parameter "foo" for step "test1" must be of type "number".' ); }); @@ -288,7 +320,7 @@ describe(BuildStepInput, () => { required: true, allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN, }); - expect(() => i.value).toThrowError( + expect(() => i.getValue({ interpolationContext: emptyInterpolationContext })).toThrowError( 'Input parameter "foo" for step "test1" must be of type "boolean".' ); }); @@ -317,7 +349,7 @@ describe(BuildStepInput, () => { allowedValueTypeName: BuildStepInputValueTypeName.JSON, required: true, }); - expect(() => i.value).toThrowError( + expect(() => i.getValue({ interpolationContext: emptyInterpolationContext })).toThrowError( 'Input parameter "foo" for step "test1" must be of type "json".' ); }); @@ -346,7 +378,7 @@ describe(BuildStepInput, () => { bazbaz: ['bazbaz', '${ eas.context_val_1 }', '${ eas.context_val_2.in_val_1 }'], }, }); - expect(i.value).toEqual({ + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toEqual({ foo: 'foo', bar: 'val_1', baz: { @@ -377,7 +409,7 @@ describe(BuildStepInput, () => { bazbaz: ['bazbaz', '${ eas.context_val_1 }'], }, }); - expect(i.value).toEqual({ + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toEqual({ foo: 'foo', bar: 'Line 1\nLine 2\n\nLine 3', baz: { @@ -396,7 +428,7 @@ describe(BuildStepInput, () => { allowedValueTypeName: BuildStepInputValueTypeName.NUMBER, required: true, }); - expect(i.value).toBe(42); + expect(i.getValue({ interpolationContext: emptyInterpolationContext })).toBe(42); }); test('enforces required policy when reading value', () => { @@ -409,7 +441,7 @@ describe(BuildStepInput, () => { }); expect(() => { // eslint-disable-next-line @typescript-eslint/no-unused-expressions - i.value; + i.getValue({ interpolationContext: emptyInterpolationContext }); }).toThrowError( new BuildStepRuntimeError( 'Input parameter "foo" for step "test1" is required but it was not set.' @@ -428,7 +460,7 @@ describe(BuildStepInput, () => { i.set('bar'); expect(() => { // eslint-disable-next-line @typescript-eslint/no-unused-expressions - i.value; + i.getValue({ interpolationContext: emptyInterpolationContext }); }).toThrowError( new BuildStepRuntimeError('Input parameter "foo" for step "test1" must be of type "boolean".') ); @@ -445,7 +477,7 @@ describe(BuildStepInput, () => { i.set('${ eas.runtimePlatform }'); expect(() => { // eslint-disable-next-line @typescript-eslint/no-unused-expressions - i.value; + i.getValue({ interpolationContext: emptyInterpolationContext }); }).toThrowError('Input parameter "foo" for step "test1" must be of type "json".'); }); @@ -460,7 +492,7 @@ describe(BuildStepInput, () => { i.set('${ eas.runtimePlatform }'); expect(() => { // eslint-disable-next-line @typescript-eslint/no-unused-expressions - i.value; + i.getValue({ interpolationContext: emptyInterpolationContext }); }).toThrowError( new BuildStepRuntimeError('Input parameter "foo" for step "test1" must be of type "number".') ); @@ -477,7 +509,7 @@ describe(BuildStepInput, () => { i.set('${ eas.runtimePlatform }'); expect(() => { // eslint-disable-next-line @typescript-eslint/no-unused-expressions - i.value; + i.getValue({ interpolationContext: emptyInterpolationContext }); }).toThrowError( new BuildStepRuntimeError('Input parameter "foo" for step "test1" must be of type "boolean".') ); @@ -542,7 +574,7 @@ describe(BuildStepInput, () => { expect(input.allowedValueTypeName).toBe(BuildStepInputValueTypeName.STRING); expect(input.allowedValues).toEqual(['bar', 'baz']); expect(input.required).toBe(true); - expect(input.value).toBe('bar'); + expect(input.getValue({ interpolationContext: emptyInterpolationContext })).toBe('bar'); }); }); @@ -580,8 +612,8 @@ describe(makeBuildStepInputByIdMap, () => { expect(Object.keys(result).length).toBe(3); expect(result.foo1).toBeDefined(); expect(result.foo2).toBeDefined(); - expect(result.foo1.value).toBe('bar1'); - expect(result.foo2.value).toBe('bar2'); - expect(result.foo3.value).toBe(true); + expect(result.foo1.getValue({ interpolationContext: emptyInterpolationContext })).toBe('bar1'); + expect(result.foo2.getValue({ interpolationContext: emptyInterpolationContext })).toBe('bar2'); + expect(result.foo3.getValue({ interpolationContext: emptyInterpolationContext })).toBe(true); }); }); diff --git a/packages/steps/src/__tests__/BuildWorkflowValidator-test.ts b/packages/steps/src/__tests__/BuildWorkflowValidator-test.ts index 25151243..4a8ca38d 100644 --- a/packages/steps/src/__tests__/BuildWorkflowValidator-test.ts +++ b/packages/steps/src/__tests__/BuildWorkflowValidator-test.ts @@ -219,9 +219,6 @@ describe(BuildWorkflowValidator, () => { expect((error as BuildWorkflowError).errors[2].message).toBe( 'Input parameter "id3" for step "step_id" is set to "abc" which is not of type "json" or is not step or context reference.' ); - expect((error as BuildWorkflowError).errors[3].message).toBe( - 'Input parameter "id6" for step "step_id" is set to "${ wrong.aaa }" which is not of type "number" or is not step or context reference.' - ); }); test('output from future step', async () => { const ctx = createGlobalContextMock(); diff --git a/packages/steps/src/__tests__/StepsConfigParser-test.ts b/packages/steps/src/__tests__/StepsConfigParser-test.ts index 2410f52d..e62b09ef 100644 --- a/packages/steps/src/__tests__/StepsConfigParser-test.ts +++ b/packages/steps/src/__tests__/StepsConfigParser-test.ts @@ -335,22 +335,23 @@ describe(StepsConfigParser, () => { expect(step4.inputs).toBeDefined(); assert(step4.inputs); const [input1, input2, input3, input4] = step4.inputs; + const step4InterpolationContext = step4.getInterpolationContext(); expect(input1.id).toBe('arg1'); - expect(input1.value).toBe('value1'); + expect(input1.getValue({ interpolationContext: step4InterpolationContext })).toBe('value1'); expect(input1.allowedValueTypeName).toBe(BuildStepInputValueTypeName.STRING); expect(input1.allowedValues).toBeUndefined(); expect(input1.defaultValue).toBeUndefined(); expect(input1.rawValue).toBe('value1'); expect(input1.required).toBe(true); expect(input2.id).toBe('arg2'); - expect(input2.value).toBe(2); + expect(input2.getValue({ interpolationContext: step4InterpolationContext })).toBe(2); expect(input2.allowedValueTypeName).toBe(BuildStepInputValueTypeName.NUMBER); expect(input2.allowedValues).toBeUndefined(); expect(input2.defaultValue).toBeUndefined(); expect(input2.rawValue).toBe(2); expect(input2.required).toBe(true); expect(input3.id).toBe('arg3'); - expect(input3.value).toMatchObject({ + expect(input3.getValue({ interpolationContext: step4InterpolationContext })).toMatchObject({ key1: 'value1', key2: ['value1'], });