Skip to content

Commit b1600fa

Browse files
authored
Refresh MRT admin schema; add env clone, bundle delete, org member/cert commands (#405)
* Refresh MRT admin schema and add env clone, bundle delete, org members, org certs - Update OpenAPI spec from cloud-soak.mrt-soak.com (target clone, certificates, org members, bundle delete + bulk-delete endpoints) and regenerate types. - SDK: add cloneEnv, deleteBundle/bulkDeleteBundles, organization-member operations, certificate operations. - CLI: b2c mrt env clone, b2c mrt bundle delete, b2c mrt org member list/add/get/update/remove, b2c mrt org cert list/get/create/delete/restart-validation. - Docs and b2c-mrt skill updated; changesets included. * Fix prettier formatting in cloneEnv test * Use base -e/--environment flag as clone source instead of separate --from Drop the redundant --from flag on b2c mrt env clone. Every MrtCommand already has a base --environment / -e flag (with MRT_ENVIRONMENT and dw.json mrtEnvironment fallbacks) that selects the working environment; using it as the source for clone keeps the behavior consistent with the rest of the env commands. The positional argument is the new slug. Also reject identical source and destination slugs.
1 parent 3cefda3 commit b1600fa

31 files changed

Lines changed: 5860 additions & 96 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@salesforce/b2c-agent-plugins': patch
3+
---
4+
5+
Document new MRT environment clone, bundle delete, organization member, and organization certificate commands in the `b2c-mrt` skill.

.changeset/mrt-schema-refresh.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@salesforce/b2c-cli': minor
3+
'@salesforce/b2c-tooling-sdk': minor
4+
---
5+
6+
Refresh the MRT admin API schema and add new commands:
7+
8+
- `b2c mrt env clone` — clone an environment from an existing source, optionally copying redirects, environment variables, and B2C target info
9+
- `b2c mrt bundle delete` — delete one or more bundles (uses bulk-delete when more than one ID is supplied)
10+
- `b2c mrt org member list|add|get|update|remove` — manage organization-level members
11+
- `b2c mrt org cert list|get|create|delete|restart-validation` — manage custom domain certificates referenced by environments

docs/cli/mrt.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,35 @@ b2c mrt env delete staging --project my-storefront
323323
b2c mrt env delete old-env -p my-storefront --force
324324
```
325325

326+
### b2c mrt env clone
327+
328+
Clone an environment from an existing source environment. The new target receives the source's configuration (excluding proxies and the production flag) and is automatically deployed with the source target's current bundle (if any). Optionally clones redirects, environment variables, and B2C target info.
329+
330+
The source environment is the one selected by `--environment` / `-e` (or `MRT_ENVIRONMENT` / `mrtEnvironment` in `dw.json`). The positional argument is the **new** environment's slug.
331+
332+
```bash
333+
# Clone the configured environment into a new slug
334+
b2c mrt env clone staging-copy -p my-storefront -e staging
335+
336+
# Clone with redirects and environment variables
337+
b2c mrt env clone qa -p my-storefront -e staging --clone-redirects --clone-env-vars
338+
339+
# Clone using a custom domain certificate
340+
b2c mrt env clone qa -p my-storefront -e staging \
341+
--external-hostname qa.example.com --certificate-id 123 --wait
342+
```
343+
344+
| Flag | Description |
345+
|------|-------------|
346+
| `--environment`, `-e` | Source environment slug (defaults to `mrtEnvironment` / `MRT_ENVIRONMENT`) |
347+
| `--external-hostname` | Full external hostname (required for non-MRT-managed certificates) |
348+
| `--external-domain` | External domain for Universal PWA SSR |
349+
| `--certificate-id` | Certificate ID for custom domain (use `b2c mrt org cert list` to find) |
350+
| `--clone-redirects` | Clone redirects from the source environment |
351+
| `--clone-env-vars` | Clone environment variables from the source environment |
352+
| `--clone-b2c-info` | Clone B2C target info from the source environment |
353+
| `--wait`, `-w` | Wait for the new environment to reach a terminal state |
354+
326355
### b2c mrt env invalidate
327356

328357
Invalidate CDN cache for an environment. The `--pattern` flag is required and accepts a path pattern (use `/*` to invalidate everything).
@@ -552,6 +581,105 @@ b2c mrt bundle download 12345 -p my-storefront -o ./artifacts/bundle.tgz
552581
b2c mrt bundle download 12345 -p my-storefront --url-only
553582
```
554583

584+
### b2c mrt bundle delete
585+
586+
Delete one or more bundles. Bundles are deleted asynchronously by the server and only project admins can run this command. With more than one bundle ID the CLI uses the bulk-delete endpoint and reports any rejected bundles (e.g. bundles in use by an active deployment).
587+
588+
```bash
589+
# Delete a single bundle
590+
b2c mrt bundle delete 12345 -p my-storefront
591+
592+
# Delete several at once
593+
b2c mrt bundle delete 12345 12346 12347 -p my-storefront
594+
595+
# Skip the confirmation prompt
596+
b2c mrt bundle delete 12345 -p my-storefront --force
597+
```
598+
599+
---
600+
601+
## Organization Member Commands
602+
603+
Organization members are distinct from project members: they hold a role at the organization level and can optionally be granted permission to view all projects and manage custom domain certificates.
604+
605+
### b2c mrt org member list
606+
607+
```bash
608+
b2c mrt org member list --org my-org
609+
b2c mrt org member list --org my-org --search alice
610+
```
611+
612+
### b2c mrt org member add
613+
614+
Roles: `owner` or `member`.
615+
616+
```bash
617+
b2c mrt org member add alice@example.com --org my-org --role member
618+
b2c mrt org member add bob@example.com --org my-org --role owner --view-all-projects
619+
```
620+
621+
### b2c mrt org member get
622+
623+
```bash
624+
b2c mrt org member get alice@example.com --org my-org
625+
```
626+
627+
### b2c mrt org member update
628+
629+
```bash
630+
b2c mrt org member update alice@example.com --org my-org --view-all-projects
631+
b2c mrt org member update alice@example.com --org my-org --no-cert-permission
632+
```
633+
634+
### b2c mrt org member remove
635+
636+
```bash
637+
b2c mrt org member remove alice@example.com --org my-org
638+
```
639+
640+
---
641+
642+
## Organization Certificate Commands
643+
644+
Manage custom domain certificates for environments that use a non-MRT-managed hostname. Certificates are organization-scoped; reference a certificate from `env create`, `env update`, or `env clone` via `--certificate-id`.
645+
646+
### b2c mrt org cert list
647+
648+
```bash
649+
b2c mrt org cert list --org my-org
650+
b2c mrt org cert list --org my-org --custom-only
651+
```
652+
653+
### b2c mrt org cert get
654+
655+
Returns the validation record (the DNS entry the customer must add to validate the certificate).
656+
657+
```bash
658+
b2c mrt org cert get 123 --org my-org
659+
```
660+
661+
### b2c mrt org cert create
662+
663+
```bash
664+
b2c mrt org cert create shop.example.com --org my-org
665+
```
666+
667+
The output includes the validation record. Add it to your DNS to complete validation.
668+
669+
### b2c mrt org cert delete
670+
671+
```bash
672+
b2c mrt org cert delete 123 --org my-org
673+
```
674+
675+
### b2c mrt org cert restart-validation
676+
677+
Restart validation for a certificate that has not yet been validated. The response includes a fresh validation record.
678+
679+
```bash
680+
b2c mrt org cert restart-validation 123 --org my-org
681+
```
682+
555683
---
556684

557685
## Tail Logs

packages/b2c-cli/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,12 @@
147147
"mrt:org": {
148148
"description": "List organizations and view B2C Commerce instance connections\n\nDocs: https://salesforcecommercecloud.github.io/b2c-developer-tooling/cli/mrt.html#organization-commands"
149149
},
150+
"mrt:org:member": {
151+
"description": "Manage organization-level members and their permissions\n\nDocs: https://salesforcecommercecloud.github.io/b2c-developer-tooling/cli/mrt.html#organization-member-commands"
152+
},
153+
"mrt:org:cert": {
154+
"description": "Manage custom domain certificates for organization environments\n\nDocs: https://salesforcecommercecloud.github.io/b2c-developer-tooling/cli/mrt.html#organization-certificate-commands"
155+
},
150156
"mrt:project": {
151157
"description": "Create, update, delete, and configure MRT projects\n\nDocs: https://salesforcecommercecloud.github.io/b2c-developer-tooling/cli/mrt.html#project-commands"
152158
},
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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 {MrtCommand} from '@salesforce/b2c-tooling-sdk/cli';
8+
import {
9+
bulkDeleteBundles,
10+
deleteBundle,
11+
type BulkDeleteBundlesResult,
12+
} from '@salesforce/b2c-tooling-sdk/operations/mrt';
13+
import {t, withDocs} from '../../../i18n/index.js';
14+
import {confirm} from '../../../prompts.js';
15+
16+
interface DeleteBundleResult {
17+
queued: number[];
18+
rejected: BulkDeleteBundlesResult['rejected'];
19+
}
20+
21+
export default class MrtBundleDelete extends MrtCommand<typeof MrtBundleDelete> {
22+
static args = {
23+
bundleId: Args.integer({
24+
description: 'Bundle ID to delete (additional IDs may follow)',
25+
required: true,
26+
}),
27+
};
28+
29+
static description = withDocs(
30+
t('commands.mrt.bundle.delete.description', 'Delete one or more bundles from a Managed Runtime project'),
31+
'/cli/mrt.html#b2c-mrt-bundle-delete',
32+
);
33+
34+
static enableJsonFlag = true;
35+
36+
static examples = [
37+
'<%= config.bin %> <%= command.id %> 12345 --project my-storefront',
38+
'<%= config.bin %> <%= command.id %> 12345 12346 12347 -p my-storefront',
39+
'<%= config.bin %> <%= command.id %> 12345 -p my-storefront --force',
40+
];
41+
42+
static flags = {
43+
...MrtCommand.baseFlags,
44+
force: Flags.boolean({
45+
char: 'f',
46+
description: 'Skip confirmation prompt',
47+
default: false,
48+
}),
49+
};
50+
51+
// Allow multiple positional bundle IDs
52+
static strict = false;
53+
54+
protected operations = {
55+
deleteBundle,
56+
bulkDeleteBundles,
57+
};
58+
59+
async run(): Promise<DeleteBundleResult> {
60+
this.requireMrtCredentials();
61+
62+
const {argv, flags} = await this.parse(MrtBundleDelete);
63+
const {mrtProject: project} = this.resolvedConfig.values;
64+
65+
if (!project) {
66+
this.error('MRT project is required. Provide --project flag, set MRT_PROJECT, or set mrtProject in dw.json.');
67+
}
68+
69+
const bundleIds: number[] = [];
70+
for (const arg of argv as Array<number | string>) {
71+
const n = typeof arg === 'number' ? arg : Number.parseInt(String(arg), 10);
72+
if (!Number.isInteger(n) || n <= 0) {
73+
this.error(t('commands.mrt.bundle.delete.invalidId', 'Invalid bundle ID: {{arg}}', {arg: String(arg)}));
74+
}
75+
bundleIds.push(n);
76+
}
77+
78+
if (!flags.force && !this.jsonEnabled()) {
79+
const message =
80+
bundleIds.length === 1
81+
? t('commands.mrt.bundle.delete.confirmOne', 'Delete bundle {{id}} from {{project}}?', {
82+
id: String(bundleIds[0]),
83+
project,
84+
})
85+
: t('commands.mrt.bundle.delete.confirmMany', 'Delete {{n}} bundles from {{project}}?', {
86+
n: String(bundleIds.length),
87+
project,
88+
});
89+
const confirmed = await confirm(message);
90+
if (!confirmed) {
91+
this.log(t('commands.mrt.bundle.delete.cancelled', 'Delete cancelled.'));
92+
return {queued: [], rejected: []};
93+
}
94+
}
95+
96+
try {
97+
if (bundleIds.length === 1) {
98+
const [bundleId] = bundleIds;
99+
await this.operations.deleteBundle(
100+
{
101+
projectSlug: project,
102+
bundleId,
103+
origin: this.resolvedConfig.values.mrtOrigin,
104+
},
105+
this.getMrtAuth(),
106+
);
107+
108+
if (!this.jsonEnabled()) {
109+
this.log(
110+
t('commands.mrt.bundle.delete.queuedOne', 'Bundle {{id}} queued for deletion.', {id: String(bundleId)}),
111+
);
112+
}
113+
114+
return {queued: [bundleId], rejected: []};
115+
}
116+
117+
const result = await this.operations.bulkDeleteBundles(
118+
{
119+
projectSlug: project,
120+
bundleIds,
121+
origin: this.resolvedConfig.values.mrtOrigin,
122+
},
123+
this.getMrtAuth(),
124+
);
125+
126+
if (!this.jsonEnabled()) {
127+
this.log(
128+
t('commands.mrt.bundle.delete.queuedMany', '{{n}} bundle(s) queued for deletion.', {
129+
n: String(result.queued.length),
130+
}),
131+
);
132+
if (result.rejected.length > 0) {
133+
this.log(t('commands.mrt.bundle.delete.rejectedHeader', 'Rejected bundles:'));
134+
for (const r of result.rejected) {
135+
this.log(` - ${r.bundleId ?? '?'}: ${r.reason}`);
136+
}
137+
}
138+
}
139+
140+
return result;
141+
} catch (error) {
142+
if (error instanceof Error) {
143+
this.error(
144+
t('commands.mrt.bundle.delete.failed', 'Failed to delete bundle(s): {{message}}', {message: error.message}),
145+
);
146+
}
147+
throw error;
148+
}
149+
}
150+
}

0 commit comments

Comments
 (0)