From fac03cd0fe2c8fd61f9e6a8b74db67ba73412f31 Mon Sep 17 00:00:00 2001 From: Ivo Murrell Date: Wed, 27 Nov 2024 14:44:18 +0000 Subject: [PATCH] feat(circleci): allow workflow job options to be merged --- plugins/circleci/src/circleci-config.ts | 37 +++++++++++++-- plugins/circleci/test/circleci-config.test.ts | 45 +++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/plugins/circleci/src/circleci-config.ts b/plugins/circleci/src/circleci-config.ts index 3a8a0bdff..9693828d2 100644 --- a/plugins/circleci/src/circleci-config.ts +++ b/plugins/circleci/src/circleci-config.ts @@ -1,7 +1,8 @@ import type { CircleCiOptions, CircleCiSchema, - CircleCiWorkflow + CircleCiWorkflow, + CircleCiWorkflowJob } from '@dotcom-tool-kit/schemas/lib/hooks/circleci' import { type Conflict, isConflict } from '@dotcom-tool-kit/conflict' import { Hook, type HookInstallation } from '@dotcom-tool-kit/base' @@ -226,6 +227,28 @@ const customOptionsOverlap = ( }) } +const workflowJobOptionsOverlap = ( + installation?: CircleCiWorkflowJob[], + other?: CircleCiWorkflowJob[] +): boolean => { + if (!installation || !other) { + return false + } + return installation.some((installationWorkflowJob) => { + const otherWorkflowJob = other.find(({ name }) => installationWorkflowJob.name === name) + return ( + otherWorkflowJob && + ((installationWorkflowJob.runOnRelease !== undefined && + otherWorkflowJob.runOnRelease !== undefined && + installationWorkflowJob.runOnRelease !== otherWorkflowJob.runOnRelease) || + (installationWorkflowJob.splitIntoMatrix !== undefined && + otherWorkflowJob.splitIntoMatrix !== undefined && + installationWorkflowJob.splitIntoMatrix !== otherWorkflowJob.splitIntoMatrix) || + customOptionsOverlap(installationWorkflowJob.custom, otherWorkflowJob.custom)) + ) + }) +} + const workflowOptionsOverlap = (installation?: CircleCiWorkflow[], other?: CircleCiWorkflow[]): boolean => { if (!installation || !other) { return false @@ -238,7 +261,7 @@ const workflowOptionsOverlap = (installation?: CircleCiWorkflow[], other?: Circl otherWorkflow.runOnRelease !== undefined && installationWorkflow.runOnRelease !== otherWorkflow.runOnRelease) || customOptionsOverlap(installationWorkflow.custom, otherWorkflow.custom) || - rootOptionOverlaps(installationWorkflow.jobs, otherWorkflow.jobs)) + workflowJobOptionsOverlap(installationWorkflow.jobs, otherWorkflow.jobs)) ) }) } @@ -290,7 +313,15 @@ const mergeInstallations = (installations: HookInstallation[]): const rootOptions = installations.flatMap<{ name: string }>( (installation) => installation.options[rootKey] ?? [] ) - return [rootKey, mergeRootOptions(rootOptions)] + const mergedOptions = mergeRootOptions(rootOptions) + if (rootKey === 'workflows') { + mergedOptions.forEach((workflow: CircleCiWorkflow) => { + if (workflow.jobs) { + workflow.jobs = mergeRootOptions(workflow.jobs) + } + }) + } + return [rootKey, mergedOptions] }) ), // squash all the custom options together diff --git a/plugins/circleci/test/circleci-config.test.ts b/plugins/circleci/test/circleci-config.test.ts index 882fcf0be..c9b0524d6 100644 --- a/plugins/circleci/test/circleci-config.test.ts +++ b/plugins/circleci/test/circleci-config.test.ts @@ -412,5 +412,50 @@ describe('CircleCI config hook', () => { } ]) }) + + it('should merge sibling plugins with different custom fields for a workflow job', () => { + const childInstallations: HookInstallation[] = [ + { + plugin: { id: 'a', root: 'plugins/a' }, + forHook: 'CircleCi', + hookConstructor: CircleCi, + options: { + jobs: [testJob], + workflows: [{ name: 'tool-kit', jobs: [{ ...testWorkflowJob, custom: { param1: 'a' } }] }] + } + }, + { + plugin: { id: 'b', root: 'plugins/b' }, + forHook: 'CircleCi', + hookConstructor: CircleCi, + options: { + workflows: [{ name: 'tool-kit', jobs: [{ name: testWorkflowJob.name, custom: { param2: 'b' } }] }] + } + } + ] + + const plugin = { id: 'p', root: 'plugins/p' } + + expect(CircleCi.mergeChildInstallations(plugin, childInstallations)).toEqual([ + { + plugin, + forHook: 'CircleCi', + hookConstructor: CircleCi, + options: expect.objectContaining({ + workflows: expect.arrayContaining([ + expect.objectContaining({ + name: 'tool-kit', + jobs: expect.arrayContaining([ + expect.objectContaining({ + name: testWorkflowJob.name, + custom: expect.objectContaining({ param1: 'a', param2: 'b' }) + }) + ]) + }) + ]) + }) + } + ]) + }) }) })