Skip to content

Commit 232e6d2

Browse files
authored
W-21213800 adding realm, alias and usage commands in ODS (#139)
* @W-21213800 adding realm alias usage reset commands in ods * added unit tests
1 parent d3ded7e commit 232e6d2

21 files changed

Lines changed: 3238 additions & 0 deletions

File tree

docs/cli/sandbox.md

Lines changed: 424 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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 {Args, Flags} from '@oclif/core';
7+
import {OdsCommand} from '@salesforce/b2c-tooling-sdk/cli';
8+
import {getApiErrorMessage, type OdsComponents} from '@salesforce/b2c-tooling-sdk';
9+
import {t, withDocs} from '../../../i18n/index.js';
10+
import open from 'open';
11+
12+
type SandboxAliasModel = OdsComponents['schemas']['SandboxAliasModel'];
13+
14+
/**
15+
* Command to create a sandbox alias.
16+
*/
17+
export default class SandboxAliasCreate extends OdsCommand<typeof SandboxAliasCreate> {
18+
static aliases = ['ods:alias:create'];
19+
20+
static args = {
21+
sandboxId: Args.string({
22+
description: 'Sandbox ID (UUID or realm-instance, e.g., abcd-123)',
23+
required: true,
24+
}),
25+
hostname: Args.string({
26+
description: 'Hostname alias to register (e.g., my-store.example.com)',
27+
required: true,
28+
}),
29+
};
30+
31+
static description = withDocs(
32+
t('commands.sandbox.alias.create.description', 'Create a hostname alias for a sandbox'),
33+
'/cli/sandbox.html#b2c-sandbox-alias-create',
34+
);
35+
36+
static enableJsonFlag = true;
37+
38+
static examples = [
39+
'<%= config.bin %> <%= command.id %> zzzv-123 my-store.example.com',
40+
'<%= config.bin %> <%= command.id %> zzzv-123 secure-store.example.com --unique',
41+
'<%= config.bin %> <%= command.id %> zzzv-123 secure-store.example.com --unique --letsencrypt',
42+
'<%= config.bin %> <%= command.id %> zzzv-123 my-store.example.com --json',
43+
];
44+
45+
static flags = {
46+
unique: Flags.boolean({
47+
char: 'u',
48+
description: "Make the alias unique (required for Let's Encrypt certificates)",
49+
default: false,
50+
}),
51+
letsencrypt: Flags.boolean({
52+
description: "Request a Let's Encrypt certificate for this alias (requires --unique)",
53+
default: false,
54+
dependsOn: ['unique'],
55+
}),
56+
'no-open': Flags.boolean({
57+
description: 'Do not open registration URL in browser (for non-unique aliases)',
58+
default: false,
59+
}),
60+
};
61+
62+
async run(): Promise<SandboxAliasModel> {
63+
const {sandboxId, hostname} = this.args;
64+
const {unique, letsencrypt, 'no-open': noOpen} = this.flags;
65+
66+
const resolvedSandboxId = await this.resolveSandboxId(sandboxId);
67+
68+
this.log(
69+
t('commands.sandbox.alias.create.creating', 'Creating alias {{hostname}} for sandbox {{sandboxId}}...', {
70+
hostname,
71+
sandboxId,
72+
}),
73+
);
74+
75+
const result = await this.odsClient.POST('/sandboxes/{sandboxId}/aliases', {
76+
params: {
77+
path: {sandboxId: resolvedSandboxId},
78+
},
79+
body: {
80+
name: hostname,
81+
unique,
82+
requestLetsEncryptCertificate: letsencrypt,
83+
},
84+
});
85+
86+
if (!result.data?.data) {
87+
const message = getApiErrorMessage(result.error, result.response);
88+
this.error(
89+
t('commands.sandbox.alias.create.error', 'Failed to create alias: {{message}}', {
90+
message,
91+
}),
92+
);
93+
}
94+
95+
const alias = result.data.data as SandboxAliasModel;
96+
97+
if (!this.jsonEnabled()) {
98+
this.log(t('commands.sandbox.alias.create.success', 'Alias created successfully'));
99+
this.log('');
100+
this.log(`ID: ${alias.id}`);
101+
this.log(`Hostname: ${alias.name}`);
102+
this.log(`Status: ${alias.status}`);
103+
104+
if (unique && alias.domainVerificationRecord) {
105+
this.log('');
106+
this.log(t('commands.sandbox.alias.create.verification', '⚠️ DNS Verification Required:'));
107+
this.log(
108+
t(
109+
'commands.sandbox.alias.create.verification_instructions',
110+
'Add this TXT record to your DNS configuration:',
111+
),
112+
);
113+
this.log(` ${alias.domainVerificationRecord}`);
114+
this.log('');
115+
this.log(t('commands.sandbox.alias.create.verification_wait', 'The alias will activate after DNS propagation'));
116+
}
117+
118+
if (!unique && alias.registration && !noOpen) {
119+
this.log('');
120+
this.log(t('commands.sandbox.alias.create.registration', 'Opening alias registration in browser...'));
121+
await open(alias.registration);
122+
}
123+
}
124+
125+
return alias;
126+
}
127+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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 {Args, Flags} from '@oclif/core';
7+
import {OdsCommand} from '@salesforce/b2c-tooling-sdk/cli';
8+
import {getApiErrorMessage} from '@salesforce/b2c-tooling-sdk';
9+
import {t, withDocs} from '../../../i18n/index.js';
10+
import {confirm} from '@inquirer/prompts';
11+
12+
/**
13+
* Command to delete a sandbox alias.
14+
*/
15+
export default class SandboxAliasDelete extends OdsCommand<typeof SandboxAliasDelete> {
16+
static aliases = ['ods:alias:delete'];
17+
18+
static args = {
19+
sandboxId: Args.string({
20+
description: 'Sandbox ID (UUID or realm-instance, e.g., abcd-123)',
21+
required: true,
22+
}),
23+
aliasId: Args.string({
24+
description: 'Alias ID to delete',
25+
required: true,
26+
}),
27+
};
28+
29+
static description = withDocs(
30+
t('commands.sandbox.alias.delete.description', 'Delete a hostname alias from a sandbox'),
31+
'/cli/sandbox.html#b2c-sandbox-alias-delete',
32+
);
33+
34+
static enableJsonFlag = true;
35+
36+
static examples = [
37+
'<%= config.bin %> <%= command.id %> zzzv-123 alias-uuid-here',
38+
'<%= config.bin %> <%= command.id %> zzzv-123 alias-uuid-here --force',
39+
'<%= config.bin %> <%= command.id %> abc12345-1234-1234-1234-abc123456789 alias-uuid-here --json',
40+
];
41+
42+
static flags = {
43+
force: Flags.boolean({
44+
char: 'f',
45+
description: 'Skip confirmation prompt',
46+
default: false,
47+
}),
48+
};
49+
50+
async run(): Promise<{success: boolean; message: string}> {
51+
const {sandboxId, aliasId} = this.args;
52+
const {force} = this.flags;
53+
54+
const resolvedSandboxId = await this.resolveSandboxId(sandboxId);
55+
56+
// Confirmation prompt (skip if --force or --json)
57+
if (!force && !this.jsonEnabled()) {
58+
const confirmed = await confirm({
59+
message: t('commands.sandbox.alias.delete.confirm', 'Delete alias {{aliasId}}?', {aliasId}),
60+
default: false,
61+
});
62+
63+
if (!confirmed) {
64+
this.log(t('commands.sandbox.alias.delete.cancelled', 'Delete cancelled'));
65+
return {success: false, message: 'Cancelled by user'};
66+
}
67+
}
68+
69+
this.log(
70+
t('commands.sandbox.alias.delete.deleting', 'Deleting alias {{aliasId}} from sandbox {{sandboxId}}...', {
71+
aliasId,
72+
sandboxId,
73+
}),
74+
);
75+
76+
const result = await this.odsClient.DELETE('/sandboxes/{sandboxId}/aliases/{sandboxAliasId}', {
77+
params: {
78+
path: {sandboxId: resolvedSandboxId, sandboxAliasId: aliasId},
79+
},
80+
});
81+
82+
if (result.response?.status !== 404 && result.error) {
83+
const message = getApiErrorMessage(result.error, result.response);
84+
this.error(
85+
t('commands.sandbox.alias.delete.error', 'Failed to delete alias: {{message}}', {
86+
message,
87+
}),
88+
);
89+
}
90+
91+
const message = t('commands.sandbox.alias.delete.success', 'Alias deleted successfully');
92+
this.log(message);
93+
94+
return {success: true, message};
95+
}
96+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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 {Args, Flags} from '@oclif/core';
7+
import {OdsCommand, TableRenderer} from '@salesforce/b2c-tooling-sdk/cli';
8+
import {getApiErrorMessage, type OdsComponents} from '@salesforce/b2c-tooling-sdk';
9+
import {t, withDocs} from '../../../i18n/index.js';
10+
11+
type SandboxAliasModel = OdsComponents['schemas']['SandboxAliasModel'];
12+
13+
/**
14+
* Command to list sandbox aliases.
15+
*/
16+
export default class SandboxAliasList extends OdsCommand<typeof SandboxAliasList> {
17+
static aliases = ['ods:alias:list'];
18+
19+
static args = {
20+
sandboxId: Args.string({
21+
description: 'Sandbox ID (UUID or realm-instance, e.g., abcd-123)',
22+
required: true,
23+
}),
24+
};
25+
26+
static description = withDocs(
27+
t('commands.sandbox.alias.list.description', 'List all hostname aliases for a sandbox'),
28+
'/cli/sandbox.html#b2c-sandbox-alias-list',
29+
);
30+
31+
static enableJsonFlag = true;
32+
33+
static examples = [
34+
'<%= config.bin %> <%= command.id %> abc12345-1234-1234-1234-abc123456789',
35+
'<%= config.bin %> <%= command.id %> zzzv-123',
36+
'<%= config.bin %> <%= command.id %> zzzv-123 --alias-id some-alias-uuid',
37+
'<%= config.bin %> <%= command.id %> zzzv-123 --json',
38+
];
39+
40+
static flags = {
41+
'alias-id': Flags.string({
42+
description: 'Specific alias ID to retrieve (if omitted, lists all aliases)',
43+
required: false,
44+
}),
45+
};
46+
47+
async run(): Promise<SandboxAliasModel | SandboxAliasModel[]> {
48+
const {sandboxId} = this.args;
49+
const {'alias-id': aliasId} = this.flags;
50+
51+
const resolvedSandboxId = await this.resolveSandboxId(sandboxId);
52+
53+
// If alias ID provided, get specific alias; otherwise list all
54+
if (aliasId) {
55+
return this.showAlias(resolvedSandboxId, aliasId);
56+
}
57+
return this.listAllAliases(resolvedSandboxId);
58+
}
59+
60+
private async listAllAliases(sandboxId: string): Promise<SandboxAliasModel[]> {
61+
this.log(
62+
t('commands.sandbox.alias.list.fetching', 'Fetching aliases for sandbox {{sandboxId}}...', {
63+
sandboxId: this.args.sandboxId,
64+
}),
65+
);
66+
67+
const result = await this.odsClient.GET('/sandboxes/{sandboxId}/aliases', {
68+
params: {
69+
path: {sandboxId},
70+
},
71+
});
72+
73+
if (!result.data?.data) {
74+
const message = getApiErrorMessage(result.error, result.response);
75+
this.error(
76+
t('commands.sandbox.alias.list.error', 'Failed to fetch aliases: {{message}}', {
77+
message,
78+
}),
79+
);
80+
}
81+
82+
const aliases = (result.data?.data ?? []) as SandboxAliasModel[];
83+
84+
if (!this.jsonEnabled()) {
85+
if (aliases.length === 0) {
86+
this.log(t('commands.sandbox.alias.list.no_aliases', 'No aliases found'));
87+
} else {
88+
this.log(t('commands.sandbox.alias.list.count', 'Found {{count}} alias(es):', {count: aliases.length}));
89+
const columns = {
90+
id: {
91+
header: 'Alias ID',
92+
get: (row: SandboxAliasModel) => row.id || '-',
93+
},
94+
name: {
95+
header: 'Hostname',
96+
get: (row: SandboxAliasModel) => row.name,
97+
},
98+
status: {
99+
header: 'Status',
100+
get: (row: SandboxAliasModel) => row.status || '-',
101+
},
102+
unique: {
103+
header: 'Unique',
104+
get: (row: SandboxAliasModel) => (row.unique ? 'Yes' : 'No'),
105+
},
106+
domainVerificationRecord: {
107+
header: 'Verification Record',
108+
get: (row: SandboxAliasModel) => row.domainVerificationRecord || '-',
109+
},
110+
};
111+
const table = new TableRenderer(columns);
112+
table.render(aliases, ['id', 'name', 'status', 'unique', 'domainVerificationRecord']);
113+
}
114+
}
115+
116+
return aliases;
117+
}
118+
119+
private async showAlias(sandboxId: string, aliasId: string): Promise<SandboxAliasModel> {
120+
this.log(
121+
t('commands.sandbox.alias.list.fetching_one', 'Fetching alias {{aliasId}} for sandbox {{sandboxId}}...', {
122+
aliasId,
123+
sandboxId,
124+
}),
125+
);
126+
127+
const result = await this.odsClient.GET('/sandboxes/{sandboxId}/aliases/{sandboxAliasId}', {
128+
params: {
129+
path: {sandboxId, sandboxAliasId: aliasId},
130+
},
131+
});
132+
133+
if (!result.data?.data) {
134+
const message = getApiErrorMessage(result.error, result.response);
135+
this.error(
136+
t('commands.sandbox.alias.list.error_one', 'Failed to fetch alias: {{message}}', {
137+
message,
138+
}),
139+
);
140+
}
141+
142+
const alias = result.data.data as SandboxAliasModel;
143+
144+
if (!this.jsonEnabled()) {
145+
this.log('');
146+
this.log(t('commands.sandbox.alias.list.alias_details', 'Alias Details:'));
147+
this.log('─'.repeat(60));
148+
this.log(`ID: ${alias.id}`);
149+
this.log(`Name: ${alias.name}`);
150+
this.log(`Status: ${alias.status}`);
151+
if (alias.unique) {
152+
this.log(`Unique: ${alias.unique}`);
153+
}
154+
if (alias.domainVerificationRecord) {
155+
this.log(`Verification Record: ${alias.domainVerificationRecord}`);
156+
}
157+
if (alias.registration) {
158+
this.log(`Registration URL: ${alias.registration}`);
159+
}
160+
}
161+
162+
return alias;
163+
}
164+
}

0 commit comments

Comments
 (0)