Skip to content

Commit 91593f2

Browse files
authored
Add instance management commands and easy setup (#98)
* Add instance management commands - Rename `setup config` to `setup inspect` - Add `setup instance list` to view configured instances - Add `setup instance create` with interactive prompts - Add `setup instance remove` to delete instances - Add `setup instance set-active` to set default instance Extends ConfigSource interface with optional instance management methods and implements CRUD operations in DwJsonSource for managing the dw.json configs array. * Update skills for setup config → setup inspect rename - Update b2c-config skill with new command name and instance management docs - Fix reference in b2c-custom-api-development skill * Document instance management and credential storage methods Add documentation for the new optional ConfigSource methods: - listInstances, createInstance, removeInstance, setActiveInstance - credentialFields, storeCredential, removeCredential These enable plugins to provide custom instance storage (cloud config, global registry) and secure credential storage (keychain, vault). * Improve instance create and set-active interactive experience - Accept URL or hostname in create command (auto-extracts hostname) - Rename auth choices: WebDAV, API Client; show access key URL - Make client secret optional (supports user auth flow) - Auto-detect active code version via OCAPI when OAuth is configured - Add searchable instance picker to set-active when name omitted * initial setup * changeset * lint
1 parent f879d99 commit 91593f2

18 files changed

Lines changed: 2125 additions & 11 deletions

File tree

.changeset/instance-management.md

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 `setup instance` commands for managing B2C Commerce instance configurations (create, list, remove, set-active).

docs/cli/setup.md

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,186 @@ Use `--unmask` to reveal the actual values when needed for debugging.
105105

106106
- [Configuration Guide](/guide/configuration) - How to configure the CLI
107107

108+
## b2c setup instance list
109+
110+
List all configured B2C Commerce instances from dw.json.
111+
112+
### Usage
113+
114+
```bash
115+
b2c setup instance list [FLAGS]
116+
```
117+
118+
### Flags
119+
120+
| Flag | Description | Default |
121+
|------|-------------|---------|
122+
| `--json` | Output results as JSON | `false` |
123+
124+
### Examples
125+
126+
```bash
127+
# List all configured instances
128+
b2c setup instance list
129+
130+
# Output as JSON
131+
b2c setup instance list --json
132+
```
133+
134+
### Output
135+
136+
The command displays a table of configured instances:
137+
138+
```
139+
Instances
140+
────────────────────────────────────────────────────────────
141+
Name Hostname Source Active
142+
production prod.demandware.net DwJsonSource
143+
staging staging.demandware.net DwJsonSource ✓
144+
development dev.demandware.net DwJsonSource
145+
```
146+
147+
## b2c setup instance create
148+
149+
Create a new B2C Commerce instance configuration in dw.json.
150+
151+
### Usage
152+
153+
```bash
154+
b2c setup instance create [NAME] [FLAGS]
155+
```
156+
157+
### Arguments
158+
159+
| Argument | Description | Required |
160+
|----------|-------------|----------|
161+
| `NAME` | Instance name | Yes (or prompted) |
162+
163+
### Flags
164+
165+
| Flag | Description | Default |
166+
|------|-------------|---------|
167+
| `--hostname`, `-s` | B2C instance hostname | Prompted |
168+
| `--username` | WebDAV username | |
169+
| `--password` | WebDAV password | Prompted if username set |
170+
| `--client-id` | OAuth client ID | |
171+
| `--client-secret` | OAuth client secret | Prompted if client-id set |
172+
| `--code-version` | Code version | |
173+
| `--active` | Set as active instance | `false` |
174+
| `--force` | Non-interactive mode | `false` |
175+
| `--json` | Output results as JSON | `false` |
176+
177+
### Examples
178+
179+
```bash
180+
# Interactive mode (prompts for all values)
181+
b2c setup instance create staging
182+
183+
# Create with hostname
184+
b2c setup instance create staging --hostname staging.example.com
185+
186+
# Create and set as active
187+
b2c setup instance create staging --hostname staging.example.com --active
188+
189+
# Non-interactive mode (CI/CD)
190+
b2c setup instance create staging --hostname staging.example.com --username admin --password secret --force
191+
```
192+
193+
### Interactive Mode
194+
195+
When run without `--force`, the command provides an interactive experience:
196+
197+
1. Prompts for instance name (if not provided)
198+
2. Prompts for hostname (if not provided)
199+
3. Prompts for authentication type (Basic, OAuth, Both, or Skip)
200+
4. Prompts for credentials based on selection
201+
5. Asks whether to set as active instance
202+
6. Shows summary and confirms before creating
203+
204+
## b2c setup instance remove
205+
206+
Remove a B2C Commerce instance configuration from dw.json.
207+
208+
### Usage
209+
210+
```bash
211+
b2c setup instance remove NAME [FLAGS]
212+
```
213+
214+
### Arguments
215+
216+
| Argument | Description | Required |
217+
|----------|-------------|----------|
218+
| `NAME` | Instance name to remove | Yes |
219+
220+
### Flags
221+
222+
| Flag | Description | Default |
223+
|------|-------------|---------|
224+
| `--force` | Skip confirmation prompt | `false` |
225+
| `--json` | Output results as JSON | `false` |
226+
227+
### Examples
228+
229+
```bash
230+
# Remove with confirmation
231+
b2c setup instance remove staging
232+
233+
# Remove without confirmation
234+
b2c setup instance remove staging --force
235+
```
236+
237+
## b2c setup instance set-active
238+
239+
Set a B2C Commerce instance as the default (active) instance.
240+
241+
### Usage
242+
243+
```bash
244+
b2c setup instance set-active NAME [FLAGS]
245+
```
246+
247+
### Arguments
248+
249+
| Argument | Description | Required |
250+
|----------|-------------|----------|
251+
| `NAME` | Instance name to set as active | Yes |
252+
253+
### Flags
254+
255+
| Flag | Description | Default |
256+
|------|-------------|---------|
257+
| `--json` | Output results as JSON | `false` |
258+
259+
### Examples
260+
261+
```bash
262+
# Set staging as the active instance
263+
b2c setup instance set-active staging
264+
265+
# Set production as active
266+
b2c setup instance set-active production
267+
```
268+
269+
### How Active Instance Works
270+
271+
The active instance is used as the default when no `--instance` or `-i` flag is provided to other commands. This allows you to work with multiple instances without specifying which one to use each time.
272+
273+
Example workflow:
274+
275+
```bash
276+
# Configure multiple instances
277+
b2c setup instance create staging --hostname staging.example.com
278+
b2c setup instance create production --hostname prod.example.com
279+
280+
# Set staging as active
281+
b2c setup instance set-active staging
282+
283+
# Commands now use staging by default
284+
b2c code list # Uses staging
285+
b2c code list -i production # Uses production
286+
```
287+
108288
## b2c setup skills
109289

110290
Install agent skills from the B2C Developer Tooling project to AI-powered IDEs.

docs/guide/extending.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,129 @@ export class MyCustomSource implements ConfigSource {
206206
}
207207
```
208208

209+
### Instance Management Methods
210+
211+
Config sources can optionally implement instance management methods to support the `b2c setup instance` commands. This enables plugins to store and manage instance configurations in custom locations (cloud config, global registry, etc.).
212+
213+
```typescript
214+
import type {
215+
ConfigSource,
216+
ConfigLoadResult,
217+
ResolveConfigOptions,
218+
InstanceInfo,
219+
CreateInstanceOptions,
220+
} from '@salesforce/b2c-tooling-sdk/config';
221+
222+
export class MyInstanceSource implements ConfigSource {
223+
readonly name = 'my-instance-source';
224+
225+
load(options: ResolveConfigOptions): ConfigLoadResult | undefined {
226+
// Standard config loading...
227+
}
228+
229+
// List all instances from this source
230+
listInstances(options?: ResolveConfigOptions): InstanceInfo[] {
231+
return [
232+
{
233+
name: 'staging',
234+
hostname: 'staging.example.com',
235+
active: true,
236+
source: this.name,
237+
location: '/path/to/config',
238+
},
239+
];
240+
}
241+
242+
// Create a new instance
243+
createInstance(options: CreateInstanceOptions & ResolveConfigOptions): void {
244+
// Store the instance configuration
245+
}
246+
247+
// Remove an instance
248+
removeInstance(name: string, options?: ResolveConfigOptions): void {
249+
// Delete the instance configuration
250+
}
251+
252+
// Set an instance as active
253+
setActiveInstance(name: string, options?: ResolveConfigOptions): void {
254+
// Update the active flag
255+
}
256+
}
257+
```
258+
259+
When a source implements `listInstances()`, its instances appear in `b2c setup instance list`. The `InstanceManager` class aggregates instances from all sources.
260+
261+
### Credential Storage Methods
262+
263+
Config sources can optionally implement credential storage methods to securely store secrets. This is useful for keychain integrations, vault plugins, or other secure storage backends.
264+
265+
```typescript
266+
import type {
267+
ConfigSource,
268+
NormalizedConfig,
269+
ResolveConfigOptions,
270+
} from '@salesforce/b2c-tooling-sdk/config';
271+
272+
export class KeychainSource implements ConfigSource {
273+
readonly name = 'keychain';
274+
275+
// Declare which credential fields this source can store
276+
readonly credentialFields: (keyof NormalizedConfig)[] = [
277+
'password',
278+
'clientSecret',
279+
];
280+
281+
load(options: ResolveConfigOptions): ConfigLoadResult | undefined {
282+
// Load credentials from keychain for the requested instance
283+
const instanceName = options.instance || '_default';
284+
const password = this.getFromKeychain(`b2c/${instanceName}/password`);
285+
const clientSecret = this.getFromKeychain(`b2c/${instanceName}/clientSecret`);
286+
287+
if (!password && !clientSecret) {
288+
return undefined;
289+
}
290+
291+
return {
292+
config: { password, clientSecret },
293+
location: `keychain:b2c/${instanceName}`,
294+
};
295+
}
296+
297+
// Store a credential value for an instance
298+
storeCredential(
299+
instanceName: string,
300+
field: keyof NormalizedConfig,
301+
value: string,
302+
options?: ResolveConfigOptions
303+
): void {
304+
this.saveToKeychain(`b2c/${instanceName}/${String(field)}`, value);
305+
}
306+
307+
// Remove a credential for an instance
308+
removeCredential(
309+
instanceName: string,
310+
field: keyof NormalizedConfig,
311+
options?: ResolveConfigOptions
312+
): void {
313+
this.deleteFromKeychain(`b2c/${instanceName}/${String(field)}`);
314+
}
315+
316+
private getFromKeychain(key: string): string | undefined {
317+
// Keychain lookup implementation
318+
}
319+
320+
private saveToKeychain(key: string, value: string): void {
321+
// Keychain save implementation
322+
}
323+
324+
private deleteFromKeychain(key: string): void {
325+
// Keychain delete implementation
326+
}
327+
}
328+
```
329+
330+
When `b2c setup instance create` collects credentials, it checks for sources with `credentialFields` and can route secrets to secure storage instead of plaintext files.
331+
209332
### Error Handling
210333

211334
If your `ConfigSource` encounters an error (e.g., malformed config file, network failure), you can:
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 {confirm} from '@inquirer/prompts';
7+
import {BaseCommand} from '@salesforce/b2c-tooling-sdk/cli';
8+
import {withDocs} from '../../i18n/index.js';
9+
10+
/**
11+
* Default setup command - provides topic help and prompts to create an instance if none configured.
12+
*
13+
* - `b2c setup` with no instance configured (TTY): prompts to create one
14+
* - `b2c setup` with instance configured or non-TTY: shows topic help
15+
*/
16+
export default class SetupIndex extends BaseCommand<typeof SetupIndex> {
17+
static description = withDocs('Manage instances, view configuration, and install agent skills', '/cli/setup.html');
18+
19+
static examples = ['<%= config.bin %> setup --help', '<%= config.bin %> setup instance create'];
20+
21+
async run(): Promise<void> {
22+
const hasInstance = this.resolvedConfig.hasB2CInstanceConfig();
23+
const isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY);
24+
25+
if (!hasInstance && isTTY) {
26+
const shouldCreate = await confirm({
27+
message: 'No instance configured. Would you like to set one up?',
28+
default: true,
29+
});
30+
31+
if (shouldCreate) {
32+
await this.config.runCommand('setup:instance:create');
33+
return;
34+
}
35+
}
36+
37+
await this.config.runCommand('help', ['setup']);
38+
}
39+
}

0 commit comments

Comments
 (0)