Skip to content

Commit b7f78ca

Browse files
authored
Add site cartridge path management commands (#295)
* Add site cartridge path management commands (closes #194) Add `sites cartridges list|add|remove|set` commands with SDK operations for managing the ordered list of active cartridges on a site. Includes `--bm` flag for Business Manager support and automatic fallback to site archive import/export when OCAPI permissions are unavailable. * linting
1 parent c10ddad commit b7f78ca

20 files changed

Lines changed: 1963 additions & 4 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': minor
3+
'@salesforce/b2c-tooling-sdk': minor
4+
---
5+
6+
Add site cartridge path management commands (`sites cartridges list|add|remove|set`) with `--bm` flag for Business Manager support and automatic fallback to site archive import when OCAPI permissions are unavailable

docs/cli/sites.md

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ Sites commands require OAuth authentication with OCAPI permissions for the `/sit
1616
|----------|---------|
1717
| `/sites` | GET |
1818
| `/sites/*` | GET |
19+
| `/sites/*/cartridges` | POST, PUT, DELETE |
20+
21+
Cartridge path commands also work without the cartridge-specific OCAPI permissions — they automatically fall back to site archive import/export when direct OCAPI access is unavailable. The fallback requires job execution permissions for `sfcc-site-archive-import` and WebDAV write access to `Impex/`.
1922

2023
### Configuration
2124

@@ -77,3 +80,171 @@ Found 2 site(s):
7780
Status: online
7881
```
7982

83+
---
84+
85+
## Cartridge Commands
86+
87+
Manage the cartridge path for a site — the ordered list of cartridges that are active on a storefront. Use `sites cartridges` or the singular alias `sites cartridge`.
88+
89+
::: tip Business Manager
90+
Use the `--bm` flag as a shorthand for `--site-id Sites-Site` to manage the Business Manager cartridge path. BM updates always use site archive import since OCAPI direct updates are not supported for the BM site.
91+
:::
92+
93+
::: tip Automatic Fallback
94+
If OCAPI permissions for `/sites/*/cartridges` are not available, cartridge commands automatically fall back to site archive import/export. This means the commands work even without specific cartridge OCAPI permissions, as long as job execution and WebDAV access are configured.
95+
:::
96+
97+
---
98+
99+
### b2c sites cartridges list
100+
101+
List the cartridge path for a site.
102+
103+
#### Usage
104+
105+
```bash
106+
b2c sites cartridges list --site-id <site-id>
107+
b2c sites cartridges list --bm
108+
```
109+
110+
#### Flags
111+
112+
| Flag | Description |
113+
|------|-------------|
114+
| `--site-id <id>` | Site ID (e.g. `RefArch`) |
115+
| `--bm` | Use Business Manager site (`Sites-Site`) |
116+
| `--json` | Output as JSON |
117+
118+
One of `--site-id` or `--bm` is required.
119+
120+
#### Examples
121+
122+
```bash
123+
# List cartridge path for a storefront site
124+
b2c sites cartridges list --site-id RefArch
125+
126+
# List Business Manager cartridge path
127+
b2c sites cartridges list --bm
128+
129+
# JSON output for automation
130+
b2c sites cartridges list --site-id RefArch --json
131+
```
132+
133+
---
134+
135+
### b2c sites cartridges add
136+
137+
Add a cartridge to a site's cartridge path.
138+
139+
#### Usage
140+
141+
```bash
142+
b2c sites cartridges add <cartridge> --site-id <site-id> [--position <position>] [--target <target>]
143+
```
144+
145+
#### Arguments
146+
147+
| Argument | Description |
148+
|----------|-------------|
149+
| `cartridge` | Name of the cartridge to add |
150+
151+
#### Flags
152+
153+
| Flag | Description |
154+
|------|-------------|
155+
| `--site-id <id>` | Site ID (e.g. `RefArch`) |
156+
| `--bm` | Use Business Manager site (`Sites-Site`) |
157+
| `--position <pos>` | Position: `first` (default), `last`, `before`, `after` |
158+
| `--target <name>` | Target cartridge (required when position is `before` or `after`) |
159+
| `--json` | Output as JSON |
160+
161+
#### Examples
162+
163+
```bash
164+
# Add to beginning of path (default)
165+
b2c sites cartridges add plugin_applepay --site-id RefArch
166+
167+
# Add to end
168+
b2c sites cartridges add plugin_applepay --site-id RefArch --position last
169+
170+
# Add after a specific cartridge
171+
b2c sites cartridges add plugin_applepay --site-id RefArch --position after --target app_storefront_base
172+
173+
# Add to Business Manager
174+
b2c sites cartridges add bm_extension --bm --position first
175+
```
176+
177+
---
178+
179+
### b2c sites cartridges remove
180+
181+
Remove a cartridge from a site's cartridge path.
182+
183+
::: warning Destructive Operation
184+
This command modifies the site cartridge path. It is blocked in safe mode — use `--safety-level off` to allow it.
185+
:::
186+
187+
#### Usage
188+
189+
```bash
190+
b2c sites cartridges remove <cartridge> --site-id <site-id>
191+
```
192+
193+
#### Arguments
194+
195+
| Argument | Description |
196+
|----------|-------------|
197+
| `cartridge` | Name of the cartridge to remove |
198+
199+
#### Flags
200+
201+
| Flag | Description |
202+
|------|-------------|
203+
| `--site-id <id>` | Site ID (e.g. `RefArch`) |
204+
| `--bm` | Use Business Manager site (`Sites-Site`) |
205+
| `--json` | Output as JSON |
206+
207+
#### Examples
208+
209+
```bash
210+
b2c sites cartridges remove old_cartridge --site-id RefArch
211+
b2c sites cartridges remove bm_extension --bm
212+
```
213+
214+
---
215+
216+
### b2c sites cartridges set
217+
218+
Replace the entire cartridge path for a site.
219+
220+
::: warning Destructive Operation
221+
This command replaces the entire cartridge path. It is blocked in safe mode — use `--safety-level off` to allow it.
222+
:::
223+
224+
#### Usage
225+
226+
```bash
227+
b2c sites cartridges set <cartridges> --site-id <site-id>
228+
```
229+
230+
#### Arguments
231+
232+
| Argument | Description |
233+
|----------|-------------|
234+
| `cartridges` | New cartridge path (colon-separated, e.g. `cart1:cart2:cart3`) |
235+
236+
#### Flags
237+
238+
| Flag | Description |
239+
|------|-------------|
240+
| `--site-id <id>` | Site ID (e.g. `RefArch`) |
241+
| `--bm` | Use Business Manager site (`Sites-Site`) |
242+
| `--json` | Output as JSON |
243+
244+
#### Examples
245+
246+
```bash
247+
b2c sites cartridges set "app_storefront_base:plugin_applepay:plugin_wishlists" --site-id RefArch
248+
b2c sites cartridges set "bm_ext1:bm_ext2" --bm
249+
```
250+

docs/guide/sdk-migration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ The OCAPI client is generated from the OpenAPI spec, so all paths, parameters, a
388388
| `code.compare(...)` | _Not ported_ | |
389389
| `code.diffdeploy(...)` | _Not ported_ | |
390390
| `manifest.generate(...)` | _Not ported_ | |
391-
| `cartridge.add(...)` | _Planned for a future release_ | |
391+
| `cartridge.add(...)` | `addCartridge(instance, siteId, opts)` | `*/operations/sites` |
392392
| `instance.upload(instance, file, token, opts, cb)` | `instance.webdav.put(path, data)` | `*/instance` |
393393
| `instance.import(instance, file, token, cb)` | `siteArchiveImport(instance, zipPath)` | `*/operations/jobs` |
394394
| `job.run(instance, jobId, params, token, cb)` | `executeJob(instance, jobId, opts?)` | `*/operations/jobs` |

docs/guide/sfcc-ci-migration.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,17 @@ The B2C CLI's canonical syntax uses spaces instead of colons (e.g., `b2c code de
7777
| `sfcc-ci instance:import <archive>` | `b2c content import <archive>` | Or `b2c job run sfcc-site-archive-import` |
7878
| `sfcc-ci instance:export` | `b2c content export` | See [Content commands](/cli/content) |
7979

80+
### Cartridge Path
81+
82+
| sfcc-ci | b2c-cli | Notes |
83+
|---------|---------|-------|
84+
| `sfcc-ci cartridge:add <name> --siteid <id>` | `b2c sites cartridges add <name> --site-id <id>` | Supports `--position` and `--target` flags |
85+
| _(no equivalent)_ | `b2c sites cartridges list --site-id <id>` | List the active cartridge path |
86+
| _(no equivalent)_ | `b2c sites cartridges remove <name> --site-id <id>` | Remove a cartridge from the path |
87+
| _(no equivalent)_ | `b2c sites cartridges set <path> --site-id <id>` | Replace the entire cartridge path |
88+
89+
The B2C CLI also supports the Business Manager cartridge path via the `--bm` flag (shorthand for `--site-id Sites-Site`). When OCAPI direct permissions are unavailable, the commands automatically fall back to site archive import/export. See [Sites commands](/cli/sites#cartridge-commands) for details.
90+
8091
### Jobs
8192

8293
| sfcc-ci | b2c-cli | Notes |

packages/b2c-cli/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ dw.json
1818

1919
export/
2020
cartridges/
21+
!src/commands/**/cartridges/
22+
!test/commands/**/cartridges/

packages/b2c-cli/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,12 @@
183183
}
184184
},
185185
"sites": {
186-
"description": "List and inspect storefront sites\n\nDocs: https://salesforcecommercecloud.github.io/b2c-developer-tooling/cli/sites.html"
186+
"description": "List and manage storefront sites\n\nDocs: https://salesforcecommercecloud.github.io/b2c-developer-tooling/cli/sites.html",
187+
"subtopics": {
188+
"cartridges": {
189+
"description": "Manage site cartridge path (list, add, remove, set)"
190+
}
191+
}
187192
},
188193
"am": {
189194
"description": "Manage Account Manager resources",
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright (c) 2025, Salesforce, Inc.
3+
* SPDX-License-Identifier: Apache-2
4+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
import {Args, Flags} from '@oclif/core';
7+
import {InstanceCommand} from '@salesforce/b2c-tooling-sdk/cli';
8+
import {
9+
type CartridgePathResult,
10+
type CartridgePosition,
11+
BM_SITE_ID,
12+
addCartridge,
13+
} from '@salesforce/b2c-tooling-sdk/operations/sites';
14+
import {t, withDocs} from '../../../i18n/index.js';
15+
16+
export default class SitesCartridgesAdd extends InstanceCommand<typeof SitesCartridgesAdd> {
17+
static description = withDocs(
18+
t('commands.sites.cartridges.add.description', "Add a cartridge to a site's cartridge path"),
19+
'/cli/sites.html#b2c-sites-cartridges-add',
20+
);
21+
22+
static enableJsonFlag = true;
23+
24+
static examples = [
25+
'<%= config.bin %> sites cartridges add my_cartridge --site-id RefArch',
26+
'<%= config.bin %> sites cartridges add my_cartridge --site-id RefArch --position first',
27+
'<%= config.bin %> sites cartridges add my_cartridge --site-id RefArch --position after --target app_storefront_base',
28+
'<%= config.bin %> sites cartridges add bm_extension --bm',
29+
];
30+
31+
static hiddenAliases = ['sites:cartridge:add'];
32+
33+
static args = {
34+
cartridge: Args.string({
35+
description: t('args.cartridge.description', 'Cartridge name to add'),
36+
required: true,
37+
}),
38+
};
39+
40+
static flags = {
41+
'site-id': Flags.string({
42+
description: t('flags.siteId.description', 'Site ID (e.g. RefArch)'),
43+
exclusive: ['bm'],
44+
}),
45+
bm: Flags.boolean({
46+
description: t('flags.bm.description', 'Use Business Manager site (Sites-Site)'),
47+
exclusive: ['site-id'],
48+
}),
49+
position: Flags.string({
50+
description: t('flags.position.description', 'Position to add the cartridge'),
51+
options: ['first', 'last', 'before', 'after'],
52+
default: 'first',
53+
}),
54+
target: Flags.string({
55+
description: t('flags.target.description', 'Target cartridge (required when position is before/after)'),
56+
}),
57+
};
58+
59+
async run(): Promise<CartridgePathResult> {
60+
this.requireOAuthCredentials();
61+
62+
const siteId = this.resolveSiteId();
63+
const {cartridge} = this.args;
64+
const position = this.flags.position as CartridgePosition;
65+
const {target} = this.flags;
66+
67+
// Validate target is provided for relative positions
68+
if ((position === 'before' || position === 'after') && !target) {
69+
this.error(
70+
t('commands.sites.cartridges.add.targetRequired', '--target is required when --position is "{{position}}"', {
71+
position,
72+
}),
73+
);
74+
}
75+
76+
const result = await addCartridge(
77+
this.instance,
78+
siteId,
79+
{name: cartridge, position, target},
80+
{
81+
log: (msg) => {
82+
if (!this.jsonEnabled()) this.log(msg);
83+
},
84+
waitOptions: {
85+
onProgress: (exec, elapsed) => {
86+
if (!this.jsonEnabled()) {
87+
const elapsedSec = Math.floor(elapsed / 1000);
88+
this.log(
89+
t('commands.sites.cartridges.jobProgress', ' Status: {{status}} ({{elapsed}}s elapsed)', {
90+
status: exec.execution_status,
91+
elapsed: elapsedSec.toString(),
92+
}),
93+
);
94+
}
95+
},
96+
},
97+
},
98+
);
99+
100+
if (this.jsonEnabled()) {
101+
return result;
102+
}
103+
104+
this.log(
105+
t('commands.sites.cartridges.add.success', 'Added "{{cartridge}}" to site "{{siteId}}" cartridge path.', {
106+
cartridge,
107+
siteId,
108+
}),
109+
);
110+
this.log(
111+
t('commands.sites.cartridges.add.updatedPath', 'Updated path: {{cartridges}}', {
112+
cartridges: result.cartridges,
113+
}),
114+
);
115+
116+
return result;
117+
}
118+
119+
private resolveSiteId(): string {
120+
const siteId = this.flags['site-id'];
121+
const bm = this.flags.bm;
122+
123+
if (!siteId && !bm) {
124+
this.error(t('commands.sites.cartridges.siteIdRequired', 'Provide --site-id <id> or --bm to specify a site.'));
125+
}
126+
127+
return bm ? BM_SITE_ID : siteId!;
128+
}
129+
}

0 commit comments

Comments
 (0)