Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/instance-management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@salesforce/b2c-cli': minor
'@salesforce/b2c-tooling-sdk': minor
---

Add `setup instance` commands for managing B2C Commerce instance configurations (create, list, remove, set-active).
180 changes: 180 additions & 0 deletions docs/cli/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,186 @@ Use `--unmask` to reveal the actual values when needed for debugging.

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

## b2c setup instance list

List all configured B2C Commerce instances from dw.json.

### Usage

```bash
b2c setup instance list [FLAGS]
```

### Flags

| Flag | Description | Default |
|------|-------------|---------|
| `--json` | Output results as JSON | `false` |

### Examples

```bash
# List all configured instances
b2c setup instance list

# Output as JSON
b2c setup instance list --json
```

### Output

The command displays a table of configured instances:

```
Instances
────────────────────────────────────────────────────────────
Name Hostname Source Active
production prod.demandware.net DwJsonSource
staging staging.demandware.net DwJsonSource ✓
development dev.demandware.net DwJsonSource
```

## b2c setup instance create

Create a new B2C Commerce instance configuration in dw.json.

### Usage

```bash
b2c setup instance create [NAME] [FLAGS]
```

### Arguments

| Argument | Description | Required |
|----------|-------------|----------|
| `NAME` | Instance name | Yes (or prompted) |

### Flags

| Flag | Description | Default |
|------|-------------|---------|
| `--hostname`, `-s` | B2C instance hostname | Prompted |
| `--username` | WebDAV username | |
| `--password` | WebDAV password | Prompted if username set |
| `--client-id` | OAuth client ID | |
| `--client-secret` | OAuth client secret | Prompted if client-id set |
| `--code-version` | Code version | |
| `--active` | Set as active instance | `false` |
| `--force` | Non-interactive mode | `false` |
| `--json` | Output results as JSON | `false` |

### Examples

```bash
# Interactive mode (prompts for all values)
b2c setup instance create staging

# Create with hostname
b2c setup instance create staging --hostname staging.example.com

# Create and set as active
b2c setup instance create staging --hostname staging.example.com --active

# Non-interactive mode (CI/CD)
b2c setup instance create staging --hostname staging.example.com --username admin --password secret --force
```

### Interactive Mode

When run without `--force`, the command provides an interactive experience:

1. Prompts for instance name (if not provided)
2. Prompts for hostname (if not provided)
3. Prompts for authentication type (Basic, OAuth, Both, or Skip)
4. Prompts for credentials based on selection
5. Asks whether to set as active instance
6. Shows summary and confirms before creating

## b2c setup instance remove

Remove a B2C Commerce instance configuration from dw.json.

### Usage

```bash
b2c setup instance remove NAME [FLAGS]
```

### Arguments

| Argument | Description | Required |
|----------|-------------|----------|
| `NAME` | Instance name to remove | Yes |

### Flags

| Flag | Description | Default |
|------|-------------|---------|
| `--force` | Skip confirmation prompt | `false` |
| `--json` | Output results as JSON | `false` |

### Examples

```bash
# Remove with confirmation
b2c setup instance remove staging

# Remove without confirmation
b2c setup instance remove staging --force
```

## b2c setup instance set-active

Set a B2C Commerce instance as the default (active) instance.

### Usage

```bash
b2c setup instance set-active NAME [FLAGS]
```

### Arguments

| Argument | Description | Required |
|----------|-------------|----------|
| `NAME` | Instance name to set as active | Yes |

### Flags

| Flag | Description | Default |
|------|-------------|---------|
| `--json` | Output results as JSON | `false` |

### Examples

```bash
# Set staging as the active instance
b2c setup instance set-active staging

# Set production as active
b2c setup instance set-active production
```

### How Active Instance Works

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.

Example workflow:

```bash
# Configure multiple instances
b2c setup instance create staging --hostname staging.example.com
b2c setup instance create production --hostname prod.example.com

# Set staging as active
b2c setup instance set-active staging

# Commands now use staging by default
b2c code list # Uses staging
b2c code list -i production # Uses production
```

## b2c setup skills

Install agent skills from the B2C Developer Tooling project to AI-powered IDEs.
Expand Down
123 changes: 123 additions & 0 deletions docs/guide/extending.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,129 @@ export class MyCustomSource implements ConfigSource {
}
```

### Instance Management Methods

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.).

```typescript
import type {
ConfigSource,
ConfigLoadResult,
ResolveConfigOptions,
InstanceInfo,
CreateInstanceOptions,
} from '@salesforce/b2c-tooling-sdk/config';

export class MyInstanceSource implements ConfigSource {
readonly name = 'my-instance-source';

load(options: ResolveConfigOptions): ConfigLoadResult | undefined {
// Standard config loading...
}

// List all instances from this source
listInstances(options?: ResolveConfigOptions): InstanceInfo[] {
return [
{
name: 'staging',
hostname: 'staging.example.com',
active: true,
source: this.name,
location: '/path/to/config',
},
];
}

// Create a new instance
createInstance(options: CreateInstanceOptions & ResolveConfigOptions): void {
// Store the instance configuration
}

// Remove an instance
removeInstance(name: string, options?: ResolveConfigOptions): void {
// Delete the instance configuration
}

// Set an instance as active
setActiveInstance(name: string, options?: ResolveConfigOptions): void {
// Update the active flag
}
}
```

When a source implements `listInstances()`, its instances appear in `b2c setup instance list`. The `InstanceManager` class aggregates instances from all sources.

### Credential Storage Methods

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.

```typescript
import type {
ConfigSource,
NormalizedConfig,
ResolveConfigOptions,
} from '@salesforce/b2c-tooling-sdk/config';

export class KeychainSource implements ConfigSource {
readonly name = 'keychain';

// Declare which credential fields this source can store
readonly credentialFields: (keyof NormalizedConfig)[] = [
'password',
'clientSecret',
];

load(options: ResolveConfigOptions): ConfigLoadResult | undefined {
// Load credentials from keychain for the requested instance
const instanceName = options.instance || '_default';
const password = this.getFromKeychain(`b2c/${instanceName}/password`);
const clientSecret = this.getFromKeychain(`b2c/${instanceName}/clientSecret`);

if (!password && !clientSecret) {
return undefined;
}

return {
config: { password, clientSecret },
location: `keychain:b2c/${instanceName}`,
};
}

// Store a credential value for an instance
storeCredential(
instanceName: string,
field: keyof NormalizedConfig,
value: string,
options?: ResolveConfigOptions
): void {
this.saveToKeychain(`b2c/${instanceName}/${String(field)}`, value);
}

// Remove a credential for an instance
removeCredential(
instanceName: string,
field: keyof NormalizedConfig,
options?: ResolveConfigOptions
): void {
this.deleteFromKeychain(`b2c/${instanceName}/${String(field)}`);
}

private getFromKeychain(key: string): string | undefined {
// Keychain lookup implementation
}

private saveToKeychain(key: string, value: string): void {
// Keychain save implementation
}

private deleteFromKeychain(key: string): void {
// Keychain delete implementation
}
}
```

When `b2c setup instance create` collects credentials, it checks for sources with `credentialFields` and can route secrets to secure storage instead of plaintext files.

### Error Handling

If your `ConfigSource` encounters an error (e.g., malformed config file, network failure), you can:
Expand Down
39 changes: 39 additions & 0 deletions packages/b2c-cli/src/commands/setup/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2025, Salesforce, Inc.
* SPDX-License-Identifier: Apache-2
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
*/
import {confirm} from '@inquirer/prompts';
import {BaseCommand} from '@salesforce/b2c-tooling-sdk/cli';
import {withDocs} from '../../i18n/index.js';

/**
* Default setup command - provides topic help and prompts to create an instance if none configured.
*
* - `b2c setup` with no instance configured (TTY): prompts to create one
* - `b2c setup` with instance configured or non-TTY: shows topic help
*/
export default class SetupIndex extends BaseCommand<typeof SetupIndex> {
static description = withDocs('Manage instances, view configuration, and install agent skills', '/cli/setup.html');

static examples = ['<%= config.bin %> setup --help', '<%= config.bin %> setup instance create'];

async run(): Promise<void> {
const hasInstance = this.resolvedConfig.hasB2CInstanceConfig();
const isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY);

if (!hasInstance && isTTY) {
const shouldCreate = await confirm({
message: 'No instance configured. Would you like to set one up?',
default: true,
});

if (shouldCreate) {
await this.config.runCommand('setup:instance:create');
return;
}
}

await this.config.runCommand('help', ['setup']);
}
}
Loading
Loading