From 29ea164a1f95420052a5df3699cf4c1176a1852f Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Wed, 10 Jun 2026 11:55:00 +0100 Subject: [PATCH 1/6] add failing test --- packages/deploy/test/stateTransform.test.ts | 22 +++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/deploy/test/stateTransform.test.ts b/packages/deploy/test/stateTransform.test.ts index 8b55dbcb9..7315199a6 100644 --- a/packages/deploy/test/stateTransform.test.ts +++ b/packages/deploy/test/stateTransform.test.ts @@ -401,6 +401,23 @@ test('toNextState removing a job and edge', (t) => { t.deepEqual(result, existingState); }); +test.only('toNextState deleting a whole workflow', (t) => { + let existingState = fullExampleState(); + let spec = fullExampleSpec(); + + delete spec.workflows['workflow-one']; + + let result = mergeSpecIntoState(existingState, spec); + + const workflowId = existingState.workflows['workflow-one'].id; + jp.apply(existingState, '$.workflows["workflow-one"]', () => ({ + id: workflowId, + delete: true, + })); + + t.deepEqual(result, existingState); +}); + test('toNextState with for kafka trigger', (t) => { const state = { workflows: {} }; const spec = { @@ -1133,10 +1150,7 @@ test('toNextState resolves channel destination_credential to id', (t) => { const result = mergeSpecIntoState(state, spec); - t.is( - result.channels['webhook-out'].destination_credential_id, - 'cred-id-123' - ); + t.is(result.channels['webhook-out'].destination_credential_id, 'cred-id-123'); }); test('toNextState throws when channel references unknown credential', (t) => { From 3f390460971f765736d20a4db057b223757c8ec7 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Wed, 10 Jun 2026 12:08:36 +0100 Subject: [PATCH 2/6] ensure v1 can remove a workflow --- packages/deploy/src/index.ts | 2 +- packages/deploy/src/stateTransform.ts | 11 +---------- packages/deploy/test/stateTransform.test.ts | 5 ++--- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/packages/deploy/src/index.ts b/packages/deploy/src/index.ts index 7aeae9e12..d5aaad97e 100644 --- a/packages/deploy/src/index.ts +++ b/packages/deploy/src/index.ts @@ -117,7 +117,7 @@ export async function deploy(config: DeployConfig, logger: Logger) { spec.errors.forEach((e: any) => logger.warn(`${e.path} :: ${e.message}`)); throw new DeployError(`${config.specPath} has errors`, 'VALIDATION_ERROR'); } - const nextState = mergeSpecIntoState(state, spec.doc, logger); + const nextState = mergeSpecIntoState(state, spec.doc); validateProjectState(nextState); diff --git a/packages/deploy/src/stateTransform.ts b/packages/deploy/src/stateTransform.ts index 69e28e20f..7d9238596 100644 --- a/packages/deploy/src/stateTransform.ts +++ b/packages/deploy/src/stateTransform.ts @@ -21,7 +21,6 @@ import { assignIfTruthy, } from './utils'; import { DeployError } from './deployError'; -import { Logger } from '@openfn/logger/dist'; function stringifyJobBody(body: SpecJobBody): string { if (typeof body === 'object') { @@ -301,7 +300,6 @@ function mergeEdges( export function mergeSpecIntoState( oldState: ProjectState, spec: ProjectSpec, - logger?: Logger ): ProjectState { const nextCredentials = Object.fromEntries( splitZip(oldState.project_credentials || {}, spec.credentials || {}).map( @@ -462,14 +460,7 @@ export function mergeSpecIntoState( } if (!specWorkflow && !isEmpty(stateWorkflow || {})) { - logger?.error('Critical error! Cannot continue'); - logger?.error( - 'Workflow found in project state but not spec:', - stateWorkflow?.name - ? `${stateWorkflow.name} (${stateWorkflow?.id})` - : stateWorkflow?.id - ); - process.exit(1); + return [workflowKey, { id: stateWorkflow!.id, delete: true }]; } return [ diff --git a/packages/deploy/test/stateTransform.test.ts b/packages/deploy/test/stateTransform.test.ts index 7315199a6..924956afc 100644 --- a/packages/deploy/test/stateTransform.test.ts +++ b/packages/deploy/test/stateTransform.test.ts @@ -409,9 +409,8 @@ test.only('toNextState deleting a whole workflow', (t) => { let result = mergeSpecIntoState(existingState, spec); - const workflowId = existingState.workflows['workflow-one'].id; - jp.apply(existingState, '$.workflows["workflow-one"]', () => ({ - id: workflowId, + jp.apply(existingState, '$.workflows["workflow-one"]', (value) => ({ + id: value.id, delete: true, })); From 97dabc405e7fd51260c54064831eb2dd49cee939 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Wed, 10 Jun 2026 12:19:05 +0100 Subject: [PATCH 3/6] changeset --- .changeset/busy-rats-tickle.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/busy-rats-tickle.md diff --git a/.changeset/busy-rats-tickle.md b/.changeset/busy-rats-tickle.md new file mode 100644 index 000000000..d2b6fd2de --- /dev/null +++ b/.changeset/busy-rats-tickle.md @@ -0,0 +1,5 @@ +--- +'@openfn/deploy': patch +--- + +Ensure that workflows can be removed in v1 sync From b506a7e5e9f6185bdc89b637c709f8d4a619c2a5 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Wed, 10 Jun 2026 14:45:50 +0100 Subject: [PATCH 4/6] don't blow up on deleted workflows --- packages/deploy/src/stateTransform.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/deploy/src/stateTransform.ts b/packages/deploy/src/stateTransform.ts index 7d9238596..e40388474 100644 --- a/packages/deploy/src/stateTransform.ts +++ b/packages/deploy/src/stateTransform.ts @@ -299,7 +299,7 @@ function mergeEdges( // Prepare the next state, based on the current state and the spec. export function mergeSpecIntoState( oldState: ProjectState, - spec: ProjectSpec, + spec: ProjectSpec ): ProjectState { const nextCredentials = Object.fromEntries( splitZip(oldState.project_credentials || {}, spec.credentials || {}).map( @@ -647,16 +647,16 @@ export function toProjectPayload(state: ProjectState): ProjectPayload { // the server expects lists of jobs, triggers, and edges, so we need to // convert the keyed objects into lists. - const workflows: ProjectPayload['workflows'] = Object.values( - state.workflows - ).map((workflow) => { - return { - ...workflow, - jobs: Object.values(workflow.jobs), - triggers: Object.values(workflow.triggers), - edges: Object.values(workflow.edges), - }; - }); + const workflows: ProjectPayload['workflows'] = Object.values(state.workflows) + .filter((workflow) => !workflow.delete) + .map((workflow) => { + return { + ...workflow, + jobs: Object.values(workflow.jobs), + triggers: Object.values(workflow.triggers), + edges: Object.values(workflow.edges), + }; + }); const project_credentials: ProjectPayload['project_credentials'] = Object.values(state.project_credentials); From 92c973cbd0c6169514b4b9b359acdd5115a8de34 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Wed, 10 Jun 2026 16:52:33 +0100 Subject: [PATCH 5/6] version: cli@1.38.1 --- .changeset/busy-rats-tickle.md | 5 ----- packages/cli/CHANGELOG.md | 7 +++++++ packages/cli/package.json | 2 +- packages/deploy/CHANGELOG.md | 6 ++++++ packages/deploy/package.json | 2 +- 5 files changed, 15 insertions(+), 7 deletions(-) delete mode 100644 .changeset/busy-rats-tickle.md diff --git a/.changeset/busy-rats-tickle.md b/.changeset/busy-rats-tickle.md deleted file mode 100644 index d2b6fd2de..000000000 --- a/.changeset/busy-rats-tickle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@openfn/deploy': patch ---- - -Ensure that workflows can be removed in v1 sync diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 99ccbabf0..98b6bf472 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,12 @@ # @openfn/cli +## 1.38.1 + +### Patch Changes + +- Updated dependencies [97dabc4] + - @openfn/deploy@0.13.1 + ## 1.38.0 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 1ba4d5a0d..ade33cf32 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openfn/cli", - "version": "1.38.0", + "version": "1.38.1", "description": "CLI devtools for the OpenFn toolchain", "engines": { "node": ">=18", diff --git a/packages/deploy/CHANGELOG.md b/packages/deploy/CHANGELOG.md index bdc72b1fc..c3996736a 100644 --- a/packages/deploy/CHANGELOG.md +++ b/packages/deploy/CHANGELOG.md @@ -1,5 +1,11 @@ # @openfn/deploy +## 0.13.1 + +### Patch Changes + +- 97dabc4: Ensure that workflows can be removed in v1 sync + ## 0.13.0 ### Minor Changes diff --git a/packages/deploy/package.json b/packages/deploy/package.json index aa8f60188..6c6ef2123 100644 --- a/packages/deploy/package.json +++ b/packages/deploy/package.json @@ -1,6 +1,6 @@ { "name": "@openfn/deploy", - "version": "0.13.0", + "version": "0.13.1", "description": "Deploy projects to Lightning instances", "type": "module", "exports": { From 291d8d9fe25f2fae5dc33c61ca33545541e4147a Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 11 Jun 2026 09:31:28 +0100 Subject: [PATCH 6/6] remove only --- packages/deploy/test/stateTransform.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/deploy/test/stateTransform.test.ts b/packages/deploy/test/stateTransform.test.ts index 924956afc..7d8163581 100644 --- a/packages/deploy/test/stateTransform.test.ts +++ b/packages/deploy/test/stateTransform.test.ts @@ -401,7 +401,7 @@ test('toNextState removing a job and edge', (t) => { t.deepEqual(result, existingState); }); -test.only('toNextState deleting a whole workflow', (t) => { +test('toNextState deleting a whole workflow', (t) => { let existingState = fullExampleState(); let spec = fullExampleSpec();