From 48827a70e542de8aed58b32e0adfc04b157a0a9a Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 21 May 2026 17:06:33 +0100 Subject: [PATCH 1/7] add option to not generate credentials.yaml --- packages/cli/src/projects/checkout.ts | 16 ++- packages/cli/src/projects/options.ts | 11 +++ packages/cli/test/projects/checkout.test.ts | 102 +++++++++++++++++++- 3 files changed, 124 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/projects/checkout.ts b/packages/cli/src/projects/checkout.ts index c81351a85..5969fefad 100644 --- a/packages/cli/src/projects/checkout.ts +++ b/packages/cli/src/projects/checkout.ts @@ -19,10 +19,16 @@ import { createProjectCredentials } from './create-credentials'; export type CheckoutOptions = Pick< Opts, - 'command' | 'project' | 'workspace' | 'log' | 'clean' | 'force' + | 'command' + | 'project' + | 'workspace' + | 'log' + | 'clean' + | 'force' + | 'createCredentials' >; -const options = [o.log, po.workspace, po.clean, o.force]; +const options = [o.log, po.workspace, po.clean, o.force, po.creds]; const command: yargs.CommandModule = { command: 'checkout ', @@ -125,8 +131,10 @@ export const handler = async (options: CheckoutOptions, logger?: Logger) => { logger?.warn('WARNING! No content for file', f); } } - - createProjectCredentials(workspacePath, switchProject, logger); + console.log({ options }); + if (options.createCredentials) { + createProjectCredentials(workspacePath, switchProject, logger); + } logger?.success(`Expanded project to ${workspacePath}`); }; diff --git a/packages/cli/src/projects/options.ts b/packages/cli/src/projects/options.ts index d518fa854..ba5ecbfd4 100644 --- a/packages/cli/src/projects/options.ts +++ b/packages/cli/src/projects/options.ts @@ -11,6 +11,7 @@ export type Opts = BaseOpts & { project?: string; format?: 'yaml' | 'json' | 'state'; clean?: boolean; + createCredentials?: boolean; }; // project specific options @@ -39,6 +40,16 @@ export const clean: CLIOption = { }, }; +export const creds: CLIOption = { + name: 'create-credentials', + yargs: { + boolean: true, + default: true, + description: + 'Create a credentials.yaml file and intialize with empty values', + }, +}; + export const dryRun: CLIOption = { name: 'dryRun', yargs: { diff --git a/packages/cli/test/projects/checkout.test.ts b/packages/cli/test/projects/checkout.test.ts index 4479180b1..f4b1b5c71 100644 --- a/packages/cli/test/projects/checkout.test.ts +++ b/packages/cli/test/projects/checkout.test.ts @@ -561,7 +561,7 @@ test.serial( triggers: [], edges: [], }, - ], + ], }), }); @@ -580,6 +580,106 @@ test.serial( } ); +test.serial('checkout: creates credentials.yaml', async (t) => { + mock({ + '/ws2/workflows': {}, + '/ws2/openfn.yaml': jsonToYaml({ + project: { id: 'main-project' }, + }), + '/ws2/.projects/main-project@server.yaml': jsonToYaml({ + id: '', + name: 'Main Project', + project_credentials: [ + { + id: 'cred-uuid', + name: 'my-credential', + owner: 'alice', + }, + ], + workflows: [ + { + name: 'My Workflow', + jobs: [ + { + name: 'Run Job', + body: 'fn(s => s)', + adaptor: '@openfn/language-http@latest', + project_credential_id: 'cred-uuid', + }, + ], + triggers: [], + edges: [], + }, + ], + }), + }); + + t.false(fs.existsSync('/ws2/credentials.yaml')); + + await checkoutHandler( + { + command: 'project-checkout', + project: 'main-project', + workspace: '/ws2', + createCredentials: false, + }, + logger + ); + + t.true(fs.existsSync('/ws2/credentials.yaml')); + + const creds = yamlToJson(fs.readFileSync('/ws2/credentials.yaml', 'utf8')); + t.deepEqual(creds, { 'alice|my-credential': {} }); +}); + +test.serial('checkout: do not create credentials.yaml', async (t) => { + mock({ + '/ws2/workflows': {}, + '/ws2/openfn.yaml': jsonToYaml({ + project: { id: 'main-project' }, + }), + '/ws2/.projects/main-project@server.yaml': jsonToYaml({ + id: '', + name: 'Main Project', + project_credentials: [ + { + id: 'cred-uuid', + name: 'my-credential', + owner: 'alice', + }, + ], + workflows: [ + { + name: 'My Workflow', + jobs: [ + { + name: 'Run Job', + body: 'fn(s => s)', + adaptor: '@openfn/language-http@latest', + project_credential_id: 'cred-uuid', + }, + ], + triggers: [], + edges: [], + }, + ], + }), + }); + + t.false(fs.existsSync('/ws2/credentials.yaml')); + + await checkoutHandler( + { + command: 'project-checkout', + project: 'main-project', + workspace: '/ws2', + createCredentials: false, + }, + logger + ); + + t.false(fs.existsSync('/ws2/credentials.yaml')); +}); test.serial( 'checkout: removes workflow directory when workflow is deleted on server', async (t) => { From c2a2f2abd689b2b1b30a954c34a95ed47670e46d Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 21 May 2026 17:45:33 +0100 Subject: [PATCH 2/7] update tests --- packages/cli/src/projects/checkout.ts | 1 - packages/cli/src/projects/pull.ts | 2 + packages/cli/src/pull/handler.ts | 3 +- packages/cli/test/pull/handler.test.ts | 54 +++++++++++++++++++++++--- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/projects/checkout.ts b/packages/cli/src/projects/checkout.ts index 5969fefad..883feb128 100644 --- a/packages/cli/src/projects/checkout.ts +++ b/packages/cli/src/projects/checkout.ts @@ -131,7 +131,6 @@ export const handler = async (options: CheckoutOptions, logger?: Logger) => { logger?.warn('WARNING! No content for file', f); } } - console.log({ options }); if (options.createCredentials) { createProjectCredentials(workspacePath, switchProject, logger); } diff --git a/packages/cli/src/projects/pull.ts b/packages/cli/src/projects/pull.ts index f745dd7cf..5de9bf09c 100644 --- a/packages/cli/src/projects/pull.ts +++ b/packages/cli/src/projects/pull.ts @@ -24,6 +24,7 @@ export type PullOptions = Pick< | 'confirm' | 'snapshots' | 'force' + | 'createCredentials' >; const options = [ @@ -32,6 +33,7 @@ const options = [ o2.alias, o2.env, o2.workspace, + o2.creds, // general options o.apiKey, diff --git a/packages/cli/src/pull/handler.ts b/packages/cli/src/pull/handler.ts index 2fc6dd503..3a65ab4b0 100644 --- a/packages/cli/src/pull/handler.ts +++ b/packages/cli/src/pull/handler.ts @@ -28,7 +28,7 @@ async function pullHandler(options: PullOptions, logger: Logger) { ); 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')); + const config = yamlToJson(await fs.readFile(v2ConfigPath, 'utf-8')) ?? {}; if (config?.project?.endpoint) { config.endpoint = config.project.endpoint; } @@ -42,6 +42,7 @@ async function pullHandler(options: PullOptions, logger: Logger) { project: options.projectId, force: true, endpoint: config.endpoint, + createCredentials: false, }, logger ); diff --git a/packages/cli/test/pull/handler.test.ts b/packages/cli/test/pull/handler.test.ts index 3ef838dff..472b62371 100644 --- a/packages/cli/test/pull/handler.test.ts +++ b/packages/cli/test/pull/handler.test.ts @@ -1,8 +1,15 @@ import test from 'ava'; import mockfs from 'mock-fs'; +import fs from 'node:fs'; +import { MockAgent, setGlobalDispatcher } from 'undici'; import { createMockLogger } from '@openfn/logger'; + import pullHandler from '../../src/pull/handler'; import { PullOptions } from '../../src/pull/command'; +import { myProject_v1 } from '../projects/fixtures'; + +const ENDPOINT = 'https://app.openfn.org'; +const PROJECT_UUID = 'e16c5f09-f0cb-4ba7-a4c2-73fcb2f29d00'; test.beforeEach(() => { mockfs.restore(); @@ -12,14 +19,31 @@ test.afterEach(() => { mockfs.restore(); }); +let mockAgent = new MockAgent(); +mockAgent.disableNetConnect(); +setGlobalDispatcher(mockAgent); + +test.before(() => { + const mockPool = mockAgent.get(ENDPOINT); + mockPool + .intercept({ + path: `/api/provision/${PROJECT_UUID}?`, + method: 'GET', + }) + .reply(200, { + data: myProject_v1, + }) + .persist(); +}); + const options: PullOptions = { beta: false, command: 'pull', - projectPath: './project.yaml', - configPath: './config.json', - projectId: 'abc-123', + configPath: '/tmp/config.json', + projectId: PROJECT_UUID, confirm: false, snapshots: [], + workspace: '/tmp', // needed in tests to drive other paths }; test.serial( @@ -27,12 +51,30 @@ test.serial( async (t) => { const logger = createMockLogger('', { level: 'debug' }); mockfs({ - ['./config.json']: `{"apiKey": "123"}`, - ['./openfn.yaml']: '', + ['/tmp/config.json']: `{"apiKey": "123", "endpoint": "${ENDPOINT}"}`, + ['/tmp/openfn.yaml']: ` +project: + endpoint: ${ENDPOINT}`, }); - await t.throwsAsync(() => pullHandler(options, logger)); + await pullHandler(options, logger); + + t.true(fs.existsSync('/tmp/.projects/main@app.openfn.org.yaml')); t.truthy(logger._find('always', /Detected openfn.yaml file/i)); } ); + +test.serial('does not create credentials.yaml when redirecting', async (t) => { + const logger = createMockLogger('', { level: 'debug' }); + mockfs({ + ['/tmp/config.json']: `{"apiKey": "123", "endpoint": "${ENDPOINT}"}`, + ['/tmp/openfn.yaml']: ` +project: + endpoint: ${ENDPOINT}`, + }); + + await pullHandler(options, logger); + + t.false(fs.existsSync('/tmp/credentials.yaml')); +}); From 68463d8ea0913123cf17e216160ec97974441cfb Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 21 May 2026 17:53:28 +0100 Subject: [PATCH 3/7] changeset --- .changeset/empty-snakes-return.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/empty-snakes-return.md diff --git a/.changeset/empty-snakes-return.md b/.changeset/empty-snakes-return.md new file mode 100644 index 000000000..dfe48852b --- /dev/null +++ b/.changeset/empty-snakes-return.md @@ -0,0 +1,5 @@ +--- +'@openfn/cli': patch +--- + +When running legacy deploy with an openfn.yaml file (ie, github sync), do not generate credentials.yaml by default" From 4b012390796801022b8c84242108787065a22187 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 21 May 2026 17:57:59 +0100 Subject: [PATCH 4/7] fix test --- packages/cli/test/projects/checkout.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/test/projects/checkout.test.ts b/packages/cli/test/projects/checkout.test.ts index f4b1b5c71..3a110c5bf 100644 --- a/packages/cli/test/projects/checkout.test.ts +++ b/packages/cli/test/projects/checkout.test.ts @@ -621,7 +621,7 @@ test.serial('checkout: creates credentials.yaml', async (t) => { command: 'project-checkout', project: 'main-project', workspace: '/ws2', - createCredentials: false, + createCredentials: true, }, logger ); From eb0b656bd6a7c194e0b3ea6ea3566bbde0c25faa Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 21 May 2026 17:59:33 +0100 Subject: [PATCH 5/7] use api key from config if set --- packages/cli/src/pull/handler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/pull/handler.ts b/packages/cli/src/pull/handler.ts index 3a65ab4b0..cc51c7cc4 100644 --- a/packages/cli/src/pull/handler.ts +++ b/packages/cli/src/pull/handler.ts @@ -42,6 +42,7 @@ async function pullHandler(options: PullOptions, logger: Logger) { project: options.projectId, force: true, endpoint: config.endpoint, + apiKey: config.apiKey, createCredentials: false, }, logger From 1ea02b991b8e0f1efc0bee53be31a401f51bd97a Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Fri, 22 May 2026 11:40:36 +0100 Subject: [PATCH 6/7] update tests --- packages/cli/src/deploy/handler.ts | 9 +++++---- packages/cli/src/pull/handler.ts | 11 ++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/deploy/handler.ts b/packages/cli/src/deploy/handler.ts index ea4d5a344..63bfaae66 100644 --- a/packages/cli/src/deploy/handler.ts +++ b/packages/cli/src/deploy/handler.ts @@ -41,10 +41,10 @@ async function deployHandler( 'openfn.yaml' ); 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) { - config.endpoint = config.project.endpoint; + // default endpoint to one from openfn.yaml + const v2config = yamlToJson(await fs.readFile(v2ConfigPath, 'utf-8')); + if (!config.endpoint && v2config?.project?.endpoint) { + config.endpoint = v2config.project.endpoint; } logger.always( @@ -55,6 +55,7 @@ async function deployHandler( ...options, force: true, endpoint: config.endpoint, + apiKey: config.apiKey ?? undefined, }, logger ); diff --git a/packages/cli/src/pull/handler.ts b/packages/cli/src/pull/handler.ts index cc51c7cc4..dadfc04a1 100644 --- a/packages/cli/src/pull/handler.ts +++ b/packages/cli/src/pull/handler.ts @@ -27,10 +27,10 @@ async function pullHandler(options: PullOptions, logger: Logger) { 'openfn.yaml' ); 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) { - config.endpoint = config.project.endpoint; + // default endpoint to one from openfn.yaml + const v2config = yamlToJson(await fs.readFile(v2ConfigPath, 'utf-8')); + if (!config.endpoint && v2config?.project?.endpoint) { + config.endpoint = v2config.project.endpoint; } logger.always( @@ -42,12 +42,13 @@ async function pullHandler(options: PullOptions, logger: Logger) { project: options.projectId, force: true, endpoint: config.endpoint, - apiKey: config.apiKey, + apiKey: config.apiKey ?? undefined, createCredentials: false, }, logger ); } + if (process.env['OPENFN_API_KEY']) { logger.info('Using OPENFN_API_KEY environment variable'); config.apiKey = process.env['OPENFN_API_KEY']; From 5635e16f15f3435c0387b3fca677a202114fa68b Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Fri, 22 May 2026 12:11:12 +0100 Subject: [PATCH 7/7] version: cli@1.36.1 --- .changeset/empty-snakes-return.md | 5 ----- packages/cli/CHANGELOG.md | 6 ++++++ packages/cli/package.json | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 .changeset/empty-snakes-return.md diff --git a/.changeset/empty-snakes-return.md b/.changeset/empty-snakes-return.md deleted file mode 100644 index dfe48852b..000000000 --- a/.changeset/empty-snakes-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@openfn/cli': patch ---- - -When running legacy deploy with an openfn.yaml file (ie, github sync), do not generate credentials.yaml by default" diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 4dfe82d45..b6f77d9e6 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,11 @@ # @openfn/cli +## 1.36.1 + +### Patch Changes + +- 68463d8: When running legacy deploy with an openfn.yaml file (ie, github sync), do not generate credentials.yaml by default" + ## 1.36.0 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 119b43651..00007d2db 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openfn/cli", - "version": "1.36.0", + "version": "1.36.1", "description": "CLI devtools for the OpenFn toolchain", "engines": { "node": ">=18",