Skip to content

Commit 2b95516

Browse files
committed
slas should create tenant if doesn't exist
1 parent 3aeb392 commit 2b95516

4 files changed

Lines changed: 107 additions & 13 deletions

File tree

docs/cli/slas.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,17 +102,18 @@ b2c slas client create [CLIENTID] --tenant-id <TENANT_ID> --channels <CHANNELS>
102102

103103
### Flags
104104

105-
| Flag | Description | Required |
106-
|------|-------------|----------|
107-
| `--tenant-id` | SLAS tenant ID (organization ID) | Yes |
108-
| `--channels` | Site IDs/channels (comma-separated) | Yes |
109-
| `--redirect-uri` | Redirect URIs (comma-separated) | Yes |
110-
| `--name` | Display name for the client | No |
111-
| `--scopes` | OAuth scopes for the client (comma-separated) | No |
112-
| `--default-scopes` | Use default shopper scopes | No |
113-
| `--callback-uri` | Callback URIs for passwordless login | No |
114-
| `--secret` | Client secret (generated if omitted) | No |
115-
| `--public` | Create a public client (default is private) | No |
105+
| Flag | Description | Default |
106+
|------|-------------|---------|
107+
| `--tenant-id` | SLAS tenant ID (organization ID) | Required |
108+
| `--channels` | Site IDs/channels (comma-separated) | Required |
109+
| `--redirect-uri` | Redirect URIs (comma-separated) | Required |
110+
| `--name` | Display name for the client | Auto-generated |
111+
| `--scopes` | OAuth scopes for the client (comma-separated) | |
112+
| `--default-scopes` | Use default shopper scopes | `false` |
113+
| `--callback-uri` | Callback URIs for passwordless login | |
114+
| `--secret` | Client secret (generated if omitted) | Auto-generated |
115+
| `--public` | Create a public client (default is private) | `false` |
116+
| `--[no-]create-tenant` | Automatically create tenant if it doesn't exist | `true` |
116117

117118
### Examples
118119

@@ -150,6 +151,7 @@ b2c slas client create --tenant-id abcd_123 \
150151
- If `--secret` is not provided for a private client, one will be generated
151152
- The generated secret is only shown once during creation
152153
- Use `--default-scopes` for common shopper API access scopes
154+
- By default, the tenant is automatically created if it doesn't exist. Use `--no-create-tenant` to disable this behavior if you prefer to manage tenants separately
153155

154156
---
155157

packages/b2c-cli/src/commands/slas/client/create.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ export default class SlasClientCreate extends SlasClientCommand<typeof SlasClien
9898
description: 'Create a public client (default is private)',
9999
default: false,
100100
}),
101+
'create-tenant': Flags.boolean({
102+
description: 'Automatically create tenant if it does not exist',
103+
default: true,
104+
allowNo: true,
105+
}),
101106
};
102107

103108
async run(): Promise<ClientOutput> {
@@ -113,6 +118,7 @@ export default class SlasClientCreate extends SlasClientCommand<typeof SlasClien
113118
'callback-uri': callbackUri,
114119
secret,
115120
public: isPublic,
121+
'create-tenant': createTenant,
116122
} = this.flags;
117123

118124
// Validate that either --scopes or --default-scopes is provided
@@ -140,6 +146,11 @@ export default class SlasClientCreate extends SlasClientCommand<typeof SlasClien
140146

141147
const slasClient = this.getSlasClient();
142148

149+
// Ensure tenant exists before creating client (if enabled)
150+
if (createTenant) {
151+
await this.ensureTenantExists(slasClient, tenantId);
152+
}
153+
143154
// Build body - secret should only be included for private clients
144155
const body: Record<string, unknown> = {
145156
clientId,

packages/b2c-cli/src/utils/slas/client.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,71 @@ export abstract class SlasClientCommand<T extends typeof Command> extends OAuthC
126126
const oauthStrategy = this.getOAuthStrategy();
127127
return createSlasClient({shortCode}, oauthStrategy);
128128
}
129+
130+
/**
131+
* Ensure tenant exists, creating it if necessary.
132+
* This is required before creating SLAS clients.
133+
*/
134+
protected async ensureTenantExists(slasClient: SlasClient, tenantId: string): Promise<void> {
135+
// Try to get the tenant first
136+
const {error, response} = await slasClient.GET('/tenants/{tenantId}', {
137+
params: {
138+
path: {tenantId},
139+
},
140+
});
141+
142+
// If tenant exists, we're done
143+
if (!error) {
144+
return;
145+
}
146+
147+
// Check if this is a "tenant not found" error (SLAS returns 400 with TenantNotFoundException)
148+
const isTenantNotFound =
149+
response.status === 404 ||
150+
(response.status === 400 &&
151+
typeof error === 'object' &&
152+
error !== null &&
153+
'exception_name' in error &&
154+
(error as {exception_name?: string}).exception_name === 'TenantNotFoundException');
155+
156+
// If it's not a tenant-not-found error, something else went wrong
157+
if (!isTenantNotFound) {
158+
this.error(
159+
t('commands.slas.client.create.tenantError', 'Failed to check tenant: {{message}}', {
160+
message: formatApiError(error),
161+
}),
162+
);
163+
}
164+
165+
// Tenant doesn't exist, create it with placeholder values
166+
if (!this.jsonEnabled()) {
167+
this.log(t('commands.slas.client.create.creatingTenant', 'Creating SLAS tenant {{tenantId}}...', {tenantId}));
168+
}
169+
170+
const {error: createError} = await slasClient.PUT('/tenants/{tenantId}', {
171+
params: {
172+
path: {tenantId},
173+
},
174+
body: {
175+
tenantId,
176+
merchantName: 'B2C CLI Tenant',
177+
description: 'Auto-created by b2c-cli',
178+
contact: 'B2C CLI',
179+
emailAddress: 'noreply@example.com',
180+
phoneNo: '+1 000-000-0000',
181+
},
182+
});
183+
184+
if (createError) {
185+
this.error(
186+
t('commands.slas.client.create.tenantCreateError', 'Failed to create tenant: {{message}}', {
187+
message: formatApiError(createError),
188+
}),
189+
);
190+
}
191+
192+
if (!this.jsonEnabled()) {
193+
this.log(t('commands.slas.client.create.tenantCreated', 'SLAS tenant created successfully.'));
194+
}
195+
}
129196
}

plugins/b2c-cli/skills/b2c-slas/SKILL.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,24 @@ b2c slas client get --tenant-id abcd_123 --client-id my-client-id
2929
### Create SLAS Client
3030

3131
```bash
32-
# create a new SLAS client
33-
b2c slas client create --tenant-id abcd_123
32+
# create a new SLAS client with default scopes (auto-generates UUID client ID)
33+
b2c slas client create --tenant-id abcd_123 --channels RefArch --default-scopes --redirect-uri http://localhost:3000/callback
34+
35+
# create with a specific client ID and custom scopes
36+
b2c slas client create my-client-id --tenant-id abcd_123 --channels RefArch --scopes sfcc.shopper-products,sfcc.shopper-search --redirect-uri http://localhost:3000/callback
37+
38+
# create a public client
39+
b2c slas client create --tenant-id abcd_123 --channels RefArch --default-scopes --redirect-uri http://localhost:3000/callback --public
40+
41+
# create client without auto-creating tenant (if you manage tenants separately)
42+
b2c slas client create --tenant-id abcd_123 --channels RefArch --default-scopes --redirect-uri http://localhost:3000/callback --no-create-tenant
43+
44+
# output as JSON (useful for capturing the generated secret)
45+
b2c slas client create --tenant-id abcd_123 --channels RefArch --default-scopes --redirect-uri http://localhost:3000/callback --json
3446
```
3547

48+
Note: By default, the tenant is automatically created if it doesn't exist.
49+
3650
### Update SLAS Client
3751

3852
```bash

0 commit comments

Comments
 (0)