Skip to content

Commit

Permalink
feat(circleci): allow workflow job options to be merged
Browse files Browse the repository at this point in the history
  • Loading branch information
ivomurrell committed Nov 27, 2024
1 parent c39850a commit e33e579
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 6 deletions.
40 changes: 34 additions & 6 deletions plugins/circleci/src/circleci-config.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -226,6 +227,27 @@ const customOptionsOverlap = (
})
}

const areDefinedAndUnequal = <T>(a: T | undefined, b: T | undefined): boolean =>
a !== undefined && b !== undefined && a !== b

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 &&
(areDefinedAndUnequal(installationWorkflowJob.runOnRelease, otherWorkflowJob.runOnRelease) ||
areDefinedAndUnequal(installationWorkflowJob.splitIntoMatrix, otherWorkflowJob.splitIntoMatrix) ||
customOptionsOverlap(installationWorkflowJob.custom, otherWorkflowJob.custom))
)
})
}

const workflowOptionsOverlap = (installation?: CircleCiWorkflow[], other?: CircleCiWorkflow[]): boolean => {
if (!installation || !other) {
return false
Expand All @@ -234,11 +256,9 @@ const workflowOptionsOverlap = (installation?: CircleCiWorkflow[], other?: Circl
const otherWorkflow = other.find(({ name }) => installationWorkflow.name === name)
return (
otherWorkflow &&
((installationWorkflow.runOnRelease !== undefined &&
otherWorkflow.runOnRelease !== undefined &&
installationWorkflow.runOnRelease !== otherWorkflow.runOnRelease) ||
(areDefinedAndUnequal(installationWorkflow.runOnRelease, otherWorkflow.runOnRelease) ||
customOptionsOverlap(installationWorkflow.custom, otherWorkflow.custom) ||
rootOptionOverlaps(installationWorkflow.jobs, otherWorkflow.jobs))
workflowJobOptionsOverlap(installationWorkflow.jobs, otherWorkflow.jobs))
)
})
}
Expand Down Expand Up @@ -290,7 +310,15 @@ const mergeInstallations = (installations: HookInstallation<CircleCiOptions>[]):
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
Expand Down
45 changes: 45 additions & 0 deletions plugins/circleci/test/circleci-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,5 +412,50 @@ describe('CircleCI config hook', () => {
}
])
})

it('should merge sibling plugins with different custom fields for a workflow job', () => {
const childInstallations: HookInstallation<CircleCiOptions>[] = [
{
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' })
})
])
})
])
})
}
])
})
})
})

0 comments on commit e33e579

Please sign in to comment.