Skip to content

Commit 7ad490a

Browse files
authored
feat: add --wait to mrt bundle deploy and align SDK wait functions (#305)
* feat: add --wait to mrt bundle deploy and align SDK wait functions Align all SDK wait functions (waitForJob, waitForEnv) to the canonical pattern established by waitForSandbox/waitForClone: structured PollInfo callbacks, seconds-based options, injectable sleep for testing, and initial delay before first poll. Add --wait/--poll-interval/--timeout flags to mrt bundle deploy, and add missing --poll-interval/--timeout flags to mrt env create and job run for consistency across all CLI wait commands. * chore: set mrt bundle deploy default poll interval to 30s * fix: update SDK test files to use new WaitForJobOptions interface Update FAST_WAIT_OPTIONS in content/export and site-archive tests to use pollIntervalSeconds and injectable sleep instead of the removed pollInterval property.
1 parent c24e920 commit 7ad490a

24 files changed

Lines changed: 497 additions & 155 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@salesforce/b2c-tooling-sdk': minor
3+
'@salesforce/b2c-cli': minor
4+
'@salesforce/b2c-dx-docs': patch
5+
---
6+
7+
Add `--wait` flag to `mrt bundle deploy` to poll until deployment completes, and align all SDK wait functions (`waitForJob`, `waitForEnv`) to a consistent pattern with structured `onPoll` callbacks, seconds-based options, and injectable `sleep` for testing.

docs/cli/jobs.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ In addition to [global flags](./index#global-flags):
6464
|------|-------------|---------|
6565
| `--wait`, `-w` | Wait for job to complete | `false` |
6666
| `--timeout`, `-t` | Timeout in seconds when waiting | No timeout |
67+
| `--poll-interval` | Polling interval in seconds when using `--wait` | `3` |
6768
| `--param`, `-P` | Job parameter in format "name=value" (repeatable) | |
6869
| `--body`, `-B` | Raw JSON request body (for system jobs with non-standard schemas) | |
6970
| `--no-wait-running` | Do not wait for running job to finish before starting | `false` |

docs/cli/mrt.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,8 @@ b2c mrt env create prod -p my-storefront --name "Production" \
284284
| `--enable-source-maps` | Enable source maps |
285285
| `--proxy` | Proxy configuration in format `path=host` (repeatable) |
286286
| `--wait`, `-w` | Wait for the environment to be ready before returning |
287+
| `--poll-interval` | Polling interval in seconds when using `--wait` | `10` |
288+
| `--timeout` | Maximum time to wait in seconds when using `--wait` (`0` for no timeout) | `600` |
287289

288290
### b2c mrt env get
289291

@@ -454,6 +456,9 @@ b2c mrt bundle deploy -p my-storefront --build-dir ./dist
454456

455457
# Deploy existing bundle by ID
456458
b2c mrt bundle deploy 12345 -p my-storefront -e production
459+
460+
# Deploy and wait for completion
461+
b2c mrt bundle deploy -p my-storefront -e staging --wait
457462
```
458463

459464
**Flags:**
@@ -465,6 +470,9 @@ b2c mrt bundle deploy 12345 -p my-storefront -e production
465470
| `--ssr-shared` | Shared file patterns | `static/**/*,client/**/*` |
466471
| `--node-version`, `-n` | Node.js version for SSR | `22.x` |
467472
| `--ssr-param` | SSR parameters (key=value) | |
473+
| `--wait`, `-w` | Wait for the deployment to complete before returning | `false` |
474+
| `--poll-interval` | Polling interval in seconds when using `--wait` | `30` |
475+
| `--timeout` | Maximum time to wait in seconds when using `--wait` (`0` for no timeout) | `600` |
468476

469477
### b2c mrt bundle list
470478

packages/b2c-cli/src/commands/content/export.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export default class ContentExport extends JobCommand<typeof ContentExport> {
116116
this.requireOAuthCredentials();
117117
}
118118

119-
const waitOptions = flags.timeout ? {timeout: flags.timeout * 1000} : undefined;
119+
const waitOptions = flags.timeout ? {timeoutSeconds: flags.timeout} : undefined;
120120

121121
if (flags['dry-run']) {
122122
const {library} = await this.operations.fetchContentLibrary(this.instance, libraryId, {

packages/b2c-cli/src/commands/content/list.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export default class ContentList extends JobCommand<typeof ContentList> {
9898
this.requireOAuthCredentials();
9999
}
100100

101-
const waitOptions = flags.timeout ? {timeout: flags.timeout * 1000} : undefined;
101+
const waitOptions = flags.timeout ? {timeoutSeconds: flags.timeout} : undefined;
102102

103103
const instance = flags['library-file'] ? (null as unknown as typeof this.instance) : this.instance;
104104

packages/b2c-cli/src/commands/job/export.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,13 @@ export default class JobExport extends JobCommand<typeof JobExport> {
182182
this.log(t('commands.job.export.dataUnits', 'Data units: {{dataUnits}}', {dataUnits: JSON.stringify(dataUnits)}));
183183

184184
const waitOptions: WaitForJobOptions = {
185-
timeout: timeout ? timeout * 1000 : undefined,
186-
onProgress: (exec, elapsed) => {
185+
timeoutSeconds: timeout,
186+
onPoll: (info) => {
187187
if (!this.jsonEnabled()) {
188-
const elapsedSec = Math.floor(elapsed / 1000);
189188
this.log(
190189
t('commands.job.export.progress', ' Status: {{status}} ({{elapsed}}s elapsed)', {
191-
status: exec.execution_status,
192-
elapsed: elapsedSec.toString(),
190+
status: info.status,
191+
elapsed: String(info.elapsedSeconds),
193192
}),
194193
);
195194
}

packages/b2c-cli/src/commands/job/import.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,13 @@ export default class JobImport extends JobCommand<typeof JobImport> {
117117
const result = await this.operations.siteArchiveImport(this.instance, importTarget, {
118118
keepArchive,
119119
waitOptions: {
120-
timeout: timeout ? timeout * 1000 : undefined,
121-
onProgress: (exec, elapsed) => {
120+
timeoutSeconds: timeout,
121+
onPoll: (info) => {
122122
if (!this.jsonEnabled()) {
123-
const elapsedSec = Math.floor(elapsed / 1000);
124123
this.log(
125124
t('commands.job.import.progress', ' Status: {{status}} ({{elapsed}}s elapsed)', {
126-
status: exec.execution_status,
127-
elapsed: elapsedSec.toString(),
125+
status: info.status,
126+
elapsed: String(info.elapsedSeconds),
128127
}),
129128
);
130129
}

packages/b2c-cli/src/commands/job/run.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ export default class JobRun extends JobCommand<typeof JobRun> {
4949
char: 't',
5050
description: 'Timeout in seconds when waiting (default: no timeout)',
5151
}),
52+
'poll-interval': Flags.integer({
53+
description: 'Polling interval in seconds when using --wait',
54+
default: 3,
55+
dependsOn: ['wait'],
56+
}),
5257
param: Flags.string({
5358
char: 'P',
5459
description: 'Job parameter in format "name=value" (use -P multiple times for multiple params)',
@@ -80,7 +85,15 @@ export default class JobRun extends JobCommand<typeof JobRun> {
8085
this.requireOAuthCredentials();
8186

8287
const {jobId} = this.args;
83-
const {wait, timeout, param, body, 'no-wait-running': noWaitRunning, 'show-log': showLog} = this.flags;
88+
const {
89+
wait,
90+
timeout,
91+
'poll-interval': pollInterval,
92+
param,
93+
body,
94+
'no-wait-running': noWaitRunning,
95+
'show-log': showLog,
96+
} = this.flags;
8497

8598
// Parse parameters or body
8699
const parameters = this.parseParameters(param || []);
@@ -138,6 +151,7 @@ export default class JobRun extends JobCommand<typeof JobRun> {
138151
jobId,
139152
executionId: execution.id!,
140153
timeout,
154+
pollInterval,
141155
showLog,
142156
context,
143157
});
@@ -216,22 +230,23 @@ export default class JobRun extends JobCommand<typeof JobRun> {
216230
jobId: string;
217231
executionId: string;
218232
timeout: number | undefined;
233+
pollInterval: number | undefined;
219234
showLog: boolean;
220235
context: B2COperationContext;
221236
}): Promise<JobExecution> {
222-
const {jobId, executionId, timeout, showLog, context} = options;
237+
const {jobId, executionId, timeout, pollInterval, showLog, context} = options;
223238
this.log(t('commands.job.run.waiting', 'Waiting for job to complete...'));
224239

225240
try {
226241
const execution = await this.operations.waitForJob(this.instance, jobId, executionId, {
227-
timeout: timeout ? timeout * 1000 : undefined,
228-
onProgress: (exec, elapsed) => {
242+
timeoutSeconds: timeout,
243+
pollIntervalSeconds: pollInterval,
244+
onPoll: (info) => {
229245
if (!this.jsonEnabled()) {
230-
const elapsedSec = Math.floor(elapsed / 1000);
231246
this.log(
232247
t('commands.job.run.progress', ' Status: {{status}} ({{elapsed}}s elapsed)', {
233-
status: exec.execution_status,
234-
elapsed: elapsedSec.toString(),
248+
status: info.status,
249+
elapsed: String(info.elapsedSeconds),
235250
}),
236251
);
237252
}

packages/b2c-cli/src/commands/job/wait.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,14 @@ export default class JobWait extends JobCommand<typeof JobWait> {
6868

6969
try {
7070
const execution = await this.operations.waitForJob(this.instance, jobId, executionId, {
71-
timeout: timeout ? timeout * 1000 : undefined,
72-
pollInterval: pollInterval * 1000,
73-
onProgress: (exec, elapsed) => {
71+
timeoutSeconds: timeout,
72+
pollIntervalSeconds: pollInterval,
73+
onPoll: (info) => {
7474
if (!this.jsonEnabled()) {
75-
const elapsedSec = Math.floor(elapsed / 1000);
7675
this.log(
7776
t('commands.job.wait.progress', ' Status: {{status}} ({{elapsed}}s elapsed)', {
78-
status: exec.execution_status,
79-
elapsed: elapsedSec.toString(),
77+
status: info.status,
78+
elapsed: String(info.elapsedSeconds),
8079
}),
8180
);
8281
}

packages/b2c-cli/src/commands/mrt/bundle/deploy.ts

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import {MrtCommand} from '@salesforce/b2c-tooling-sdk/cli';
88
import {
99
pushBundle,
1010
createDeployment,
11+
waitForEnv,
1112
DEFAULT_SSR_PARAMETERS,
1213
type PushResult,
1314
type CreateDeploymentResult,
15+
type MrtEnvironment,
1416
} from '@salesforce/b2c-tooling-sdk/operations/mrt';
1517
import {t, withDocs} from '../../../i18n/index.js';
1618

@@ -53,7 +55,7 @@ function parseSsrParams(params: string[]): Record<string, string> {
5355
return result;
5456
}
5557

56-
type DeployResult = CreateDeploymentResult | PushResult;
58+
type DeployResult = CreateDeploymentResult | MrtEnvironment | PushResult;
5759

5860
/**
5961
* Deploy a bundle to Managed Runtime.
@@ -86,6 +88,7 @@ export default class MrtBundleDeploy extends MrtCommand<typeof MrtBundleDeploy>
8688
'<%= config.bin %> <%= command.id %> --project my-storefront --node-version 20.x',
8789
'<%= config.bin %> <%= command.id %> --project my-storefront --ssr-param SSRProxyPath=/api',
8890
'<%= config.bin %> <%= command.id %> 12345 --project my-storefront --environment staging',
91+
'<%= config.bin %> <%= command.id %> 12345 --project my-storefront --environment staging --wait',
8992
];
9093

9194
static flags = {
@@ -116,6 +119,27 @@ export default class MrtBundleDeploy extends MrtCommand<typeof MrtBundleDeploy>
116119
multiple: true,
117120
default: [],
118121
}),
122+
wait: Flags.boolean({
123+
char: 'w',
124+
description: 'Wait for the deployment to complete before returning',
125+
default: false,
126+
}),
127+
'poll-interval': Flags.integer({
128+
description: 'Polling interval in seconds when using --wait',
129+
default: 30,
130+
dependsOn: ['wait'],
131+
}),
132+
timeout: Flags.integer({
133+
description: 'Maximum time to wait in seconds when using --wait (0 for no timeout)',
134+
default: 600,
135+
dependsOn: ['wait'],
136+
}),
137+
};
138+
139+
protected operations = {
140+
pushBundle,
141+
createDeployment,
142+
waitForEnv,
119143
};
120144

121145
async run(): Promise<DeployResult> {
@@ -132,7 +156,7 @@ export default class MrtBundleDeploy extends MrtCommand<typeof MrtBundleDeploy>
132156
/**
133157
* Deploy an existing bundle to an environment.
134158
*/
135-
private async deployExistingBundle(bundleId: number): Promise<CreateDeploymentResult> {
159+
private async deployExistingBundle(bundleId: number): Promise<CreateDeploymentResult | MrtEnvironment> {
136160
const {mrtProject: project, mrtEnvironment: environment} = this.resolvedConfig.values;
137161

138162
if (!project) {
@@ -153,7 +177,7 @@ export default class MrtBundleDeploy extends MrtCommand<typeof MrtBundleDeploy>
153177
);
154178

155179
try {
156-
const result = await createDeployment(
180+
const result = await this.operations.createDeployment(
157181
{
158182
projectSlug: project,
159183
targetSlug: environment,
@@ -174,12 +198,18 @@ export default class MrtBundleDeploy extends MrtCommand<typeof MrtBundleDeploy>
174198
},
175199
),
176200
);
177-
this.log(
178-
t(
179-
'commands.mrt.bundle.deploy.note',
180-
'Note: Deployments are asynchronous. Use "b2c mrt env get" or the Runtime Admin dashboard to check status.',
181-
),
182-
);
201+
if (!this.flags.wait) {
202+
this.log(
203+
t(
204+
'commands.mrt.bundle.deploy.note',
205+
'Note: Deployments are asynchronous. Use "b2c mrt env get" or the Runtime Admin dashboard to check status.',
206+
),
207+
);
208+
}
209+
}
210+
211+
if (this.flags.wait) {
212+
return this.waitForDeployment(project, environment);
183213
}
184214

185215
return result;
@@ -198,7 +228,7 @@ export default class MrtBundleDeploy extends MrtCommand<typeof MrtBundleDeploy>
198228
/**
199229
* Push a local build to create a new bundle.
200230
*/
201-
private async pushLocalBuild(): Promise<PushResult> {
231+
private async pushLocalBuild(): Promise<MrtEnvironment | PushResult> {
202232
const {mrtProject: project, mrtEnvironment: target} = this.resolvedConfig.values;
203233
const {message} = this.flags;
204234

@@ -227,7 +257,7 @@ export default class MrtBundleDeploy extends MrtCommand<typeof MrtBundleDeploy>
227257
}
228258

229259
try {
230-
const result = await pushBundle(
260+
const result = await this.operations.pushBundle(
231261
{
232262
projectSlug: project,
233263
target,
@@ -256,6 +286,14 @@ export default class MrtBundleDeploy extends MrtCommand<typeof MrtBundleDeploy>
256286
),
257287
);
258288

289+
if (this.flags.wait) {
290+
if (!target) {
291+
this.warn('--wait was specified but no environment target was provided. Skipping wait.');
292+
return result;
293+
}
294+
return this.waitForDeployment(project, target);
295+
}
296+
259297
return result;
260298
} catch (error) {
261299
if (error instanceof Error) {
@@ -264,4 +302,46 @@ export default class MrtBundleDeploy extends MrtCommand<typeof MrtBundleDeploy>
264302
throw error;
265303
}
266304
}
305+
306+
/**
307+
* Wait for a deployment to complete by polling the environment state.
308+
*/
309+
private async waitForDeployment(project: string, environment: string): Promise<MrtEnvironment> {
310+
this.log(
311+
t('commands.mrt.bundle.deploy.waiting', 'Waiting for deployment to complete on {{environment}}...', {
312+
environment,
313+
}),
314+
);
315+
316+
const envResult = await this.operations.waitForEnv(
317+
{
318+
projectSlug: project,
319+
slug: environment,
320+
origin: this.resolvedConfig.values.mrtOrigin,
321+
pollIntervalSeconds: this.flags['poll-interval'],
322+
timeoutSeconds: this.flags.timeout,
323+
onPoll: (info) => {
324+
if (!this.jsonEnabled()) {
325+
this.log(
326+
t('commands.mrt.bundle.deploy.state', '[{{elapsed}}s] State: {{state}}', {
327+
elapsed: String(info.elapsedSeconds),
328+
state: info.state,
329+
}),
330+
);
331+
}
332+
},
333+
},
334+
this.getMrtAuth(),
335+
);
336+
337+
if (!this.jsonEnabled()) {
338+
this.log(
339+
t('commands.mrt.bundle.deploy.deployComplete', 'Deployment complete. Environment is {{state}}.', {
340+
state: envResult.state ?? 'unknown',
341+
}),
342+
);
343+
}
344+
345+
return envResult;
346+
}
267347
}

0 commit comments

Comments
 (0)