From cb21649290f44b1ec5497a86c4e16683596adfc7 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Fri, 7 Aug 2026 07:13:21 +0100 Subject: [PATCH 1/5] mock: tighten provisioner validation --- .changeset/kind-symbols-love.md | 5 + packages/lightning-mock/src/api-rest.ts | 61 +++++++++++- packages/lightning-mock/test/rest.test.ts | 112 +++++++++++++++++++++- 3 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 .changeset/kind-symbols-love.md diff --git a/.changeset/kind-symbols-love.md b/.changeset/kind-symbols-love.md new file mode 100644 index 000000000..72305f7da --- /dev/null +++ b/.changeset/kind-symbols-love.md @@ -0,0 +1,5 @@ +--- +'@openfn/lightning-mock': patch +--- + +Tighten validation on provisioner to ensure ids on entities diff --git a/packages/lightning-mock/src/api-rest.ts b/packages/lightning-mock/src/api-rest.ts index 6dc8d6e64..6c5cc1b28 100644 --- a/packages/lightning-mock/src/api-rest.ts +++ b/packages/lightning-mock/src/api-rest.ts @@ -95,23 +95,74 @@ export function validateProvisionPayload( : Object.values(incoming.workflows ?? {}); for (const wf of wfList) { + const wfErrors: Record = {}; + + if (!wf.id) { + wfErrors.id = ["This field can't be blank"]; + } + const edgeErrors: Record = {}; const edgeList: any[] = Array.isArray(wf.edges) ? wf.edges : Object.values(wf.edges ?? {}); for (const edge of edgeList) { + const key = edge.id ?? '->'; + const fieldErrors: Record = {}; + if (!edge.delete && !edge.source_trigger_id && !edge.source_job_id) { - const key = edge.id ?? '->'; - edgeErrors[key] = { - source_job_id: ['source_job_id or source_trigger_id must be present'], - }; + fieldErrors.source_job_id = [ + 'source_job_id or source_trigger_id must be present', + ]; + } + if (!edge.id) { + fieldErrors.id = ["This field can't be blank"]; + } + + if (Object.keys(fieldErrors).length > 0) { + edgeErrors[key] = fieldErrors; } } if (Object.keys(edgeErrors).length > 0) { + wfErrors.edges = edgeErrors; + } + + const jobErrors: Record = {}; + const jobList: any[] = Array.isArray(wf.jobs) + ? wf.jobs + : Object.values(wf.jobs ?? {}); + + for (const job of jobList) { + if (!job.id) { + const key = job.name ?? 'unknown'; + jobErrors[key] = { id: ["This field can't be blank"] }; + } + } + + if (Object.keys(jobErrors).length > 0) { + wfErrors.jobs = jobErrors; + } + + const triggerErrors: Record = {}; + const triggerList: any[] = Array.isArray(wf.triggers) + ? wf.triggers + : Object.values(wf.triggers ?? {}); + + for (const trigger of triggerList) { + if (!trigger.id) { + const key = trigger.type ?? 'unknown'; + triggerErrors[key] = { id: ["This field can't be blank"] }; + } + } + + if (Object.keys(triggerErrors).length > 0) { + wfErrors.triggers = triggerErrors; + } + + if (Object.keys(wfErrors).length > 0) { const wfKey = wf.name ?? wf.id ?? 'unknown'; - workflowErrors[wfKey] = { edges: edgeErrors }; + workflowErrors[wfKey] = wfErrors; } } diff --git a/packages/lightning-mock/test/rest.test.ts b/packages/lightning-mock/test/rest.test.ts index 87db89a96..21d13145e 100644 --- a/packages/lightning-mock/test/rest.test.ts +++ b/packages/lightning-mock/test/rest.test.ts @@ -90,6 +90,7 @@ test('validateProvisionPayload: returns null for a valid edge with source_trigge id: 'proj-1', workflows: [ { + id: 'wf-1', name: 'wf1', edges: [ { @@ -110,6 +111,7 @@ test('validateProvisionPayload: returns null for a valid edge with source_job_id id: 'proj-1', workflows: [ { + id: 'wf-1', name: 'wf1', edges: [ { @@ -130,6 +132,7 @@ test('validateProvisionPayload: returns errors when edge has no source', (t) => id: 'proj-1', workflows: [ { + id: 'wf-1', name: 'wf1', edges: [ { @@ -166,6 +169,7 @@ test('validateProvisionPayload: returns null for deleted edges', (t) => { id: 'proj-1', workflows: [ { + id: 'wf-1', name: 'wf1', edges: [ { @@ -183,11 +187,117 @@ test('validateProvisionPayload: returns null for deleted edges', (t) => { test('validateProvisionPayload: returns null when there are no edges', (t) => { const payload = { id: 'proj-1', - workflows: [{ name: 'wf1', edges: [] }], + workflows: [{ id: 'wf-1', name: 'wf1', edges: [] }], }; t.is(validateProvisionPayload(payload), null); }); +test('validateProvisionPayload: returns an error when a workflow has no id', (t) => { + const payload = { + id: 'proj-1', + workflows: [{ name: 'wf1', edges: [] }], + }; + const result = validateProvisionPayload(payload); + t.deepEqual(result, { + errors: { + workflows: { + wf1: { + id: ["This field can't be blank"], + }, + }, + }, + }); +}); + +test('validateProvisionPayload: returns an error when an edge has no id', (t) => { + const payload = { + id: 'proj-1', + workflows: [ + { + id: 'wf-1', + name: 'wf1', + edges: [ + { + source_trigger_id: 'trig-uuid', + target_job_id: 'job-uuid', + enabled: true, + }, + ], + }, + ], + }; + const result = validateProvisionPayload(payload); + t.deepEqual(result, { + errors: { + workflows: { + wf1: { + edges: { + '->': { + id: ["This field can't be blank"], + }, + }, + }, + }, + }, + }); +}); + +test('validateProvisionPayload: returns an error when a job has no id', (t) => { + const payload = { + id: 'proj-1', + workflows: [ + { + id: 'wf-1', + name: 'wf1', + edges: [], + jobs: [{ name: 'Transform data' }], + }, + ], + }; + const result = validateProvisionPayload(payload); + t.deepEqual(result, { + errors: { + workflows: { + wf1: { + jobs: { + 'Transform data': { + id: ["This field can't be blank"], + }, + }, + }, + }, + }, + }); +}); + +test('validateProvisionPayload: returns an error when a trigger has no id', (t) => { + const payload = { + id: 'proj-1', + workflows: [ + { + id: 'wf-1', + name: 'wf1', + edges: [], + triggers: [{ type: 'webhook', enabled: true }], + }, + ], + }; + const result = validateProvisionPayload(payload); + t.deepEqual(result, { + errors: { + workflows: { + wf1: { + triggers: { + webhook: { + id: ["This field can't be blank"], + }, + }, + }, + }, + }, + }); +}); + test.serial( 'should return 422 when a workflow edge has no source', async (t) => { From d3213e61ec743165296ee434ae17a11ef0bfa109 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Fri, 7 Aug 2026 07:21:55 +0100 Subject: [PATCH 2/5] fix issue --- .changeset/thirty-experts-notice.md | 5 +++++ packages/project/src/serialize/to-app-state.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/thirty-experts-notice.md diff --git a/.changeset/thirty-experts-notice.md b/.changeset/thirty-experts-notice.md new file mode 100644 index 000000000..2d09d8af1 --- /dev/null +++ b/.changeset/thirty-experts-notice.md @@ -0,0 +1,5 @@ +--- +'@openfn/project': patch +--- + +Fix an issue where new workflows do not get assigned a UUID diff --git a/packages/project/src/serialize/to-app-state.ts b/packages/project/src/serialize/to-app-state.ts index 5864c2a25..4e923401a 100644 --- a/packages/project/src/serialize/to-app-state.ts +++ b/packages/project/src/serialize/to-app-state.ts @@ -116,7 +116,7 @@ export const mapWorkflow = ( } as Provisioner.Workflow; if (useUuids) { - wfState.id = (workflow.openfn?.uuid ?? randomUUID) as any; + wfState.id = (workflow.openfn?.uuid ?? randomUUID()) as any; } if (workflow.name) { From 022de6633b8c9bd4aa7a05e464cca23b81ec3db4 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Fri, 7 Aug 2026 07:47:16 +0100 Subject: [PATCH 3/5] runtime: fix flaky test --- packages/runtime/test/execute/step.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/runtime/test/execute/step.test.ts b/packages/runtime/test/execute/step.test.ts index b8517fb0e..18bfd6656 100644 --- a/packages/runtime/test/execute/step.test.ts +++ b/packages/runtime/test/execute/step.test.ts @@ -265,7 +265,7 @@ test.serial('log duration of execution', async (t) => { const duration = logger._find('success', /completed in/i); - t.regex(duration?.message, /y completed in \d\d?ms/i); + t.regex(duration?.message, /y completed in \d+(\.\d+)?(ms|s|m)/i); }); test.serial('log memory usage', async (t) => { @@ -303,8 +303,8 @@ test.serial('log memory usage with profiler and peak', async (t) => { await execute(context, step, initialState); const memory = logger._find('debug', /step memory usage/i); - // All we're looking for here is two strings of numbers in mb - t.regex(memory?.message, /peak (\d)+(.+)\d(\d?)mb/i); + // All we're looking for here is a number (optionally decimal) in mb + t.regex(memory?.message, /peak \d+(\.\d+)?mb/i); }); test.serial('warn if a non-leaf step does not return state', async (t) => { From e40e618ac0c61f325d0b08a1f172ac147f9d67e5 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Fri, 7 Aug 2026 08:00:56 +0100 Subject: [PATCH 4/5] version: cli@1.39.3 --- .changeset/kind-symbols-love.md | 5 ----- .changeset/thirty-experts-notice.md | 5 ----- integration-tests/cli/CHANGELOG.md | 9 +++++++++ integration-tests/cli/package.json | 2 +- packages/cli/CHANGELOG.md | 9 +++++++++ packages/cli/package.json | 8 ++++---- packages/lightning-mock/CHANGELOG.md | 8 ++++++++ packages/lightning-mock/package.json | 2 +- packages/project/CHANGELOG.md | 6 ++++++ packages/project/package.json | 2 +- 10 files changed, 39 insertions(+), 17 deletions(-) delete mode 100644 .changeset/kind-symbols-love.md delete mode 100644 .changeset/thirty-experts-notice.md diff --git a/.changeset/kind-symbols-love.md b/.changeset/kind-symbols-love.md deleted file mode 100644 index 72305f7da..000000000 --- a/.changeset/kind-symbols-love.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@openfn/lightning-mock': patch ---- - -Tighten validation on provisioner to ensure ids on entities diff --git a/.changeset/thirty-experts-notice.md b/.changeset/thirty-experts-notice.md deleted file mode 100644 index 2d09d8af1..000000000 --- a/.changeset/thirty-experts-notice.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@openfn/project': patch ---- - -Fix an issue where new workflows do not get assigned a UUID diff --git a/integration-tests/cli/CHANGELOG.md b/integration-tests/cli/CHANGELOG.md index f5169ce86..0e85403fd 100644 --- a/integration-tests/cli/CHANGELOG.md +++ b/integration-tests/cli/CHANGELOG.md @@ -1,5 +1,14 @@ # @openfn/integration-tests-cli +## 1.0.27 + +### Patch Changes + +- Updated dependencies [cb21649] +- Updated dependencies [d3213e6] + - @openfn/lightning-mock@2.4.27 + - @openfn/project@0.18.1 + ## 1.0.26 ### Patch Changes diff --git a/integration-tests/cli/package.json b/integration-tests/cli/package.json index 6b5890c57..9aa0cc1e4 100644 --- a/integration-tests/cli/package.json +++ b/integration-tests/cli/package.json @@ -1,7 +1,7 @@ { "name": "@openfn/integration-tests-cli", "private": true, - "version": "1.0.26", + "version": "1.0.27", "description": "CLI integration tests", "author": "Open Function Group ", "license": "ISC", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index bc8da1c5d..eba8b1986 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,14 @@ # @openfn/cli +## 1.39.3 + +### Patch Changes + +Fix an issue where `openfn project deploy --new` drops UUIDs on workflows + +- Updated dependencies [d3213e6] + - @openfn/project@0.18.1 + ## 1.39.2 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 31443bfbe..4b9952642 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openfn/cli", - "version": "1.39.2", + "version": "1.39.3", "description": "CLI devtools for the OpenFn toolchain", "engines": { "node": ">=18", @@ -55,9 +55,9 @@ "@openfn/compiler": "workspace:*", "@openfn/deploy": "workspace:*", "@openfn/describe-package": "workspace:*", - "@openfn/lexicon": "workspace:^", + "@openfn/lexicon": "workspace:*", "@openfn/logger": "workspace:*", - "@openfn/project": "workspace:^", + "@openfn/project": "workspace:*", "@openfn/runtime": "workspace:*", "chalk": "^5.6.2", "chokidar": "^3.6.0", @@ -75,4 +75,4 @@ "dist", "README.md" ] -} \ No newline at end of file +} diff --git a/packages/lightning-mock/CHANGELOG.md b/packages/lightning-mock/CHANGELOG.md index 71ddbc013..df5a907cd 100644 --- a/packages/lightning-mock/CHANGELOG.md +++ b/packages/lightning-mock/CHANGELOG.md @@ -1,5 +1,13 @@ # @openfn/lightning-mock +## 2.4.27 + +### Patch Changes + +- cb21649: Tighten validation on provisioner to ensure ids on entities +- Updated dependencies [d3213e6] + - @openfn/project@0.18.1 + ## 2.4.26 ### Patch Changes diff --git a/packages/lightning-mock/package.json b/packages/lightning-mock/package.json index ddfb77962..857fad3dc 100644 --- a/packages/lightning-mock/package.json +++ b/packages/lightning-mock/package.json @@ -1,6 +1,6 @@ { "name": "@openfn/lightning-mock", - "version": "2.4.26", + "version": "2.4.27", "private": true, "description": "A mock Lightning server", "main": "dist/index.js", diff --git a/packages/project/CHANGELOG.md b/packages/project/CHANGELOG.md index d8588ef1e..606dfef88 100644 --- a/packages/project/CHANGELOG.md +++ b/packages/project/CHANGELOG.md @@ -1,5 +1,11 @@ # @openfn/project +## 0.18.1 + +### Patch Changes + +- d3213e6: Fix an issue where new workflows do not get assigned a UUID + ## 0.18.0 ### Minor Changes diff --git a/packages/project/package.json b/packages/project/package.json index cb2564f8a..572ec10d1 100644 --- a/packages/project/package.json +++ b/packages/project/package.json @@ -1,6 +1,6 @@ { "name": "@openfn/project", - "version": "0.18.0", + "version": "0.18.1", "description": "Read, serialize, replicate and sync OpenFn projects", "scripts": { "test": "pnpm ava", From 8947bd9f6fda5b590618e270aee2087b5497daf6 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Fri, 7 Aug 2026 08:04:40 +0100 Subject: [PATCH 5/5] update lockfile --- pnpm-lock.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48b884fb8..ada558b28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -199,13 +199,13 @@ importers: specifier: workspace:* version: link:../describe-package '@openfn/lexicon': - specifier: workspace:^ + specifier: workspace:* version: link:../lexicon '@openfn/logger': specifier: workspace:* version: link:../logger '@openfn/project': - specifier: workspace:^ + specifier: workspace:* version: link:../project '@openfn/runtime': specifier: workspace:*