Skip to content

Commit 33dbd2f

Browse files
authored
fix: support sandbox-api-host and mrtApiKey in config sources (#112)
* fix: support sandbox-api-host in config sources like dw.json The sandbox-api-host parameter was missing from the config resolution pipeline - it only worked as a CLI flag or env var. Add it to DwJsonConfig, NormalizedConfig, mapping, merge, and OdsCommand's loadConfiguration() so it works from all config sources. * fix: add sandboxApiHost to PackageJsonSource allowed fields * docs: update configuration tables with missing fields Add missing entries to env vars table (SFCC_SHORTCODE, SFCC_TENANT_ID, SFCC_ACCOUNT_MANAGER_HOST, SFCC_WEBDAV_SERVER, SFCC_SANDBOX_API_HOST, MRT vars), dw.json fields table (tenant-id, account-manager-host, sandbox-api-host, MRT fields), and package.json table (sandboxApiHost). * feat: support mrtApiKey in dw.json config source Add mrtApiKey to DwJsonConfig interface and dw.json mapping so MRT API key can be configured in dw.json alongside mrtProject/mrtEnvironment. * fix: handle undefined resolvedConfig in odsHost getter Use optional chaining on resolvedConfig in the odsHost getter so it falls back to DEFAULT_ODS_HOST when resolvedConfig isn't yet populated (e.g. in unit tests that stub commands without full init). * feat: normalize config keys to accept both camelCase and kebab-case Add normalizeConfigKeys() utility that resolves all key variants to canonical camelCase at load boundaries (loadDwJson, PackageJsonSource). Users can now use either format in dw.json and package.json b2c config. * fix: add sandboxApiHost to setup config display and export normalizeConfigKeys
1 parent 7687570 commit 33dbd2f

14 files changed

Lines changed: 509 additions & 97 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-tooling-sdk': minor
3+
---
4+
5+
Accept both camelCase and kebab-case for all field names in dw.json and package.json `b2c` config. For example, `clientId` and `client-id` are now equivalent everywhere. Legacy aliases like `server`, `passphrase`, and `selfsigned` continue to work.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@salesforce/b2c-tooling-sdk': patch
3+
---
4+
5+
Support `sandbox-api-host` in dw.json and other config sources (previously only worked as a CLI flag or environment variable)

docs/guide/configuration.md

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,25 @@ You can configure the CLI using environment variables:
6161
| `SFCC_CONFIG` | Path to config file (dw.json format) |
6262
| `SFCC_INSTANCE` | Instance name from config file |
6363
| `SFCC_SERVER` | The B2C instance hostname |
64+
| `SFCC_WEBDAV_SERVER` | Separate hostname for WebDAV (if different from main hostname) |
65+
| `SFCC_CODE_VERSION` | Code version for deployments |
6466
| `SFCC_CLIENT_ID` | OAuth client ID |
6567
| `SFCC_CLIENT_SECRET` | OAuth client secret |
68+
| `SFCC_OAUTH_SCOPES` | OAuth scopes to request |
69+
| `SFCC_AUTH_METHODS` | Comma-separated list of allowed auth methods |
70+
| `SFCC_SHORTCODE` | SCAPI short code |
71+
| `SFCC_TENANT_ID` | Organization/tenant ID for SCAPI |
72+
| `SFCC_ACCOUNT_MANAGER_HOST` | Account Manager hostname for OAuth |
6673
| `SFCC_USERNAME` | Basic auth username |
6774
| `SFCC_PASSWORD` | Basic auth password |
68-
| `SFCC_AUTH_METHODS` | Comma-separated list of allowed auth methods |
69-
| `SFCC_OAUTH_SCOPES` | OAuth scopes to request |
70-
| `SFCC_CODE_VERSION` | Code version for deployments |
7175
| `SFCC_CERTIFICATE` | Path to PKCS12 certificate for two-factor auth (mTLS) |
7276
| `SFCC_CERTIFICATE_PASSPHRASE` | Passphrase for the certificate |
7377
| `SFCC_SELFSIGNED` | Allow self-signed server certificates |
78+
| `SFCC_SANDBOX_API_HOST` | ODS (sandbox) API hostname |
79+
| `SFCC_MRT_API_KEY` | MRT API key |
80+
| `SFCC_MRT_PROJECT` | MRT project slug |
81+
| `SFCC_MRT_ENVIRONMENT` | MRT environment name |
82+
| `SFCC_MRT_CLOUD_ORIGIN` | MRT API origin URL override |
7483

7584
## .env File
7685

@@ -91,6 +100,10 @@ Add `.env` to your `.gitignore` to avoid committing credentials.
91100

92101
You can create a `dw.json` file to store instance settings. The CLI searches for this file starting from the current directory and walking up the directory tree.
93102

103+
::: tip Flexible Field Names
104+
Both camelCase and kebab-case are accepted for all field names in `dw.json`. For example, `client-id` and `clientId` are equivalent, as are `code-version` and `codeVersion`. Legacy aliases like `server` (for `hostname`) and `passphrase` (for `certificatePassphrase`) are also still supported.
105+
:::
106+
94107
### Single Instance
95108

96109
```json
@@ -144,7 +157,7 @@ If no instance is specified, the config with `"active": true` is used.
144157

145158
| Field | Description |
146159
|-------|-------------|
147-
| `hostname` | B2C instance hostname |
160+
| `hostname` | B2C instance hostname. Also accepts `server`. |
148161
| `webdav-hostname` | Separate hostname for WebDAV (if different from main hostname). Also accepts `webdav-server`, `secureHostname`, or `secure-server`. |
149162
| `code-version` | Code version for deployments |
150163
| `client-id` | OAuth client ID |
@@ -153,7 +166,14 @@ If no instance is specified, the config with `"active": true` is used.
153166
| `password` | Basic auth access key (WebDAV) |
154167
| `oauth-scopes` | OAuth scopes (array of strings) |
155168
| `auth-methods` | Authentication methods in priority order (array of strings) |
169+
| `account-manager-host` | Account Manager hostname for OAuth |
156170
| `shortCode` | SCAPI short code. Also accepts `short-code` or `scapi-shortcode`. |
171+
| `tenant-id` | Organization/tenant ID for SCAPI |
172+
| `sandbox-api-host` | ODS (sandbox) API hostname |
173+
| `mrtApiKey` | MRT API key |
174+
| `mrtProject` | MRT project slug |
175+
| `mrtEnvironment` | MRT environment name |
176+
| `mrtOrigin` | MRT API origin URL override. Also accepts `cloudOrigin`. |
157177
| `certificate` | Path to PKCS12 certificate for two-factor auth (mTLS) |
158178
| `certificate-passphrase` | Passphrase for the certificate. Also accepts `passphrase`. |
159179
| `self-signed` | Allow self-signed server certificates. Also accepts `selfsigned`. |
@@ -177,7 +197,7 @@ For instances that require client certificate authentication:
177197
The certificate must be in PKCS12 format (`.p12` or `.pfx`). The `self-signed` option is often needed for staging environments with internal certificates.
178198

179199
::: tip MRT Configuration
180-
Managed Runtime API key is not stored in `dw.json`. It is loaded from `~/.mobify`. You can specify `mrtProject` and `mrtEnvironment` in `dw.json` for project/environment selection.
200+
MRT API key can also be loaded from `~/.mobify`. See [MRT API Key](#mrt-api-key) below.
181201
:::
182202

183203
For multi-instance configurations, each config object also supports:
@@ -206,7 +226,7 @@ You can store project-level defaults in your `package.json` file under the `b2c`
206226

207227
### Allowed Fields
208228

209-
Only non-sensitive, project-level fields can be configured in `package.json`:
229+
Only non-sensitive, project-level fields can be configured in `package.json`. Both camelCase and kebab-case are accepted (e.g., `shortCode` or `short-code`):
210230

211231
| Field | Description |
212232
|-------|-------------|
@@ -215,6 +235,7 @@ Only non-sensitive, project-level fields can be configured in `package.json`:
215235
| `mrtProject` | MRT project slug |
216236
| `mrtOrigin` | MRT API origin URL override |
217237
| `accountManagerHost` | Account Manager hostname for OAuth |
238+
| `sandboxApiHost` | ODS (sandbox) API hostname |
218239

219240
::: warning Security Note
220241
Sensitive fields like `hostname`, `password`, `clientSecret`, `username`, and `mrtApiKey` are intentionally **not** supported in `package.json`. These should be configured via `dw.json` (which should be in `.gitignore`), environment variables, or secure credential stores.

packages/b2c-cli/src/commands/setup/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ export default class SetupConfig extends BaseCommand<typeof SetupConfig> {
186186
['scopes', config.scopes],
187187
['authMethods', config.authMethods],
188188
['accountManagerHost', config.accountManagerHost],
189+
['sandboxApiHost', config.sandboxApiHost],
189190
],
190191
fieldSources,
191192
unmask,

packages/b2c-tooling-sdk/src/cli/config.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,21 @@ export function extractOAuthFlags(flags: ParsedFlags): Partial<NormalizedConfig>
6767
};
6868
}
6969

70+
/**
71+
* Extracts ODS-related configuration from oclif flags.
72+
*
73+
* Includes OAuth flags since ODS operations require OAuth authentication.
74+
*
75+
* @param flags - Parsed oclif flags
76+
* @returns Partial NormalizedConfig with ODS and OAuth fields
77+
*/
78+
export function extractOdsFlags(flags: ParsedFlags): Partial<NormalizedConfig> {
79+
return {
80+
sandboxApiHost: flags['sandbox-api-host'] as string | undefined,
81+
...extractOAuthFlags(flags),
82+
};
83+
}
84+
7085
/**
7186
* Extracts B2C instance-related configuration from oclif flags.
7287
*

packages/b2c-tooling-sdk/src/cli/ods-command.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
import {Command, Flags} from '@oclif/core';
77
import {OAuthCommand} from './oauth-command.js';
8+
import {loadConfig, extractOdsFlags} from './config.js';
9+
import type {ResolvedB2CConfig} from '../config/index.js';
810
import {createOdsClient, type OdsClient} from '../clients/ods.js';
911
import {DEFAULT_ODS_HOST} from '../defaults.js';
1012
import {isUuid, parseFriendlySandboxId, SandboxNotFoundError} from '../operations/ods/sandbox-lookup.js';
@@ -41,6 +43,14 @@ export abstract class OdsCommand<T extends typeof Command> extends OAuthCommand<
4143
}),
4244
};
4345

46+
protected override loadConfiguration(): ResolvedB2CConfig {
47+
return loadConfig(
48+
extractOdsFlags(this.flags as Record<string, unknown>),
49+
this.getBaseConfigOptions(),
50+
this.getPluginSources(),
51+
);
52+
}
53+
4454
private _odsClient?: OdsClient;
4555

4656
/**
@@ -81,7 +91,7 @@ export abstract class OdsCommand<T extends typeof Command> extends OAuthCommand<
8191
* Gets the configured ODS API host.
8292
*/
8393
protected get odsHost(): string {
84-
return this.flags['sandbox-api-host'] ?? DEFAULT_ODS_HOST;
94+
return this.resolvedConfig?.values.sandboxApiHost ?? DEFAULT_ODS_HOST;
8595
}
8696

8797
/**

packages/b2c-tooling-sdk/src/config/dw-json.ts

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@ import * as fs from 'node:fs';
1515
import * as path from 'node:path';
1616
import type {AuthMethod} from '../auth/types.js';
1717
import {getLogger} from '../logging/logger.js';
18+
import {normalizeConfigKeys} from './mapping.js';
1819

1920
/**
20-
* Configuration structure matching dw.json file format.
21-
* Uses kebab-case keys to match the file format.
21+
* Configuration structure for dw.json after key normalization.
22+
*
23+
* All keys are normalized to camelCase by `normalizeConfigKeys()` when loading.
24+
* Both camelCase and kebab-case are accepted in the raw file; the interface
25+
* documents the canonical (post-normalization) field names.
26+
*
27+
* Legacy aliases (e.g., `server`, `secureHostname`, `passphrase`, `selfsigned`,
28+
* `cloudOrigin`, `scapi-shortcode`) are also accepted and mapped to their
29+
* canonical names during normalization.
2230
*/
2331
export interface DwJsonConfig {
2432
/** Instance name (for multi-config files) */
@@ -27,58 +35,44 @@ export interface DwJsonConfig {
2735
active?: boolean;
2836
/** B2C instance hostname */
2937
hostname?: string;
30-
/** B2C instance hostname (alias for CLI flag consistency) */
31-
server?: string;
3238
/** Code version for deployments */
33-
'code-version'?: string;
39+
codeVersion?: string;
3440
/** Username for Basic auth (WebDAV) */
3541
username?: string;
3642
/** Password/access-key for Basic auth (WebDAV) */
3743
password?: string;
3844
/** OAuth client ID */
39-
'client-id'?: string;
45+
clientId?: string;
4046
/** OAuth client secret */
41-
'client-secret'?: string;
47+
clientSecret?: string;
4248
/** OAuth scopes */
43-
'oauth-scopes'?: string[];
44-
/** SCAPI short code (kebab-case) */
45-
'short-code'?: string;
46-
/** SCAPI short code (camelCase) */
49+
oauthScopes?: string[];
50+
/** SCAPI short code */
4751
shortCode?: string;
48-
/** SCAPI short code (alternate kebab-case) */
49-
'scapi-shortcode'?: string;
5052
/** Alternate hostname for WebDAV (if different from main hostname) */
51-
'webdav-hostname'?: string;
52-
/** Alternate hostname for WebDAV (matches CLI flag name) */
53-
'webdav-server'?: string;
54-
/** Alternate hostname for WebDAV (legacy camelCase format) */
55-
secureHostname?: string;
56-
/** Alternate hostname for WebDAV (legacy kebab-case format) */
57-
'secure-server'?: string;
53+
webdavHostname?: string;
5854
/** Allowed authentication methods in priority order */
59-
'auth-methods'?: AuthMethod[];
55+
authMethods?: AuthMethod[];
6056
/** Account Manager hostname for OAuth */
61-
'account-manager-host'?: string;
57+
accountManagerHost?: string;
6258
/** MRT project slug */
6359
mrtProject?: string;
6460
/** MRT environment name (e.g., staging, production) */
6561
mrtEnvironment?: string;
62+
/** MRT API key */
63+
mrtApiKey?: string;
6664
/** MRT cloud origin URL */
6765
mrtOrigin?: string;
68-
/** MRT cloud origin URL (alias) */
69-
cloudOrigin?: string;
7066
/** Tenant/Organization ID for SCAPI */
71-
'tenant-id'?: string;
67+
tenantId?: string;
68+
/** ODS API hostname */
69+
sandboxApiHost?: string;
7270
/** Path to PKCS12 certificate file for mTLS (two-factor auth) */
7371
certificate?: string;
74-
/** Passphrase for the certificate (kebab-case) */
75-
'certificate-passphrase'?: string;
76-
/** Passphrase for the certificate (legacy) */
77-
passphrase?: string;
78-
/** Allow self-signed certificates (kebab-case) */
79-
'self-signed'?: boolean;
80-
/** Allow self-signed certificates (legacy) */
81-
selfsigned?: boolean;
72+
/** Passphrase for the certificate */
73+
certificatePassphrase?: string;
74+
/** Whether to skip SSL/TLS certificate verification (self-signed certs) */
75+
selfSigned?: boolean;
8276
}
8377

8478
/**
@@ -243,8 +237,19 @@ export function loadDwJson(options: LoadDwJsonOptions = {}): LoadDwJsonResult |
243237

244238
try {
245239
const content = fs.readFileSync(dwJsonPath, 'utf8');
246-
const json = JSON.parse(content) as DwJsonMultiConfig;
247-
const config = selectConfig(json, options.instance);
240+
const raw = JSON.parse(content) as Record<string, unknown>;
241+
242+
// Normalize root-level keys to camelCase
243+
const normalized = normalizeConfigKeys(raw) as DwJsonMultiConfig;
244+
245+
// Normalize keys in each configs[] item
246+
if (Array.isArray(normalized.configs)) {
247+
normalized.configs = normalized.configs.map(
248+
(item) => normalizeConfigKeys(item as Record<string, unknown>) as DwJsonConfig,
249+
);
250+
}
251+
252+
const config = selectConfig(normalized, options.instance);
248253
if (!config) {
249254
return undefined;
250255
}

packages/b2c-tooling-sdk/src/config/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export type {
113113
} from './types.js';
114114

115115
// Instance creation utility (public API for CLI commands)
116-
export {createInstanceFromConfig} from './mapping.js';
116+
export {createInstanceFromConfig, normalizeConfigKeys} from './mapping.js';
117117

118118
// Low-level dw.json API (still available for advanced use)
119119
export {loadDwJson, findDwJson} from './dw-json.js';

0 commit comments

Comments
 (0)