From 144835016944ffd32c08a6ed1a026f844f74751f Mon Sep 17 00:00:00 2001 From: Charles Lavery Date: Sun, 25 Jan 2026 16:02:43 -0500 Subject: [PATCH 1/2] fix: make OAuth credentials conditional in code commands OAuth is now only required for code deploy/watch when actually needed: - No code version specified (auto-discovery via OCAPI) - --reload flag is set (reload via OCAPI) When using basic auth with an explicit --code-version, OAuth is not required. Error messages now explain why OAuth is needed and link to configuration docs. Closes #35 --- packages/b2c-cli/src/commands/code/deploy.ts | 24 ++++++++++++++++++- packages/b2c-cli/src/commands/code/watch.ts | 11 ++++++++- .../b2c-cli/test/commands/code/deploy.test.ts | 4 ++-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/b2c-cli/src/commands/code/deploy.ts b/packages/b2c-cli/src/commands/code/deploy.ts index 50fb992e..bb21d54f 100644 --- a/packages/b2c-cli/src/commands/code/deploy.ts +++ b/packages/b2c-cli/src/commands/code/deploy.ts @@ -56,11 +56,33 @@ export default class CodeDeploy extends CartridgeCommand { async run(): Promise { this.requireWebDavCredentials(); - this.requireOAuthCredentials(); const hostname = this.resolvedConfig.values.hostname!; let version = this.resolvedConfig.values.codeVersion; + // OAuth is only required if: + // 1. No code version specified (need to auto-discover via OCAPI) + // 2. --reload flag is set (need to call OCAPI to reload) + const needsOAuth = !version || this.flags.reload; + if (needsOAuth && !this.hasOAuthCredentials()) { + const reason = version + ? t( + 'commands.code.deploy.oauthRequiredForReload', + 'The --reload flag requires OAuth credentials to reload the code version via OCAPI.', + ) + : t( + 'commands.code.deploy.oauthRequiredForDiscovery', + 'No code version specified. OAuth credentials are required to auto-discover the active code version.', + ); + this.error( + t( + 'commands.code.deploy.oauthRequired', + '{{reason}}\n\nProvide --code-version to use basic auth only, or configure OAuth credentials.\nSee: https://salesforcecommercecloud.github.io/b2c-developer-tooling/guide/configuration.html', + {reason}, + ), + ); + } + // If no code version specified, discover the active one if (!version) { this.warn( diff --git a/packages/b2c-cli/src/commands/code/watch.ts b/packages/b2c-cli/src/commands/code/watch.ts index 7ee4f8be..2c154059 100644 --- a/packages/b2c-cli/src/commands/code/watch.ts +++ b/packages/b2c-cli/src/commands/code/watch.ts @@ -33,11 +33,20 @@ export default class CodeWatch extends CartridgeCommand { async run(): Promise { this.requireWebDavCredentials(); - this.requireOAuthCredentials(); const hostname = this.resolvedConfig.values.hostname!; const version = this.resolvedConfig.values.codeVersion; + // OAuth is only required if no code version specified (need to auto-discover via OCAPI) + if (!version && !this.hasOAuthCredentials()) { + this.error( + t( + 'commands.code.watch.oauthRequired', + 'No code version specified. OAuth credentials are required to auto-discover the active code version.\n\nProvide --code-version to use basic auth only, or configure OAuth credentials.\nSee: https://salesforcecommercecloud.github.io/b2c-developer-tooling/guide/configuration.html', + ), + ); + } + this.log(t('commands.code.watch.starting', 'Starting watcher for {{path}}', {path: this.cartridgePath})); this.log(t('commands.code.watch.target', 'Target: {{hostname}}', {hostname})); if (version) { diff --git a/packages/b2c-cli/test/commands/code/deploy.test.ts b/packages/b2c-cli/test/commands/code/deploy.test.ts index 84776b17..b9df4f58 100644 --- a/packages/b2c-cli/test/commands/code/deploy.test.ts +++ b/packages/b2c-cli/test/commands/code/deploy.test.ts @@ -24,7 +24,7 @@ describe('code deploy', () => { function stubCommon(command: any) { const instance = {config: {hostname: 'example.com', codeVersion: 'v1'}}; sinon.stub(command, 'requireWebDavCredentials').returns(void 0); - sinon.stub(command, 'requireOAuthCredentials').returns(void 0); + sinon.stub(command, 'hasOAuthCredentials').returns(true); sinon.stub(command, 'log').returns(void 0); sinon.stub(command, 'warn').returns(void 0); sinon.stub(command, 'resolvedConfig').get(() => ({values: {hostname: 'example.com', codeVersion: 'v1'}})); @@ -117,7 +117,7 @@ describe('code deploy', () => { const command: any = await createCommand({}, {cartridgePath: '.'}); sinon.stub(command, 'requireWebDavCredentials').returns(void 0); - sinon.stub(command, 'requireOAuthCredentials').returns(void 0); + sinon.stub(command, 'hasOAuthCredentials').returns(true); sinon.stub(command, 'log').returns(void 0); sinon.stub(command, 'warn').returns(void 0); From 89698c06428695a161362890a874355cc16781df Mon Sep 17 00:00:00 2001 From: Charles Lavery Date: Mon, 26 Jan 2026 17:41:57 -0500 Subject: [PATCH 2/2] test: add coverage for OAuth credential check in code deploy Test both error paths when OAuth credentials are missing: - No code version specified (needs auto-discovery) - --reload flag set (needs OCAPI call) --- .../b2c-cli/test/commands/code/deploy.test.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/packages/b2c-cli/test/commands/code/deploy.test.ts b/packages/b2c-cli/test/commands/code/deploy.test.ts index b9df4f58..0c7ffe06 100644 --- a/packages/b2c-cli/test/commands/code/deploy.test.ts +++ b/packages/b2c-cli/test/commands/code/deploy.test.ts @@ -113,6 +113,52 @@ describe('code deploy', () => { expect(result.reloaded).to.equal(false); }); + it('errors when no code version and no OAuth credentials', async () => { + const command: any = await createCommand({}, {cartridgePath: '.'}); + + sinon.stub(command, 'requireWebDavCredentials').returns(void 0); + sinon.stub(command, 'hasOAuthCredentials').returns(false); + sinon.stub(command, 'log').returns(void 0); + sinon.stub(command, 'warn').returns(void 0); + sinon.stub(command, 'resolvedConfig').get(() => ({values: {hostname: 'example.com', codeVersion: undefined}})); + + const errorStub = sinon.stub(command, 'error').throws(new Error('OAuth required')); + + try { + await command.run(); + expect.fail('Should have thrown'); + } catch { + // expected + } + + expect(errorStub.calledOnce).to.equal(true); + const errorMessage = errorStub.firstCall.args[0]; + expect(errorMessage).to.include('auto-discover'); + }); + + it('errors when --reload flag set but no OAuth credentials', async () => { + const command: any = await createCommand({reload: true}, {cartridgePath: '.'}); + + sinon.stub(command, 'requireWebDavCredentials').returns(void 0); + sinon.stub(command, 'hasOAuthCredentials').returns(false); + sinon.stub(command, 'log').returns(void 0); + sinon.stub(command, 'warn').returns(void 0); + sinon.stub(command, 'resolvedConfig').get(() => ({values: {hostname: 'example.com', codeVersion: 'v1'}})); + + const errorStub = sinon.stub(command, 'error').throws(new Error('OAuth required')); + + try { + await command.run(); + expect.fail('Should have thrown'); + } catch { + // expected + } + + expect(errorStub.calledOnce).to.equal(true); + const errorMessage = errorStub.firstCall.args[0]; + expect(errorMessage).to.include('reload'); + }); + it('uses active code version when resolvedConfig is missing codeVersion', async () => { const command: any = await createCommand({}, {cartridgePath: '.'});