Skip to content

Commit 3c8b31b

Browse files
committed
wip
1 parent 13e4000 commit 3c8b31b

File tree

26 files changed

+1290
-305
lines changed

26 files changed

+1290
-305
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
# B2C CLI and Tooling SDK
3+
4+
## Features
5+
6+
- Separate CLI and SDK
7+
- Logging
8+
- Localization Support
9+
10+
## CLI
11+
12+
See [CLI README](./packages/b2c-cli/README.md) for more details.
13+
14+
## Tooling SDK
15+
16+
See [Tooling SDK README](./packages/b2c-tooling/README.md) for more details.
17+
18+
## Development
19+
20+
```bash
21+
pnpm install
22+
pnpm run dev # start the cli in dev mode
23+
```

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"description": "Salesforce Commerce Cloud B2C Command Line Tools",
55
"main": "index.js",
66
"scripts": {
7-
"test": "pnpm -r test"
7+
"test": "pnpm -r test",
8+
"start": "pnpm --filter @salesforce/b2c-cli run dev"
89
},
910
"keywords": [],
1011
"author": "",

packages/b2c-cli/package.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@
6464
},
6565
"code": {
6666
"description": "Manage cartridge code on instances"
67+
},
68+
"sites": {
69+
"description": "Manage sites on instances"
70+
},
71+
"sandbox": {
72+
"description": "Manage on-demand sandboxes"
73+
},
74+
"mrt": {
75+
"description": "Managed Runtime operations"
6776
}
6877
}
6978
},
@@ -76,7 +85,8 @@
7685
"prepack": "oclif manifest && oclif readme",
7786
"pretest": "tsc --noEmit -p test",
7887
"test": "OCLIF_TEST_ROOT=. mocha --forbid-only \"test/**/*.test.ts\"",
79-
"version": "oclif readme && git add README.md"
88+
"version": "oclif readme && git add README.md",
89+
"dev": "node ./bin/dev.js"
8090
},
8191
"types": "dist/index.d.ts"
8292
}
Lines changed: 19 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,42 @@
1-
import { Args, Command, Flags } from '@oclif/core';
2-
import { B2CInstance, uploadCartridges } from '@salesforce/b2c-tooling';
3-
import { loadConfig } from '../../config/loader.js';
4-
import { AuthResolver } from '../../config/auth-resolver.js';
1+
import { Args } from '@oclif/core'
2+
import { uploadCartridges } from '@salesforce/b2c-tooling'
3+
import { InstanceCommand } from '@salesforce/b2c-tooling/cli'
54

6-
export default class Deploy extends Command {
5+
export default class Deploy extends InstanceCommand<typeof Deploy> {
76
static args = {
87
cartridgePath: Args.string({
98
description: 'Path to cartridges directory',
109
default: './cartridges',
1110
}),
12-
};
11+
}
1312

14-
static description = 'Deploy cartridges to a B2C Commerce instance';
13+
static description = 'Deploy cartridges to a B2C Commerce instance'
1514

1615
static examples = [
1716
'<%= config.bin %> <%= command.id %>',
1817
'<%= config.bin %> <%= command.id %> ./my-cartridges',
19-
'<%= config.bin %> <%= command.id %> --hostname my-sandbox.demandware.net --code-version v1',
20-
];
21-
22-
static flags = {
23-
hostname: Flags.string({
24-
char: 'h',
25-
description: 'Instance hostname',
26-
}),
27-
'code-version': Flags.string({
28-
char: 'v',
29-
description: 'Code version to deploy to',
30-
}),
31-
username: Flags.string({
32-
char: 'u',
33-
description: 'Username for Basic Auth',
34-
}),
35-
password: Flags.string({
36-
char: 'p',
37-
description: 'Password for Basic Auth',
38-
}),
39-
'client-id': Flags.string({
40-
description: 'Client ID for OAuth',
41-
}),
42-
'client-secret': Flags.string({
43-
description: 'Client Secret for OAuth',
44-
}),
45-
};
18+
'<%= config.bin %> <%= command.id %> --server my-sandbox.demandware.net --code-version v1',
19+
]
4620

4721
async run(): Promise<void> {
48-
const { args, flags } = await this.parse(Deploy);
49-
50-
// 1. Load Config with precedence (CLI > Env > dw.json)
51-
const config = await loadConfig({
52-
hostname: flags.hostname,
53-
codeVersion: flags['code-version'],
54-
username: flags.username,
55-
password: flags.password,
56-
clientId: flags['client-id'],
57-
clientSecret: flags['client-secret'],
58-
});
59-
60-
// Validate required config
61-
if (!config.hostname) {
62-
this.error('Hostname is required. Set via --hostname, DW_HOSTNAME env var, or dw.json');
63-
}
64-
65-
if (!config.codeVersion) {
66-
this.error(
67-
'Code version is required. Set via --code-version, DW_CODE_VERSION env var, or dw.json'
68-
);
69-
}
70-
71-
// 2. Create Auth Resolver
72-
const resolver = new AuthResolver(config);
73-
74-
if (!resolver.hasWebDavCredentials()) {
75-
this.error(
76-
'No valid credentials found. Provide username/password or clientId/clientSecret.'
77-
);
78-
}
22+
this.requireServer()
23+
this.requireCodeVersion()
24+
this.requireWebDavCredentials()
7925

80-
// 3. Create Instance with WebDAV Auth Strategy
81-
const instance = new B2CInstance(
82-
{
83-
hostname: config.hostname,
84-
codeVersion: config.codeVersion,
85-
},
86-
resolver.getForWebDav()
87-
);
26+
const instance = this.createWebDavInstance()
8827

89-
// 4. Execute the operation
90-
this.log(`Deploying cartridges from ${args.cartridgePath}...`);
91-
this.log(`Target: ${config.hostname}`);
92-
this.log(`Code Version: ${config.codeVersion}`);
28+
this.log(`Deploying cartridges from ${this.args.cartridgePath}...`)
29+
this.log(`Target: ${this.resolvedConfig.hostname}`)
30+
this.log(`Code Version: ${this.resolvedConfig.codeVersion}`)
9331

9432
try {
95-
await uploadCartridges(instance, args.cartridgePath);
96-
this.log('Deployment complete');
33+
await uploadCartridges(instance, this.args.cartridgePath)
34+
this.log('Deployment complete')
9735
} catch (error) {
9836
if (error instanceof Error) {
99-
this.error(`Deployment failed: ${error.message}`);
37+
this.error(`Deployment failed: ${error.message}`)
10038
}
101-
throw error;
39+
throw error
10240
}
10341
}
10442
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Args, Flags } from '@oclif/core'
2+
import { MrtCommand } from '@salesforce/b2c-tooling/cli'
3+
4+
/**
5+
* Stub command demonstrating MrtCommand usage.
6+
* MRT operations use API key authentication.
7+
*/
8+
export default class MrtEnvVarSet extends MrtCommand<typeof MrtEnvVarSet> {
9+
static args = {
10+
key: Args.string({
11+
description: 'Environment variable name',
12+
required: true,
13+
}),
14+
value: Args.string({
15+
description: 'Environment variable value',
16+
required: true,
17+
}),
18+
}
19+
20+
static description = 'Set an environment variable on a Managed Runtime project'
21+
22+
static examples = [
23+
'<%= config.bin %> <%= command.id %> MY_VAR "my value" --project acme-storefront --environment production',
24+
]
25+
26+
static flags = {
27+
project: Flags.string({
28+
description: 'MRT project ID',
29+
required: true,
30+
}),
31+
environment: Flags.string({
32+
char: 'e',
33+
description: 'Target environment',
34+
required: true,
35+
}),
36+
}
37+
38+
async run(): Promise<void> {
39+
this.requireMrtCredentials()
40+
41+
const client = this.createMrtClient({
42+
org: 'default', // Would come from config
43+
project: this.flags.project,
44+
env: this.flags.environment,
45+
})
46+
47+
this.log(`Setting ${this.args.key} on ${this.flags.project}/${this.flags.environment}...`)
48+
49+
// TODO: Implement actual MRT API call
50+
// const response = await client.request(`projects/${this.flags.project}/envs/${this.flags.environment}/variables`, {
51+
// method: 'POST',
52+
// body: JSON.stringify({ key: this.args.key, value: this.args.value })
53+
// })
54+
55+
this.log('')
56+
this.log('(stub) Environment variable setting not yet implemented')
57+
this.log(`Would set ${this.args.key}=${this.args.value}`)
58+
this.log(`Project: ${this.flags.project}`)
59+
this.log(`Environment: ${this.flags.environment}`)
60+
}
61+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Args, Flags } from '@oclif/core'
2+
import { OAuthCommand } from '@salesforce/b2c-tooling/cli'
3+
4+
/**
5+
* Stub command demonstrating OAuthCommand usage.
6+
* Sandbox API operations require OAuth but not instance credentials.
7+
*/
8+
export default class SandboxCreate extends OAuthCommand<typeof SandboxCreate> {
9+
static args = {
10+
realm: Args.string({
11+
description: 'Realm ID',
12+
required: true,
13+
}),
14+
}
15+
16+
static description = 'Create a new on-demand sandbox'
17+
18+
static examples = [
19+
'<%= config.bin %> <%= command.id %> abcd --ttl 24',
20+
'<%= config.bin %> <%= command.id %> abcd --profile medium',
21+
]
22+
23+
static flags = {
24+
ttl: Flags.integer({
25+
description: 'Time to live in hours',
26+
default: 24,
27+
}),
28+
profile: Flags.string({
29+
description: 'Sandbox profile (small, medium, large)',
30+
default: 'medium',
31+
}),
32+
}
33+
34+
async run(): Promise<void> {
35+
this.requireOAuthCredentials()
36+
37+
const auth = this.getOAuthStrategy()
38+
39+
this.log(`Creating sandbox in realm ${this.args.realm}...`)
40+
this.log(`Profile: ${this.flags.profile}`)
41+
this.log(`TTL: ${this.flags.ttl} hours`)
42+
43+
// TODO: Implement actual ODS API call
44+
// const response = await auth.fetch(
45+
// `https://admin.dx.commercecloud.salesforce.com/api/v1/realms/${this.args.realm}/sandboxes`,
46+
// { method: 'POST', body: JSON.stringify({ ttl: this.flags.ttl, profile: this.flags.profile }) }
47+
// )
48+
49+
this.log('')
50+
this.log('(stub) Sandbox creation not yet implemented')
51+
this.log(`Would create sandbox with OAuth client: ${this.resolvedConfig.clientId}`)
52+
}
53+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { InstanceCommand } from '@salesforce/b2c-tooling/cli'
2+
3+
interface SitesResponse {
4+
_v: string
5+
count: number
6+
data: Array<{
7+
id: string
8+
display_name?: { default?: string }
9+
status?: string
10+
}>
11+
total: number
12+
}
13+
14+
export default class SitesList extends InstanceCommand<typeof SitesList> {
15+
static description = 'List sites on a B2C Commerce instance'
16+
17+
static examples = [
18+
'<%= config.bin %> <%= command.id %>',
19+
'<%= config.bin %> <%= command.id %> --server my-sandbox.demandware.net',
20+
]
21+
22+
async run(): Promise<void> {
23+
this.requireServer()
24+
this.requireOAuthCredentials()
25+
26+
const instance = this.createApiInstance()
27+
28+
this.log(`Fetching sites from ${this.resolvedConfig.hostname}...`)
29+
30+
try {
31+
const response = await instance.ocapiDataRequest('sites?select=(**)')
32+
33+
if (!response.ok) {
34+
const errorText = await response.text()
35+
this.error(`Failed to fetch sites: ${response.status} ${response.statusText}\n${errorText}`)
36+
}
37+
38+
const data = (await response.json()) as SitesResponse
39+
40+
if (data.count === 0) {
41+
this.log('No sites found.')
42+
return
43+
}
44+
45+
this.log(`\nFound ${data.count} site(s):\n`)
46+
47+
for (const site of data.data) {
48+
const displayName = site.display_name?.default || site.id
49+
const status = site.status || 'unknown'
50+
this.log(` ${site.id}`)
51+
this.log(` Display Name: ${displayName}`)
52+
this.log(` Status: ${status}`)
53+
this.log('')
54+
}
55+
} catch (error) {
56+
if (error instanceof Error) {
57+
this.error(`Failed to fetch sites: ${error.message}`)
58+
}
59+
throw error
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)