From 617bc6f58c4446e2361c5fc60e680fd6b2ccb129 Mon Sep 17 00:00:00 2001 From: David Mokos Date: Wed, 2 Sep 2026 17:45:18 +0200 Subject: [PATCH] allows interpolated URIs in workflow validation --- .../workflow/__tests__/validation-test.ts | 62 +++++++++++++++++++ .../src/commandUtils/workflow/validation.ts | 12 ++-- 2 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 packages/eas-cli/src/commandUtils/workflow/__tests__/validation-test.ts diff --git a/packages/eas-cli/src/commandUtils/workflow/__tests__/validation-test.ts b/packages/eas-cli/src/commandUtils/workflow/__tests__/validation-test.ts new file mode 100644 index 0000000000..62a3700d69 --- /dev/null +++ b/packages/eas-cli/src/commandUtils/workflow/__tests__/validation-test.ts @@ -0,0 +1,62 @@ +import { validateWorkflowStructure } from '../validation'; + +const workflowSchema = { + type: 'object', + properties: { + jobs: { + type: 'object', + additionalProperties: { + anyOf: [ + { + type: 'object', + properties: { + type: { type: 'string', const: 'slack' }, + params: { + type: 'object', + properties: { + webhook_url: { type: 'string', format: 'uri' }, + message: { type: 'string' }, + }, + required: ['webhook_url', 'message'], + additionalProperties: false, + }, + }, + required: ['type', 'params'], + additionalProperties: false, + }, + ], + }, + }, + }, + required: ['jobs'], + additionalProperties: false, +}; + +const workflowWithWebhookUrl = (webhookUrl: string): object => ({ + jobs: { + notify: { + type: 'slack', + params: { + webhook_url: webhookUrl, + message: 'Build finished', + }, + }, + }, +}); + +describe(validateWorkflowStructure, () => { + it('allows interpolated values for URI fields', () => { + expect(() => { + validateWorkflowStructure( + workflowWithWebhookUrl('${{ env.SLACK_WEBHOOK_URL }}'), + workflowSchema + ); + }).not.toThrow(); + }); + + it('still rejects invalid literal values for URI fields', () => { + expect(() => { + validateWorkflowStructure(workflowWithWebhookUrl('not a URL'), workflowSchema); + }).toThrow('must be a valid URI string'); + }); +}); diff --git a/packages/eas-cli/src/commandUtils/workflow/validation.ts b/packages/eas-cli/src/commandUtils/workflow/validation.ts index 19e95bafe0..0b9948e5a3 100644 --- a/packages/eas-cli/src/commandUtils/workflow/validation.ts +++ b/packages/eas-cli/src/commandUtils/workflow/validation.ts @@ -2,6 +2,7 @@ import { InvalidEasJsonError, MissingEasJsonError } from '@expo/eas-json/build/e import { CombinedError } from '@urql/core'; import { promises as fs } from 'fs'; import path from 'path'; +import addFormats from 'ajv-formats'; import * as YAML from 'yaml'; import { validateWorkflowLocalCompositeFunctionsAsync } from './compositeFunctions'; @@ -15,9 +16,10 @@ import { ExpoGraphqlClient } from '../context/contextUtils/createGraphqlClient'; import { parsedYamlFromWorkflowContents } from './parse'; const jobTypesWithBuildProfile = new Set(['build', 'repack']); +const validateUri = addFormats.get('uri') as (value: string) => boolean; -const buildProfileIsInterpolated = (profileName: string): boolean => { - return profileName.includes('${{') && profileName.includes('}}'); +const stringIsInterpolated = (value: string): boolean => { + return value.includes('${{') && value.includes('}}'); }; export async function validateWorkflowFileAsync( @@ -113,7 +115,7 @@ async function validateWorkflowBuildJobsAsync(parsedYaml: any, projectDir: strin job => !buildProfileNames.has(job.value.params.profile) && // If a profile name is interpolated, we can't check if it's valid until the workflow actually runs - !buildProfileIsInterpolated(job.value.params.profile) + !stringIsInterpolated(job.value.params.profile) ); if (invalidBuildJobs.length > 0) { @@ -139,10 +141,12 @@ function validateWorkflowJobTypes(parsedYaml: any, workflowJsonSchema: any): voi } } -function validateWorkflowStructure(parsedYaml: any, workflowJsonSchema: any): void { +export function validateWorkflowStructure(parsedYaml: any, workflowJsonSchema: any): void { delete workflowJsonSchema['$schema']; const ajv = createValidator(); + // Interpolated values cannot be format-checked until the workflow runs. + ajv.addFormat('uri', value => stringIsInterpolated(value) || validateUri(value)); const validate = ajv.compile(workflowJsonSchema); const result = validate(parsedYaml);