From f4ebbe01ec992aac105aa434a7a28254d25e1854 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Mon, 11 May 2026 17:41:21 +0100 Subject: [PATCH 01/11] include version number in serialzied projects --- packages/cli/test/projects/fetch.test.ts | 4 +-- packages/cli/test/projects/fixtures.ts | 3 +-- packages/lexicon/portability.d.ts | 12 +++++---- packages/project/src/parse/from-project.ts | 9 ++++--- packages/project/src/serialize/to-fs.ts | 16 +++++++++--- packages/project/src/serialize/to-project.ts | 9 +++---- packages/project/src/util/yaml.ts | 4 ++- .../test/fixtures/sample-v2-project.ts | 7 +++-- .../project/test/parse/from-project.test.ts | 14 ++++++++++ packages/project/test/serialize/to-fs.test.ts | 26 ++++++++++++++++++- packages/project/test/util/yaml.test.ts | 4 +-- 11 files changed, 79 insertions(+), 29 deletions(-) diff --git a/packages/cli/test/projects/fetch.test.ts b/packages/cli/test/projects/fetch.test.ts index 6c90a4a8e..eb1402ed9 100644 --- a/packages/cli/test/projects/fetch.test.ts +++ b/packages/cli/test/projects/fetch.test.ts @@ -402,9 +402,7 @@ test.serial( const json = { id: 'my-project', name: 'My Project', - cli: { - version: 2, - }, + schema_version: '4.0', description: 'my lovely project', collections: [], credentials: [], diff --git a/packages/cli/test/projects/fixtures.ts b/packages/cli/test/projects/fixtures.ts index 8046ba390..b6f425486 100644 --- a/packages/cli/test/projects/fixtures.ts +++ b/packages/cli/test/projects/fixtures.ts @@ -61,8 +61,7 @@ export const myProject_v1: Provisioner.Project = { export const myProject_yaml = `id: my-project name: My Project -cli: - version: 2 +schema_version: "4.0" description: my lovely project collections: [] credentials: [] diff --git a/packages/lexicon/portability.d.ts b/packages/lexicon/portability.d.ts index 8db5f1ee0..6193ec2df 100644 --- a/packages/lexicon/portability.d.ts +++ b/packages/lexicon/portability.d.ts @@ -1,15 +1,13 @@ -/** - * Typings which define the Portability Spec - * See https://docs.openfn.org/documentation/deploy/portability - */ - /** * Schema for portable representation of a Project * AKA Project Spec * Can serialize to JSON or YAML or whatever you like * + * This interface describes the 4.0 Portability Spec: https://docs.openfn.org/documentation/deploy/portability + * * If you serialize a v2 project file without state, this is what you get */ + export interface ProjectSpec { /** Single-word identifier */ id: string; @@ -19,6 +17,8 @@ export interface ProjectSpec { description?: string; + schema_version?: string; + workflows: WorkflowSpec[]; credentials?: Credential[]; @@ -33,6 +33,8 @@ export interface WorkflowSpec { /** Human readable label. Can be used to generate an internal id */ name?: string; + schema_version?: string; + steps: Array; /** Global functions (path to a js file, unsupported in app) */ diff --git a/packages/project/src/parse/from-project.ts b/packages/project/src/parse/from-project.ts index 716df4381..c7f4e08b0 100644 --- a/packages/project/src/parse/from-project.ts +++ b/packages/project/src/parse/from-project.ts @@ -12,7 +12,7 @@ import { WithMeta } from '../Workflow'; // TODO move these types to a common types.ts, or maybe Project.ts export type SerializedProject = Omit, 'workflows'> & { - version: number; + version?: number; workflows: SerializedWorkflow[]; }; @@ -32,8 +32,11 @@ export default ( // first ensure the data is in JSON format let rawJson = ensureJson(data); - if (rawJson.cli?.version ?? rawJson.version /*deprecated*/) { - // If there's any version key at all, its at least v2 + if ( + rawJson.schema_version || + rawJson.cli?.version === 2 || + rawJson.version /*deprecated*/ + ) { return new Project(from_v2(rawJson as SerializedProject), config); } diff --git a/packages/project/src/serialize/to-fs.ts b/packages/project/src/serialize/to-fs.ts index e934ad624..aa35cfe2e 100644 --- a/packages/project/src/serialize/to-fs.ts +++ b/packages/project/src/serialize/to-fs.ts @@ -7,14 +7,19 @@ import { extractConfig } from '../util/config'; const stringify = (json: any) => JSON.stringify(json, null, 2); -export default function (project: Project) { +type ToFsOptions = { + // private: stamp schema_version on each workflow file. off by default + includeSchemaVersion?: boolean; +}; + +export default function (project: Project, options: ToFsOptions = {}) { const files: Record = {}; const { path, content } = extractConfig(project); files[path] = content; for (const wf of project.workflows) { - const { path, content } = extractWorkflow(project, wf.id); + const { path, content } = extractWorkflow(project, wf.id, options); files[path] = content; for (const s of wf.steps) { @@ -30,7 +35,11 @@ export default function (project: Project) { } // extracts a workflow.json|yaml from a project -export const extractWorkflow = (project: Project, workflowId: string) => { +export const extractWorkflow = ( + project: Project, + workflowId: string, + options: ToFsOptions = {} +) => { const format = project.config.formats.workflow; const workflow = project.getWorkflow(workflowId); @@ -46,6 +55,7 @@ export const extractWorkflow = (project: Project, workflowId: string) => { const path = nodepath.join(root, workflow.id, workflow.id); const wf = { + ...(options.includeSchemaVersion ? { schema_version: '4.0' } : {}), id: workflow.id, name: workflow.name, start: workflow.start, diff --git a/packages/project/src/serialize/to-project.ts b/packages/project/src/serialize/to-project.ts index 1b7d7b221..8192041f8 100644 --- a/packages/project/src/serialize/to-project.ts +++ b/packages/project/src/serialize/to-project.ts @@ -10,7 +10,7 @@ import { jsonToYaml } from '../util/yaml'; import { WithMeta } from '../Workflow'; import { tidyOpenfn } from '../util/omit-nil'; -const SERIALIZE_VERSION = 2; +const SCHEMA_VERSION = '4.0'; type ToProjectOptions = { /** What serialisation version should we write to? defaults to the highest supported by this CLI. For v1, use `Project.serialize('state)` */ @@ -23,14 +23,13 @@ type ToProjectOptions = { export default (project: Project, options: ToProjectOptions = {}) => { // return a compatible json structure const { alias, ...cliWithoutAlias } = project.cli; + const cli = Object.keys(cliWithoutAlias).length ? cliWithoutAlias : undefined; const proj: SerializedProject = omitBy( { id: project.id, name: project.name, - cli: { - ...cliWithoutAlias, - version: SERIALIZE_VERSION, // important! - }, + schema_version: SCHEMA_VERSION, + cli, description: project.description, collections: project.collections, diff --git a/packages/project/src/util/yaml.ts b/packages/project/src/util/yaml.ts index 66a571eb0..510063278 100644 --- a/packages/project/src/util/yaml.ts +++ b/packages/project/src/util/yaml.ts @@ -11,5 +11,7 @@ export function jsonToYaml(json: string | Object) { } const doc = new yaml.Document(json); - return yaml.stringify(doc, null, 2); + return yaml.stringify(doc, null, { + singleQuote: true, + }); } diff --git a/packages/project/test/fixtures/sample-v2-project.ts b/packages/project/test/fixtures/sample-v2-project.ts index 76f8177d1..8c11c46f6 100644 --- a/packages/project/test/fixtures/sample-v2-project.ts +++ b/packages/project/test/fixtures/sample-v2-project.ts @@ -6,8 +6,8 @@ import { SerializedProject } from '../../src/parse/from-project'; export const json: SerializedProject = { id: 'my-project', name: 'My Project', + schema_version: '4.0', description: 'my lovely project', - cli: { version: 2 }, openfn: { uuid: '1234', endpoint: 'https://app.openfn.org' }, options: { allow_support_access: false, env: 'dev', color: 'red' }, sandbox: { @@ -49,15 +49,14 @@ export const json: SerializedProject = { export const yaml = `id: my-project name: My Project -cli: - version: 2 +schema_version: '4.0' description: my lovely project credentials: - uuid: x owner: admin@openfn.org name: My Credential openfn: - uuid: "1234" + uuid: '1234' endpoint: https://app.openfn.org options: allow_support_access: false diff --git a/packages/project/test/parse/from-project.test.ts b/packages/project/test/parse/from-project.test.ts index 912074941..e1ecd5bc5 100644 --- a/packages/project/test/parse/from-project.test.ts +++ b/packages/project/test/parse/from-project.test.ts @@ -79,6 +79,20 @@ test('import from a v1 state as YAML', async (t) => { t.is(proj.workflows.length, 1); }); +test('import a legacy v2 project (cli.version === 2) as JSON', async (t) => { + const legacy = { + id: 'legacy-project', + name: 'Legacy Project', + cli: { version: 2 }, + workflows: [], + }; + const proj = await Project.from('project', legacy, {}); + + t.is(proj.id, 'legacy-project'); + t.is(proj.name, 'Legacy Project'); + t.is(proj.workflows.length, 0); +}); + test('import from a v2 project as JSON', async (t) => { const proj = await Project.from('project', v2.json, { alias: 'main' }); diff --git a/packages/project/test/serialize/to-fs.test.ts b/packages/project/test/serialize/to-fs.test.ts index c4adad5a4..471a4fcc7 100644 --- a/packages/project/test/serialize/to-fs.test.ts +++ b/packages/project/test/serialize/to-fs.test.ts @@ -40,7 +40,7 @@ start: step options: {} steps: - id: step - adaptor: "@openfn/language-common@latest" + adaptor: '@openfn/language-common@latest' expression: ./step.js ` ); @@ -229,6 +229,30 @@ test('extractWorkflow: include trigger enabled state (false)', (t) => { }); }); +test('extractWorkflow: includeSchemaVersion stamps schema_version into workflow', (t) => { + const project = new Project( + { + workflows: [ + { + id: 'my-workflow', + steps: [step], + }, + ], + }, + { + formats: { + workflow: 'json', + }, + } + ); + + const { content } = extractWorkflow(project, 'my-workflow', { + includeSchemaVersion: true, + }); + + t.is(JSON.parse(content).schema_version, '4.0'); +}); + test('extractWorkflow: single simple workflow with custom root', (t) => { const config = { dirs: { diff --git a/packages/project/test/util/yaml.test.ts b/packages/project/test/util/yaml.test.ts index 681cccf5a..9f256ba0a 100644 --- a/packages/project/test/util/yaml.test.ts +++ b/packages/project/test/util/yaml.test.ts @@ -36,8 +36,8 @@ test('jsonToYaml: convert an array of primitives', (t) => { arr: ['1', '2', 3, true], }; const expected = `arr: - - "1" - - "2" + - '1' + - '2' - 3 - true `; From d37a45c06a4b6ebed2171011e63d9d3524f579cd Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Mon, 11 May 2026 17:41:53 +0100 Subject: [PATCH 02/11] version --- integration-tests/cli/CHANGELOG.md | 8 ++++++++ integration-tests/cli/package.json | 2 +- packages/cli/CHANGELOG.md | 8 ++++++++ packages/cli/package.json | 2 +- packages/lightning-mock/CHANGELOG.md | 7 +++++++ packages/lightning-mock/package.json | 2 +- packages/project/CHANGELOG.md | 6 ++++++ packages/project/package.json | 2 +- 8 files changed, 33 insertions(+), 4 deletions(-) diff --git a/integration-tests/cli/CHANGELOG.md b/integration-tests/cli/CHANGELOG.md index 30232317b..a70115438 100644 --- a/integration-tests/cli/CHANGELOG.md +++ b/integration-tests/cli/CHANGELOG.md @@ -1,5 +1,13 @@ # @openfn/integration-tests-cli +## 1.0.22 + +### Patch Changes + +- Updated dependencies + - @openfn/project@0.15.2 + - @openfn/lightning-mock@2.4.17 + ## 1.0.21 ### Patch Changes diff --git a/integration-tests/cli/package.json b/integration-tests/cli/package.json index 1ad6870f8..38b095257 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.21", + "version": "1.0.22", "description": "CLI integration tests", "author": "Open Function Group ", "license": "ISC", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 9f5c6bf5d..0843663ae 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,13 @@ # @openfn/cli +## 1.35.3 + +### Patch Changes + +- Include version number in serialized project files +- Updated dependencies + - @openfn/project@0.15.2 + ## 1.35.2 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 1c5b0d562..183a515f3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openfn/cli", - "version": "1.35.2", + "version": "1.35.3", "description": "CLI devtools for the OpenFn toolchain", "engines": { "node": ">=18", diff --git a/packages/lightning-mock/CHANGELOG.md b/packages/lightning-mock/CHANGELOG.md index c7010cc9e..7bcc221d5 100644 --- a/packages/lightning-mock/CHANGELOG.md +++ b/packages/lightning-mock/CHANGELOG.md @@ -1,5 +1,12 @@ # @openfn/lightning-mock +## 2.4.17 + +### Patch Changes + +- Updated dependencies + - @openfn/project@0.15.2 + ## 2.4.16 ### Patch Changes diff --git a/packages/lightning-mock/package.json b/packages/lightning-mock/package.json index b944e47ab..35c8b7f5e 100644 --- a/packages/lightning-mock/package.json +++ b/packages/lightning-mock/package.json @@ -1,6 +1,6 @@ { "name": "@openfn/lightning-mock", - "version": "2.4.16", + "version": "2.4.17", "private": true, "description": "A mock Lightning server", "main": "dist/index.js", diff --git a/packages/project/CHANGELOG.md b/packages/project/CHANGELOG.md index a7d7dc036..8aed302d3 100644 --- a/packages/project/CHANGELOG.md +++ b/packages/project/CHANGELOG.md @@ -1,5 +1,11 @@ # @openfn/project +## 0.15.2 + +### Patch Changes + +- Include version number in serialized project files + ## 0.15.1 ### Patch Changes diff --git a/packages/project/package.json b/packages/project/package.json index c46f42b9a..4ce92a645 100644 --- a/packages/project/package.json +++ b/packages/project/package.json @@ -1,6 +1,6 @@ { "name": "@openfn/project", - "version": "0.15.1", + "version": "0.15.2", "description": "Read, serialize, replicate and sync OpenFn projects", "scripts": { "test": "pnpm ava", From c5c7f5eba492e0e25bc24dfabb2847cc409fc3b9 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Mon, 11 May 2026 17:46:58 +0100 Subject: [PATCH 03/11] tidy types a bit --- packages/project/src/parse/from-project.ts | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/project/src/parse/from-project.ts b/packages/project/src/parse/from-project.ts index c7f4e08b0..0f2e8f765 100644 --- a/packages/project/src/parse/from-project.ts +++ b/packages/project/src/parse/from-project.ts @@ -4,26 +4,15 @@ import Project from '../Project'; import ensureJson from '../util/ensure-json'; import { Provisioner } from '@openfn/lexicon/lightning'; import fromAppState, { fromAppStateConfig } from './from-app-state'; -import { WithMeta } from '../Workflow'; // Load a project from any JSON or yaml representation // This is backwards-compatible with v1 state.json files // But is really designed for v2 project.yaml files // TODO move these types to a common types.ts, or maybe Project.ts -export type SerializedProject = Omit, 'workflows'> & { - version?: number; - workflows: SerializedWorkflow[]; -}; - -export type SerializedWorkflow = { - id: string; - name: string; +export type SerializedProject = l.ProjectState; - steps: WithMeta>; - - openfn?: l.ProjectMeta; -}; +export type SerializedWorkflow = l.WorkflowState; export default ( data: l.ProjectState | SerializedProject | string, From 26827ab15a12d8a330033bb952c3b06335b714d1 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Mon, 11 May 2026 17:50:36 +0100 Subject: [PATCH 04/11] tweak serialized structure --- packages/project/src/serialize/to-fs.ts | 46 ++++++++++--------- packages/project/test/serialize/to-fs.test.ts | 3 -- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/project/src/serialize/to-fs.ts b/packages/project/src/serialize/to-fs.ts index aa35cfe2e..8b84b0687 100644 --- a/packages/project/src/serialize/to-fs.ts +++ b/packages/project/src/serialize/to-fs.ts @@ -54,28 +54,32 @@ export const extractWorkflow = ( const path = nodepath.join(root, workflow.id, workflow.id); - const wf = { - ...(options.includeSchemaVersion ? { schema_version: '4.0' } : {}), - id: workflow.id, - name: workflow.name, - start: workflow.start, - // Note: if no options are defined, options will serialize to an empty object - // Not crazy about this - maybe we should do something better? Or do we like the consistency? - options: workflow.options, - steps: workflow.steps.map((step) => { - const { openfn, expression, next, ...mapped } = step; - if (expression) { - (mapped as any).expression = `./${step.id}.js`; - } - if (next && typeof next === 'object') { - (mapped as any).next = {}; - for (const id in next) { - (mapped as any).next[id] = omit(next[id] as any, ['openfn']); + const wf = Object.assign( + { + id: workflow.id, + name: workflow.name, + start: workflow.start, + // Note: if no options are defined, options will serialize to an empty object + // Not crazy about this - maybe we should do something better? Or do we like the consistency? + }, + Object.keys(workflow.options).length && { options: workflow.options }, + options.includeSchemaVersion && { schema_version: '4.0' }, + { + steps: workflow.steps.map((step) => { + const { openfn, expression, next, ...mapped } = step; + if (expression) { + (mapped as any).expression = `./${step.id}.js`; } - } - return mapped; - }), - }; + if (next && typeof next === 'object') { + (mapped as any).next = {}; + for (const id in next) { + (mapped as any).next[id] = omit(next[id] as any, ['openfn']); + } + } + return mapped; + }), + } + ); return handleOutput(wf, path, format!); }; diff --git a/packages/project/test/serialize/to-fs.test.ts b/packages/project/test/serialize/to-fs.test.ts index 471a4fcc7..c24c1ad43 100644 --- a/packages/project/test/serialize/to-fs.test.ts +++ b/packages/project/test/serialize/to-fs.test.ts @@ -37,7 +37,6 @@ test('extractWorkflow: single simple workflow (yaml by default)', (t) => { `id: my-workflow name: My Workflow start: step -options: {} steps: - id: step adaptor: '@openfn/language-common@latest' @@ -91,7 +90,6 @@ test('extractWorkflow: single simple workflow with an edge', (t) => { t.deepEqual(JSON.parse(content), { id: 'my-workflow', name: 'My Workflow', - options: {}, steps: [ { id: 'step1', @@ -145,7 +143,6 @@ test('extractWorkflow: single simple workflow with random edge property', (t) => t.deepEqual(JSON.parse(content), { id: 'my-workflow', name: 'My Workflow', - options: {}, steps: [ { id: 'step', From 3c3c64a0eaf4dec9629f73b1f92e0c8c88de0240 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Mon, 11 May 2026 17:52:22 +0100 Subject: [PATCH 05/11] remove comment --- packages/project/src/serialize/to-fs.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/project/src/serialize/to-fs.ts b/packages/project/src/serialize/to-fs.ts index 8b84b0687..220111b05 100644 --- a/packages/project/src/serialize/to-fs.ts +++ b/packages/project/src/serialize/to-fs.ts @@ -59,8 +59,6 @@ export const extractWorkflow = ( id: workflow.id, name: workflow.name, start: workflow.start, - // Note: if no options are defined, options will serialize to an empty object - // Not crazy about this - maybe we should do something better? Or do we like the consistency? }, Object.keys(workflow.options).length && { options: workflow.options }, options.includeSchemaVersion && { schema_version: '4.0' }, From 0e259711c2cb83d597f099f4cfe89fdc2ece15b7 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Mon, 11 May 2026 18:14:06 +0100 Subject: [PATCH 06/11] test fixes --- packages/cli/test/projects/checkout.test.ts | 1 - packages/cli/test/projects/fixtures.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/cli/test/projects/checkout.test.ts b/packages/cli/test/projects/checkout.test.ts index 2208de789..4479180b1 100644 --- a/packages/cli/test/projects/checkout.test.ts +++ b/packages/cli/test/projects/checkout.test.ts @@ -476,7 +476,6 @@ workspace: t.deepEqual(JSON.parse(wf), { id: 'simple-workflow', name: 'Simple Workflow', - options: {}, start: 'webhook', steps: [ { diff --git a/packages/cli/test/projects/fixtures.ts b/packages/cli/test/projects/fixtures.ts index b6f425486..b67073a37 100644 --- a/packages/cli/test/projects/fixtures.ts +++ b/packages/cli/test/projects/fixtures.ts @@ -61,7 +61,7 @@ export const myProject_v1: Provisioner.Project = { export const myProject_yaml = `id: my-project name: My Project -schema_version: "4.0" +schema_version: '4.0' description: my lovely project collections: [] credentials: [] @@ -80,7 +80,7 @@ workflows: - id: transform-data name: Transform data expression: fn() - adaptor: "@openfn/language-common@latest" + adaptor: '@openfn/language-common@latest' openfn: uuid: 66add020-e6eb-4eec-836b-20008afca816 - id: webhook From dacc569db67331f02fb9dc2f10aae1c2a74d81f4 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Tue, 12 May 2026 16:11:17 +0100 Subject: [PATCH 07/11] dont' validate CLI unless flag is passed --- .tool-versions | 2 +- packages/cli/src/execute/command.ts | 22 ++++++++++++---------- packages/cli/src/execute/handler.ts | 4 +++- packages/cli/src/options.ts | 10 ++++++++++ 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/.tool-versions b/.tool-versions index 01c1e7d0d..67a67317d 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -nodejs 24.14.0 +nodejs 24.15.0 diff --git a/packages/cli/src/execute/command.ts b/packages/cli/src/execute/command.ts index 808750ed1..73073e395 100644 --- a/packages/cli/src/execute/command.ts +++ b/packages/cli/src/execute/command.ts @@ -8,39 +8,40 @@ import type { Opts } from '../options'; export type ExecuteOptions = { workspace?: string } & Required< Pick< Opts, - | 'apiKey' | 'adaptors' + | 'apiKey' | 'autoinstall' | 'baseDir' | 'cacheSteps' + | 'collectionsEndpoint' + | 'collectionsVersion' | 'command' | 'compile' | 'credentials' - | 'collectionsEndpoint' - | 'collectionsVersion' - | 'expandAdaptors' | 'end' - | 'immutable' - | 'ignoreImports' + | 'expandAdaptors' | 'expressionPath' + | 'globals' + | 'ignoreImports' + | 'immutable' | 'log' | 'logJson' + | 'only' | 'outputPath' | 'outputStdout' - | 'only' | 'path' | 'repoDir' + | 'sanitize' | 'skipAdaptorValidation' | 'start' | 'statePath' | 'stateStdin' - | 'sanitize' | 'timeout' | 'trace' | 'useAdaptorsMonorepo' - | 'workflowPath' + | 'validate' | 'workflowName' - | 'globals' + | 'workflowPath' > > & Pick; @@ -77,6 +78,7 @@ const options = [ o.timeout, o.trace, o.useAdaptorsMonorepo, + o.validate, po.workspace, ]; diff --git a/packages/cli/src/execute/handler.ts b/packages/cli/src/execute/handler.ts index aba2893f5..7c926102c 100644 --- a/packages/cli/src/execute/handler.ts +++ b/packages/cli/src/execute/handler.ts @@ -87,7 +87,9 @@ const executeHandler = async (options: ExecuteOptions, logger: Logger) => { await validateAdaptors(options, logger); let plan = await loadPlan(options, logger); - validatePlan(plan, logger); + if (options.validate) { + validatePlan(plan, logger); + } await loadAndApplyCredentialMap(plan, options, logger); if (options.cacheSteps) { await clearCache(plan, options, logger); diff --git a/packages/cli/src/options.ts b/packages/cli/src/options.ts index ae8bd3f82..a97df63b1 100644 --- a/packages/cli/src/options.ts +++ b/packages/cli/src/options.ts @@ -70,6 +70,7 @@ export type Opts = { useAdaptorsMonorepo?: boolean; workflow: string; workflowName?: string; + validate?: boolean; // deprecated workflowPath?: string; @@ -613,6 +614,15 @@ export const sanitize: CLIOption = { }, }; +export const validate: CLIOption = { + name: 'validate', + yargs: { + boolean: true, + default: false, + description: 'Validate workflows before executing', + }, +}; + export const workflow: CLIOption = { name: 'workflow', yargs: { From 8364acf5f3732e817d4c349efc41214c4e2ca08a Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Tue, 12 May 2026 16:11:53 +0100 Subject: [PATCH 08/11] update changeset --- packages/cli/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 0843663ae..cfb69eaaf 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -5,6 +5,7 @@ ### Patch Changes - Include version number in serialized project files +- Don't validate workflows on execution unless `--validate` is explicitly passed - Updated dependencies - @openfn/project@0.15.2 From 8ed949a79c12063bb70a69fb425eda9a82738627 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Tue, 12 May 2026 16:19:45 +0100 Subject: [PATCH 09/11] add override to force-disable v2 sync --- packages/cli/src/deploy/handler.ts | 4 ++-- packages/cli/src/pull/handler.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/deploy/handler.ts b/packages/cli/src/deploy/handler.ts index cd27c3349..0a5bc8fe8 100644 --- a/packages/cli/src/deploy/handler.ts +++ b/packages/cli/src/deploy/handler.ts @@ -40,7 +40,7 @@ async function deployHandler( options.workspace || process.cwd(), 'openfn.yaml' ); - if (await fileExists(v2ConfigPath)) { + if (!process.env.PREFER_LEGACY_DEPLOY && (await fileExists(v2ConfigPath))) { // override endpoint with one from openfn.yaml const config = yamlToJson(await fs.readFile(v2ConfigPath, 'utf-8')); if (config?.project?.endpoint) { @@ -48,7 +48,7 @@ async function deployHandler( } logger.always( - 'Detected openfn.yaml file - switching to v2 deploy (openfn project deploy)' + 'Detected openfn.yaml file - switching to v2 deploy (openfn project deploy). Set PREFER_LEGACY_DEPLOY to disable this.' ); return beta.handler( { diff --git a/packages/cli/src/pull/handler.ts b/packages/cli/src/pull/handler.ts index 15220d724..73e1e19ab 100644 --- a/packages/cli/src/pull/handler.ts +++ b/packages/cli/src/pull/handler.ts @@ -26,7 +26,7 @@ async function pullHandler(options: PullOptions, logger: Logger) { options.workspace || process.cwd(), 'openfn.yaml' ); - if (await fileExists(v2ConfigPath)) { + if (!process.env.PREFER_LEGACY_DEPLOY && (await fileExists(v2ConfigPath))) { // override endpoint with one from openfn.yaml const config = yamlToJson(await fs.readFile(v2ConfigPath, 'utf-8')); if (config?.project?.endpoint) { @@ -34,7 +34,7 @@ async function pullHandler(options: PullOptions, logger: Logger) { } logger.always( - 'Detected openfn.yaml file - switching to v2 pull (openfn project pull)' + 'Detected openfn.yaml file - switching to v2 pull (openfn project pull). Set PREFER_LEGACY_DEPLOY to disable this.' ); return beta( { From 925a714b0bcc0386f210edc0817d02ac3fcef92a Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Tue, 12 May 2026 16:21:11 +0100 Subject: [PATCH 10/11] light refactor --- packages/cli/CHANGELOG.md | 1 + packages/cli/src/deploy/handler.ts | 4 ++-- packages/cli/src/pull/handler.ts | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index cfb69eaaf..a82191824 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -6,6 +6,7 @@ - Include version number in serialized project files - Don't validate workflows on execution unless `--validate` is explicitly passed +- Allow setting `PREFER_LEGACY_SYNC=1` to strictly disable v2 sync when calling the v1 API - Updated dependencies - @openfn/project@0.15.2 diff --git a/packages/cli/src/deploy/handler.ts b/packages/cli/src/deploy/handler.ts index 0a5bc8fe8..ea4d5a344 100644 --- a/packages/cli/src/deploy/handler.ts +++ b/packages/cli/src/deploy/handler.ts @@ -40,7 +40,7 @@ async function deployHandler( options.workspace || process.cwd(), 'openfn.yaml' ); - if (!process.env.PREFER_LEGACY_DEPLOY && (await fileExists(v2ConfigPath))) { + if (!process.env.PREFER_LEGACY_SYNC && (await fileExists(v2ConfigPath))) { // override endpoint with one from openfn.yaml const config = yamlToJson(await fs.readFile(v2ConfigPath, 'utf-8')); if (config?.project?.endpoint) { @@ -48,7 +48,7 @@ async function deployHandler( } logger.always( - 'Detected openfn.yaml file - switching to v2 deploy (openfn project deploy). Set PREFER_LEGACY_DEPLOY to disable this.' + 'Detected openfn.yaml file - switching to v2 deploy (openfn project deploy). Set PREFER_LEGACY_SYNC to disable this.' ); return beta.handler( { diff --git a/packages/cli/src/pull/handler.ts b/packages/cli/src/pull/handler.ts index 73e1e19ab..2fc6dd503 100644 --- a/packages/cli/src/pull/handler.ts +++ b/packages/cli/src/pull/handler.ts @@ -26,7 +26,7 @@ async function pullHandler(options: PullOptions, logger: Logger) { options.workspace || process.cwd(), 'openfn.yaml' ); - if (!process.env.PREFER_LEGACY_DEPLOY && (await fileExists(v2ConfigPath))) { + if (!process.env.PREFER_LEGACY_SYNC && (await fileExists(v2ConfigPath))) { // override endpoint with one from openfn.yaml const config = yamlToJson(await fs.readFile(v2ConfigPath, 'utf-8')); if (config?.project?.endpoint) { @@ -34,7 +34,7 @@ async function pullHandler(options: PullOptions, logger: Logger) { } logger.always( - 'Detected openfn.yaml file - switching to v2 pull (openfn project pull). Set PREFER_LEGACY_DEPLOY to disable this.' + 'Detected openfn.yaml file - switching to v2 pull (openfn project pull). Set PREFER_LEGACY_SYNC to disable this.' ); return beta( { From 9fa61f8c4a5e3c36db7a7ca6f58e64c345a96fa0 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Tue, 12 May 2026 16:57:10 +0100 Subject: [PATCH 11/11] update yaml in integration tests --- integration-tests/cli/test/project-v1.test.ts | 3 +-- integration-tests/cli/test/project-v2.test.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/integration-tests/cli/test/project-v1.test.ts b/integration-tests/cli/test/project-v1.test.ts index 450f80bb7..295855b2c 100644 --- a/integration-tests/cli/test/project-v1.test.ts +++ b/integration-tests/cli/test/project-v1.test.ts @@ -142,7 +142,6 @@ test.serial('Checkout a project', async (t) => { `id: my-workflow name: my workflow start: webhook -options: {} steps: - id: webhook type: webhook @@ -153,7 +152,7 @@ steps: condition: always - id: transform-data name: Transform data - adaptor: "@openfn/language-common@latest" + adaptor: '@openfn/language-common@latest' expression: ./transform-data.js ` ); diff --git a/integration-tests/cli/test/project-v2.test.ts b/integration-tests/cli/test/project-v2.test.ts index f3b7cab7b..a41f8791b 100644 --- a/integration-tests/cli/test/project-v2.test.ts +++ b/integration-tests/cli/test/project-v2.test.ts @@ -151,7 +151,6 @@ test.serial('Checkout a project', async (t) => { `id: hello-workflow name: Hello Workflow start: trigger -options: {} steps: - id: trigger type: webhook @@ -161,7 +160,7 @@ steps: condition: true - id: transform-data name: Transform data - adaptor: "@openfn/language-dhis2@8.0.4" + adaptor: '@openfn/language-dhis2@8.0.4' expression: ./transform-data.js ` );