Skip to content

Commit 30a9a28

Browse files
committed
Merge main into feature/W-20893605-ecdn-zones
Resolved conflict in clients/index.ts by keeping both: - CDN zones client exports (from feature branch) - error-utils export (from main)
2 parents 3aba4f9 + c35f3a7 commit 30a9a28

30 files changed

Lines changed: 1251 additions & 65 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-cli': minor
3+
---
4+
5+
Add `setup config` command to display resolved configuration with source tracking.
6+
7+
Shows all configuration values organized by category (Instance, Authentication, SCAPI, MRT) and indicates which source file or environment variable provided each value. Sensitive values are masked by default; use `--unmask` to reveal them.

.changeset/fix-error-output.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@salesforce/b2c-cli': patch
3+
'@salesforce/b2c-tooling-sdk': patch
4+
---
5+
6+
Fix HTML response bodies appearing in ERROR log lines. When API requests fail with non-JSON responses (like HTML error pages), error messages now show the HTTP status code (e.g., "HTTP 521 Web Server Is Down") instead of serializing the entire response body.
7+
8+
Added `getApiErrorMessage(error, response)` utility that extracts clean error messages from ODS, OCAPI, and SCAPI error patterns with HTTP status fallback.

.claude/skills/api-client-development/SKILL.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,62 @@ it('fetches endpoints', async () => {
380380

381381
---
382382

383+
## Error Handling
384+
385+
When API requests fail, use `getApiErrorMessage()` to extract clean, user-friendly error messages. This utility handles multiple error formats and ensures HTML response bodies (like error pages from stopped sandboxes) are never shown to users.
386+
387+
### Using getApiErrorMessage
388+
389+
```typescript
390+
import {getApiErrorMessage} from '@salesforce/b2c-tooling-sdk/clients';
391+
392+
const {data, error, response} = await client.GET('/sites', {...});
393+
394+
if (error) {
395+
// Returns structured error message or "HTTP 521 Web Server Is Down"
396+
const message = getApiErrorMessage(error, response);
397+
this.error(`Failed to fetch sites: ${message}`);
398+
}
399+
```
400+
401+
### Supported Error Patterns
402+
403+
The utility extracts messages from these patterns in priority order:
404+
405+
| API | Error Structure | Message Location |
406+
|-----|-----------------|------------------|
407+
| ODS/SLAS | `{ error: { message } }` | `error.error.message` |
408+
| OCAPI | `{ fault: { message } }` | `error.fault.message` |
409+
| SCAPI/Problem+JSON | `{ title, detail }` | `error.detail` or `error.title` |
410+
| Standard Error | `{ message }` | `error.message` |
411+
| Fallback | Any | `HTTP {status} {statusText}` |
412+
413+
### Why This Matters
414+
415+
**Without `getApiErrorMessage`:**
416+
```
417+
ERROR: Failed to fetch sites: <!DOCTYPE html><html lang="en"><head><title>521 - Sandbox Down</title>...
418+
```
419+
420+
**With `getApiErrorMessage`:**
421+
```
422+
ERROR: Failed to fetch sites: HTTP 521 Web Server Is Down
423+
```
424+
425+
### Important: Always Destructure `response`
426+
427+
When making API calls, always destructure the `response` object alongside `error`:
428+
429+
```typescript
430+
// GOOD: Include response for error handling
431+
const {data, error, response} = await client.GET('/endpoint', {...});
432+
433+
// BAD: Missing response - can't get clean error message
434+
const {data, error} = await client.GET('/endpoint', {...});
435+
```
436+
437+
---
438+
383439
## Checklist: New SCAPI Client
384440

385441
1. Add OpenAPI spec to `specs/`

.claude/skills/cli-command-development/SKILL.md

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import { InstanceCommand, CartridgeCommand, OdsCommand } from '@salesforce/b2c-t
5757
*/
5858
import {Args, Flags} from '@oclif/core';
5959
import {InstanceCommand} from '@salesforce/b2c-tooling-sdk/cli';
60+
import {getApiErrorMessage} from '@salesforce/b2c-tooling-sdk';
6061
import {t} from '../../i18n/index.js';
6162

6263
interface MyCommandResponse {
@@ -105,27 +106,27 @@ export default class MyCommand extends InstanceCommand<typeof MyCommand> {
105106
this.log(t('commands.topic.mycommand.working', 'Working on {{name}}...', {name}));
106107

107108
// Implementation
108-
const result = await this.instance.ocapi.GET('/some/endpoint');
109+
const {data, error, response} = await this.instance.ocapi.GET('/some/endpoint');
109110

110-
if (!result.data) {
111+
if (error) {
111112
this.error(t('commands.topic.mycommand.error', 'Failed: {{message}}', {
112-
message: result.response?.statusText || 'Unknown error',
113+
message: getApiErrorMessage(error, response),
113114
}));
114115
}
115116

116-
const response: MyCommandResponse = {
117+
const result: MyCommandResponse = {
117118
success: true,
118-
data: result.data,
119+
data,
119120
};
120121

121122
// JSON mode returns the object directly (oclif handles serialization)
122123
if (this.jsonEnabled()) {
123-
return response;
124+
return result;
124125
}
125126

126127
// Human-readable output
127128
this.log('Success!');
128-
return response;
129+
return result;
129130
}
130131
}
131132
```
@@ -305,14 +306,21 @@ this.error('Config file not found', {
305306
// Warning (continues execution)
306307
this.warn('Deprecated flag used');
307308

308-
// Structured API errors
309-
if (result.error) {
309+
// API errors - use getApiErrorMessage for clean messages
310+
import {getApiErrorMessage} from '@salesforce/b2c-tooling-sdk';
311+
312+
const {data, error, response} = await this.instance.ocapi.GET('/sites', {...});
313+
if (error) {
310314
this.error(t('commands.topic.cmd.apiError', 'API error: {{message}}', {
311-
message: formatApiError(result.error),
315+
message: getApiErrorMessage(error, response),
312316
}));
313317
}
314318
```
315319

320+
**Important:** Always destructure `response` alongside `error` when making API calls. The `getApiErrorMessage` utility extracts clean messages from ODS, OCAPI, and SCAPI error patterns, and falls back to HTTP status (e.g., "HTTP 521 Web Server Is Down") for non-JSON responses like HTML error pages.
321+
322+
See [API Client Development](../api-client-development/SKILL.md#error-handling) for supported error patterns.
323+
316324
## Creating a Command Checklist
317325

318326
1. Create file at `packages/b2c-cli/src/commands/<topic>/<command>.ts`

docs/cli/setup.md

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,107 @@
11
---
2-
description: Commands for installing AI agent skills for Claude Code, Cursor, Windsurf, and other agentic IDEs.
2+
description: Commands for viewing configuration, installing AI agent skills, and setting up the development environment.
33
---
44

55
# Setup Commands
66

7-
Commands for setting up the development environment with AI agent skills.
7+
Commands for viewing configuration and setting up the development environment.
8+
9+
## b2c setup config
10+
11+
Display the resolved configuration from all sources, showing which values are set and where they came from. Useful for debugging configuration issues.
12+
13+
### Usage
14+
15+
```bash
16+
b2c setup config [FLAGS]
17+
```
18+
19+
### Flags
20+
21+
| Flag | Description | Default |
22+
|------|-------------|---------|
23+
| `--unmask` | Show sensitive values unmasked (passwords, secrets, API keys) | `false` |
24+
| `--json` | Output results as JSON | `false` |
25+
26+
### Examples
27+
28+
```bash
29+
# Display resolved configuration (sensitive values masked)
30+
b2c setup config
31+
32+
# Display configuration with sensitive values unmasked
33+
b2c setup config --unmask
34+
35+
# Output as JSON for scripting
36+
b2c setup config --json
37+
38+
# Debug configuration with a specific instance
39+
b2c setup config -i staging
40+
```
41+
42+
### Output
43+
44+
The command displays configuration organized by category:
45+
46+
- **Instance**: hostname, webdavHostname, codeVersion
47+
- **Authentication (Basic)**: username, password
48+
- **Authentication (OAuth)**: clientId, clientSecret, scopes, authMethods, accountManagerHost
49+
- **SCAPI**: shortCode
50+
- **Managed Runtime (MRT)**: mrtProject, mrtEnvironment, mrtApiKey, mrtOrigin
51+
- **Metadata**: instanceName
52+
- **Sources**: List of configuration sources that contributed values
53+
54+
Each value shows its source in brackets (e.g., `[dw.json]`, `[SFCC_CLIENT_ID]`, `[~/.mobify]`).
55+
56+
Example output:
57+
58+
```
59+
Configuration
60+
────────────────────────────────────────────────────────────
61+
62+
Instance
63+
hostname my-sandbox.dx.commercecloud.salesforce.com [DwJsonSource]
64+
webdavHostname -
65+
codeVersion version1 [DwJsonSource]
66+
67+
Authentication (Basic)
68+
username admin [DwJsonSource]
69+
password admi...REDACTED [DwJsonSource]
70+
71+
Authentication (OAuth)
72+
clientId my-client-id [password-store]
73+
clientSecret my-c...REDACTED [password-store]
74+
scopes -
75+
authMethods -
76+
accountManagerHost -
77+
78+
SCAPI
79+
shortCode abc123 [DwJsonSource]
80+
81+
Managed Runtime (MRT)
82+
mrtProject my-project [MobifySource]
83+
mrtApiKey mrtk...REDACTED [MobifySource]
84+
85+
Sources
86+
────────────────────────────────────────────────────────────
87+
1. DwJsonSource /path/to/project/dw.json
88+
2. MobifySource /Users/user/.mobify
89+
3. password-store pass:b2c-cli/_default
90+
```
91+
92+
### Sensitive Values
93+
94+
By default, sensitive fields are masked to prevent accidental exposure:
95+
96+
- `password` - Basic auth access key
97+
- `clientSecret` - OAuth client secret
98+
- `mrtApiKey` - MRT API key
99+
100+
Use `--unmask` to reveal the actual values when needed for debugging.
101+
102+
### See Also
103+
104+
- [Configuration Guide](/guide/configuration) - How to configure the CLI
8105

9106
## b2c setup skills
10107

docs/guide/configuration.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,29 @@ SFCC_AUTH_METHODS=client-credentials,implicit b2c code deploy
264264

265265
The CLI will try each method in order until one succeeds.
266266

267+
## Debugging Configuration
268+
269+
Use `b2c setup config` to view the resolved configuration and see which source provided each value:
270+
271+
```bash
272+
# Display resolved configuration (sensitive values masked)
273+
b2c setup config
274+
275+
# Show actual sensitive values
276+
b2c setup config --unmask
277+
278+
# Output as JSON
279+
b2c setup config --json
280+
```
281+
282+
This command helps troubleshoot issues like:
283+
- Verifying which configuration file is being used
284+
- Checking if environment variables are being read
285+
- Understanding credential source priority
286+
- Identifying hostname mismatch protection triggers
287+
288+
See [setup config](/cli/setup#b2c-setup-config) for full documentation.
289+
267290
## Next Steps
268291

269292
- [CLI Reference](/cli/) - Browse available commands

packages/b2c-cli/src/commands/ods/create.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import {Flags, ux} from '@oclif/core';
77
import cliui from 'cliui';
88
import {OdsCommand} from '@salesforce/b2c-tooling-sdk/cli';
9-
import type {OdsComponents} from '@salesforce/b2c-tooling-sdk';
9+
import {getApiErrorMessage, type OdsComponents} from '@salesforce/b2c-tooling-sdk';
1010
import {t} from '../../i18n/index.js';
1111

1212
type SandboxModel = OdsComponents['schemas']['SandboxModel'];
@@ -139,11 +139,9 @@ export default class OdsCreate extends OdsCommand<typeof OdsCreate> {
139139
});
140140

141141
if (!result.data?.data) {
142-
const errorResponse = result.error as OdsComponents['schemas']['ErrorResponse'] | undefined;
143-
const errorMessage = errorResponse?.error?.message || result.response?.statusText || 'Unknown error';
144142
this.error(
145143
t('commands.ods.create.error', 'Failed to create sandbox: {{message}}', {
146-
message: errorMessage,
144+
message: getApiErrorMessage(result.error, result.response),
147145
}),
148146
);
149147
}

packages/b2c-cli/src/commands/ods/delete.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import * as readline from 'node:readline';
77
import {Args, Flags} from '@oclif/core';
88
import {OdsCommand} from '@salesforce/b2c-tooling-sdk/cli';
9-
import type {OdsComponents} from '@salesforce/b2c-tooling-sdk';
9+
import {getApiErrorMessage} from '@salesforce/b2c-tooling-sdk';
1010
import {t} from '../../i18n/index.js';
1111

1212
/**
@@ -92,11 +92,9 @@ export default class OdsDelete extends OdsCommand<typeof OdsDelete> {
9292
});
9393

9494
if (result.response.status !== 202) {
95-
const errorResponse = result.error as OdsComponents['schemas']['ErrorResponse'] | undefined;
96-
const errorMessage = errorResponse?.error?.message || result.response?.statusText || 'Unknown error';
9795
this.error(
9896
t('commands.ods.delete.error', 'Failed to delete sandbox: {{message}}', {
99-
message: errorMessage,
97+
message: getApiErrorMessage(result.error, result.response),
10098
}),
10199
);
102100
}

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66
import {Flags} from '@oclif/core';
77
import {OdsCommand, TableRenderer, type ColumnDef} from '@salesforce/b2c-tooling-sdk/cli';
8-
import type {OdsComponents} from '@salesforce/b2c-tooling-sdk';
8+
import {getApiErrorMessage, type OdsComponents} from '@salesforce/b2c-tooling-sdk';
99
import {t} from '../../i18n/index.js';
1010

1111
type SandboxModel = OdsComponents['schemas']['SandboxModel'];
@@ -142,11 +142,9 @@ export default class OdsList extends OdsCommand<typeof OdsList> {
142142
});
143143

144144
if (result.error) {
145-
const errorResponse = result.error as OdsComponents['schemas']['ErrorResponse'] | undefined;
146-
const errorMessage = errorResponse?.error?.message || result.response?.statusText || 'Unknown error';
147145
this.error(
148146
t('commands.ods.list.error', 'Failed to fetch sandboxes: {{message}}', {
149-
message: errorMessage,
147+
message: getApiErrorMessage(result.error, result.response),
150148
}),
151149
);
152150
}

packages/b2c-cli/src/commands/ods/restart.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66
import {Args} from '@oclif/core';
77
import {OdsCommand} from '@salesforce/b2c-tooling-sdk/cli';
8-
import type {OdsComponents} from '@salesforce/b2c-tooling-sdk';
8+
import {getApiErrorMessage, type OdsComponents} from '@salesforce/b2c-tooling-sdk';
99
import {t} from '../../i18n/index.js';
1010

1111
type SandboxOperationModel = OdsComponents['schemas']['SandboxOperationModel'];
@@ -45,11 +45,9 @@ export default class OdsRestart extends OdsCommand<typeof OdsRestart> {
4545
});
4646

4747
if (!result.data?.data) {
48-
const errorResponse = result.error as OdsComponents['schemas']['ErrorResponse'] | undefined;
49-
const errorMessage = errorResponse?.error?.message || result.response?.statusText || 'Unknown error';
5048
this.error(
5149
t('commands.ods.restart.error', 'Failed to restart sandbox: {{message}}', {
52-
message: errorMessage,
50+
message: getApiErrorMessage(result.error, result.response),
5351
}),
5452
);
5553
}

0 commit comments

Comments
 (0)