Skip to content

Commit e597e61

Browse files
committed
change / add activation
1 parent 5136c65 commit e597e61

8 files changed

Lines changed: 179 additions & 37 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@salesforce/b2c-cli': patch
3+
'@salesforce/b2c-tooling-sdk': patch
4+
---
5+
6+
Add `--activate` flag to `code deploy` for activating a code version after deploy without the toggle behavior of `--reload`. Both `--activate` and `--reload` now error on failure instead of silently continuing.

actions/code-deploy/action.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ runs:
8585
run: |
8686
CMD="code deploy ${{ inputs.cartridge-path }}"
8787
88-
if [ "${{ inputs.activate }}" = "true" ] || [ "${{ inputs.reload }}" = "true" ]; then
88+
if [ "${{ inputs.activate }}" = "true" ]; then
89+
CMD="$CMD --activate"
90+
elif [ "${{ inputs.reload }}" = "true" ]; then
8991
CMD="$CMD --reload"
9092
fi
9193

docs/cli/code.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ In addition to [global flags](./index#global-flags):
113113

114114
| Flag | Description | Default |
115115
|------|-------------|---------|
116-
| `--reload`, `-r` | Reload (re-activate) code version after deploy | `false` |
116+
| `--activate`, `-a` | Activate code version after deploy | `false` |
117+
| `--reload`, `-r` | Reload (toggle activation to force reload) code version after deploy | `false` |
117118
| `--delete` | Delete existing cartridges before upload | `false` |
118119
| `--cartridge`, `-c` | Include specific cartridge(s) (can be repeated) | |
119120
| `--exclude-cartridge`, `-x` | Exclude specific cartridge(s) (can be repeated) | |
@@ -127,8 +128,8 @@ b2c code deploy --server my-sandbox.demandware.net --code-version v1
127128
# Deploy from a specific directory
128129
b2c code deploy ./my-project --server my-sandbox.demandware.net --code-version v1
129130

130-
# Deploy and reload the code version
131-
b2c code deploy --reload
131+
# Deploy and activate the code version
132+
b2c code deploy --activate
132133

133134
# Deploy specific cartridges only
134135
b2c code deploy -c app_storefront_base -c plugin_applepay
@@ -139,8 +140,8 @@ b2c code deploy -x test_cartridge -x int_debug
139140
# Delete existing cartridges before upload
140141
b2c code deploy --delete
141142

142-
# Delete and reload
143-
b2c code deploy --delete --reload
143+
# Delete and activate
144+
b2c code deploy --delete --activate
144145

145146
# Using environment variables
146147
export SFCC_SERVER=my-sandbox.demandware.net

packages/b2c-cli/src/commands/code/deploy.ts

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
uploadCartridges,
99
deleteCartridges,
1010
getActiveCodeVersion,
11+
activateCodeVersion,
1112
reloadCodeVersion,
1213
type DeployResult,
1314
} from '@salesforce/b2c-tooling-sdk/operations/code';
@@ -32,19 +33,27 @@ export default class CodeDeploy extends CartridgeCommand<typeof CodeDeploy> {
3233
'<%= config.bin %> <%= command.id %>',
3334
'<%= config.bin %> <%= command.id %> ./my-cartridges',
3435
'<%= config.bin %> <%= command.id %> --server my-sandbox.demandware.net --code-version v1',
36+
'<%= config.bin %> <%= command.id %> --activate',
37+
'<%= config.bin %> <%= command.id %> --delete --activate',
3538
'<%= config.bin %> <%= command.id %> --reload',
36-
'<%= config.bin %> <%= command.id %> --delete --reload',
3739
'<%= config.bin %> <%= command.id %> -c app_storefront_base -c plugin_applepay',
3840
'<%= config.bin %> <%= command.id %> -x test_cartridge',
3941
];
4042

4143
static flags = {
4244
...CartridgeCommand.baseFlags,
4345
...CartridgeCommand.cartridgeFlags,
46+
activate: Flags.boolean({
47+
char: 'a',
48+
description: 'Activate code version after deploy',
49+
default: false,
50+
exclusive: ['reload'],
51+
}),
4452
reload: Flags.boolean({
4553
char: 'r',
46-
description: 'Reload (re-activate) code version after deploy',
54+
description: 'Reload (toggle activation to force reload) code version after deploy',
4755
default: false,
56+
exclusive: ['activate'],
4857
}),
4958
delete: Flags.boolean({
5059
description: 'Delete existing cartridges before upload',
@@ -56,6 +65,7 @@ export default class CodeDeploy extends CartridgeCommand<typeof CodeDeploy> {
5665
uploadCartridges,
5766
deleteCartridges,
5867
getActiveCodeVersion,
68+
activateCodeVersion,
5969
reloadCodeVersion,
6070
};
6171

@@ -65,15 +75,15 @@ export default class CodeDeploy extends CartridgeCommand<typeof CodeDeploy> {
6575
const hostname = this.resolvedConfig.values.hostname!;
6676
let version = this.resolvedConfig.values.codeVersion;
6777

68-
// OAuth is only required if:
78+
// OAuth is required if:
6979
// 1. No code version specified (need to auto-discover via OCAPI)
70-
// 2. --reload flag is set (need to call OCAPI to reload)
71-
const needsOAuth = !version || this.flags.reload;
80+
// 2. --activate or --reload flag is set (need to call OCAPI)
81+
const needsOAuth = !version || this.flags.activate || this.flags.reload;
7282
if (needsOAuth && !this.hasOAuthCredentials()) {
7383
const reason = version
7484
? t(
75-
'commands.code.deploy.oauthRequiredForReload',
76-
'The --reload flag requires OAuth credentials to reload the code version via OCAPI.',
85+
'commands.code.deploy.oauthRequiredForActivate',
86+
'The --activate/--reload flag requires OAuth credentials to manage the code version via OCAPI.',
7787
)
7888
: t(
7989
'commands.code.deploy.oauthRequiredForDiscovery',
@@ -109,6 +119,7 @@ export default class CodeDeploy extends CartridgeCommand<typeof CodeDeploy> {
109119
cartridgePath: this.cartridgePath,
110120
hostname,
111121
codeVersion: version,
122+
activate: this.flags.activate,
112123
reload: this.flags.reload,
113124
delete: this.flags.delete,
114125
...this.cartridgeOptions,
@@ -125,6 +136,7 @@ export default class CodeDeploy extends CartridgeCommand<typeof CodeDeploy> {
125136
return {
126137
cartridges: [],
127138
codeVersion: version,
139+
activated: false,
128140
reloaded: false,
129141
};
130142
}
@@ -159,20 +171,33 @@ export default class CodeDeploy extends CartridgeCommand<typeof CodeDeploy> {
159171
// Upload cartridges
160172
await this.operations.uploadCartridges(this.instance, cartridges);
161173

162-
// Optionally reload code version
174+
// Optionally activate or reload code version
175+
let activated = false;
163176
let reloaded = false;
164-
if (this.flags.reload) {
165-
try {
177+
try {
178+
if (this.flags.activate) {
179+
await this.operations.activateCodeVersion(this.instance, version);
180+
activated = true;
181+
} else if (this.flags.reload) {
166182
await this.operations.reloadCodeVersion(this.instance, version);
183+
activated = true;
167184
reloaded = true;
168-
} catch (error) {
169-
this.logger?.debug(`Could not reload code version: ${error instanceof Error ? error.message : error}`);
170185
}
186+
} catch (error) {
187+
const clientId = this.resolvedConfig.values.clientId ?? 'unknown';
188+
this.error(
189+
t(
190+
'commands.code.deploy.activateFailed',
191+
'Failed to activate code version "{{version}}": {{message}}\n\nEnsure your OCAPI client ({{clientId}}) is configured with Data API permissions.\nSee: https://salesforcecommercecloud.github.io/b2c-developer-tooling/guide/authentication.html#ocapi-configuration',
192+
{version, message: error instanceof Error ? error.message : String(error), clientId},
193+
),
194+
);
171195
}
172196

173197
const result: DeployResult = {
174198
cartridges,
175199
codeVersion: version,
200+
activated,
176201
reloaded,
177202
};
178203

@@ -187,8 +212,12 @@ export default class CodeDeploy extends CartridgeCommand<typeof CodeDeploy> {
187212
),
188213
);
189214

190-
if (result.reloaded) {
191-
this.log(t('commands.code.deploy.reloaded', 'Code version reloaded'));
215+
if (result.activated) {
216+
this.log(
217+
result.reloaded
218+
? t('commands.code.deploy.reloaded', 'Code version reloaded')
219+
: t('commands.code.deploy.activated', 'Code version activated'),
220+
);
192221
}
193222

194223
// Run afterOperation hooks with success

packages/b2c-cli/test/commands/code/deploy.test.ts

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ describe('code deploy', () => {
4242

4343
const result = await command.run();
4444

45-
expect(result).to.deep.equal({cartridges: [], codeVersion: 'v1', reloaded: false});
45+
expect(result).to.deep.equal({cartridges: [], codeVersion: 'v1', activated: false, reloaded: false});
4646
});
4747

4848
it('errors when no cartridges are found', async () => {
@@ -90,11 +90,60 @@ describe('code deploy', () => {
9090
expect(uploadStub.calledOnceWithExactly(instance, cartridges)).to.equal(true);
9191
expect(reloadStub.calledOnceWithExactly(instance, 'v1')).to.equal(true);
9292

93-
expect(result).to.deep.include({codeVersion: 'v1', reloaded: true});
93+
expect(result).to.deep.include({codeVersion: 'v1', activated: true, reloaded: true});
9494
expect(afterHooksStub.calledOnce).to.equal(true);
9595
});
9696

97-
it('swallows reload errors and still succeeds', async () => {
97+
it('calls activate after deploy when --activate is set', async () => {
98+
const command: any = await createCommand({activate: true}, {cartridgePath: '.'});
99+
const instance = stubCommon(command);
100+
101+
sinon.stub(command, 'runBeforeHooks').resolves({skip: false});
102+
sinon.stub(command, 'runAfterHooks').resolves(void 0);
103+
104+
const cartridges = [{name: 'c1', src: '/tmp/c1', dest: 'c1'}];
105+
sinon.stub(command, 'findCartridgesWithProviders').resolves(cartridges);
106+
107+
const uploadStub = sinon.stub().resolves(void 0);
108+
const activateStub = sinon.stub().resolves(void 0);
109+
command.operations = {...command.operations, uploadCartridges: uploadStub, activateCodeVersion: activateStub};
110+
111+
const result = await command.run();
112+
113+
expect(activateStub.calledOnceWithExactly(instance, 'v1')).to.equal(true);
114+
expect(result).to.deep.include({codeVersion: 'v1', activated: true, reloaded: false});
115+
});
116+
117+
it('errors when activate fails', async () => {
118+
const command: any = await createCommand({activate: true}, {cartridgePath: '.'});
119+
stubCommon(command);
120+
121+
sinon.stub(command, 'runBeforeHooks').resolves({skip: false});
122+
sinon.stub(command, 'runAfterHooks').resolves(void 0);
123+
124+
const cartridges = [{name: 'c1', src: '/tmp/c1', dest: 'c1'}];
125+
sinon.stub(command, 'findCartridgesWithProviders').resolves(cartridges);
126+
127+
const uploadStub = sinon.stub().resolves(void 0);
128+
const activateStub = sinon.stub().rejects(new Error('activate failed'));
129+
command.operations = {...command.operations, uploadCartridges: uploadStub, activateCodeVersion: activateStub};
130+
131+
const errorStub = sinon.stub(command, 'error').throws(new Error('Expected error'));
132+
133+
try {
134+
await command.run();
135+
expect.fail('Should have thrown');
136+
} catch {
137+
// expected
138+
}
139+
140+
expect(errorStub.called).to.equal(true);
141+
const errorMessage = errorStub.firstCall.args[0];
142+
expect(errorMessage).to.include('activate failed');
143+
expect(errorMessage).to.include('OCAPI');
144+
});
145+
146+
it('errors when reload fails', async () => {
98147
const command: any = await createCommand({reload: true}, {cartridgePath: '.'});
99148
stubCommon(command);
100149

@@ -108,9 +157,19 @@ describe('code deploy', () => {
108157
const reloadStub = sinon.stub().rejects(new Error('reload failed'));
109158
command.operations = {...command.operations, uploadCartridges: uploadStub, reloadCodeVersion: reloadStub};
110159

111-
const result = await command.run();
160+
const errorStub = sinon.stub(command, 'error').throws(new Error('Expected error'));
112161

113-
expect(result.reloaded).to.equal(false);
162+
try {
163+
await command.run();
164+
expect.fail('Should have thrown');
165+
} catch {
166+
// expected
167+
}
168+
169+
expect(errorStub.called).to.equal(true);
170+
const errorMessage = errorStub.firstCall.args[0];
171+
expect(errorMessage).to.include('reload failed');
172+
expect(errorMessage).to.include('OCAPI');
114173
});
115174

116175
it('errors when no code version and no OAuth credentials', async () => {
@@ -156,7 +215,30 @@ describe('code deploy', () => {
156215

157216
expect(errorStub.calledOnce).to.equal(true);
158217
const errorMessage = errorStub.firstCall.args[0];
159-
expect(errorMessage).to.include('reload');
218+
expect(errorMessage).to.include('activate');
219+
});
220+
221+
it('errors when --activate flag set but no OAuth credentials', async () => {
222+
const command: any = await createCommand({activate: true}, {cartridgePath: '.'});
223+
224+
sinon.stub(command, 'requireWebDavCredentials').returns(void 0);
225+
sinon.stub(command, 'hasOAuthCredentials').returns(false);
226+
sinon.stub(command, 'log').returns(void 0);
227+
sinon.stub(command, 'warn').returns(void 0);
228+
sinon.stub(command, 'resolvedConfig').get(() => ({values: {hostname: 'example.com', codeVersion: 'v1'}}));
229+
230+
const errorStub = sinon.stub(command, 'error').throws(new Error('OAuth required'));
231+
232+
try {
233+
await command.run();
234+
expect.fail('Should have thrown');
235+
} catch {
236+
// expected
237+
}
238+
239+
expect(errorStub.calledOnce).to.equal(true);
240+
const errorMessage = errorStub.firstCall.args[0];
241+
expect(errorMessage).to.include('activate');
160242
});
161243

162244
it('uses active code version when resolvedConfig is missing codeVersion', async () => {

0 commit comments

Comments
 (0)