Skip to content

Commit 5135ac5

Browse files
committed
filtering and extra params support
1 parent 9094377 commit 5135ac5

9 files changed

Lines changed: 186 additions & 24 deletions

File tree

packages/b2c-cli/src/commands/ods/create.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Args, Flags, ux} from '@oclif/core';
1+
import {Flags, ux} from '@oclif/core';
22
import cliui from 'cliui';
33
import {OdsCommand} from '@salesforce/b2c-tooling/cli';
44
import type {OdsComponents} from '@salesforce/b2c-tooling';
@@ -11,26 +11,24 @@ type SandboxResourceProfile = OdsComponents['schemas']['SandboxResourceProfile']
1111
* Command to create a new on-demand sandbox.
1212
*/
1313
export default class OdsCreate extends OdsCommand<typeof OdsCreate> {
14-
static args = {
15-
realm: Args.string({
16-
description: 'Realm ID (four-letter ID)',
17-
required: true,
18-
}),
19-
};
20-
2114
static description = t('commands.ods.create.description', 'Create a new on-demand sandbox');
2215

2316
static enableJsonFlag = true;
2417

2518
static examples = [
26-
'<%= config.bin %> <%= command.id %> abcd',
27-
'<%= config.bin %> <%= command.id %> abcd --ttl 48',
28-
'<%= config.bin %> <%= command.id %> abcd --profile large',
29-
'<%= config.bin %> <%= command.id %> abcd --auto-scheduled',
30-
'<%= config.bin %> <%= command.id %> abcd --json',
19+
'<%= config.bin %> <%= command.id %> --realm abcd',
20+
'<%= config.bin %> <%= command.id %> --realm abcd --ttl 48',
21+
'<%= config.bin %> <%= command.id %> --realm abcd --profile large',
22+
'<%= config.bin %> <%= command.id %> --realm abcd --auto-scheduled',
23+
'<%= config.bin %> <%= command.id %> --realm abcd --json',
3124
];
3225

3326
static flags = {
27+
realm: Flags.string({
28+
char: 'r',
29+
description: 'Realm ID (four-letter ID)',
30+
required: true,
31+
}),
3432
ttl: Flags.integer({
3533
description: 'Time to live in hours (0 for infinite)',
3634
default: 24,
@@ -47,7 +45,7 @@ export default class OdsCreate extends OdsCommand<typeof OdsCreate> {
4745
};
4846

4947
async run(): Promise<SandboxModel> {
50-
const realm = this.args.realm;
48+
const realm = this.flags.realm;
5149
const profile = this.flags.profile as SandboxResourceProfile;
5250
const ttl = this.flags.ttl;
5351
const autoScheduled = this.flags['auto-scheduled'];

packages/b2c-tooling/src/cli/base-command.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {loadConfig} from './config.js';
33
import type {ResolvedConfig, LoadConfigOptions} from './config.js';
44
import {setLanguage} from '../i18n/index.js';
55
import {configureLogger, getLogger, type LogLevel, type Logger} from '../logging/index.js';
6+
import type {ExtraParamsConfig} from '../clients/middleware.js';
67

78
export type Flags<T extends typeof Command> = Interfaces.InferredFlags<(typeof BaseCommand)['baseFlags'] & T['flags']>;
89
export type Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>;
@@ -54,6 +55,16 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
5455
env: 'SFCC_INSTANCE',
5556
helpGroup: 'GLOBAL',
5657
}),
58+
'extra-query': Flags.string({
59+
description: 'Extra query parameters as JSON (e.g., \'{"debug":"true"}\')',
60+
helpGroup: 'GLOBAL',
61+
hidden: true,
62+
}),
63+
'extra-body': Flags.string({
64+
description: 'Extra body fields to merge as JSON (e.g., \'{"_internal":true}\')',
65+
helpGroup: 'GLOBAL',
66+
hidden: true,
67+
}),
5768
};
5869

5970
protected flags!: Flags<T>;
@@ -164,4 +175,39 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
164175
// Use oclif's error() for proper exit code and display
165176
this.error(err.message, {exit: err.exitCode ?? 1});
166177
}
178+
179+
/**
180+
* Parse extra params from --extra-query and --extra-body flags.
181+
* Returns undefined if no extra params are specified.
182+
*
183+
* @returns ExtraParamsConfig or undefined
184+
*/
185+
protected getExtraParams(): ExtraParamsConfig | undefined {
186+
const extraQuery = this.flags['extra-query'];
187+
const extraBody = this.flags['extra-body'];
188+
189+
if (!extraQuery && !extraBody) {
190+
return undefined;
191+
}
192+
193+
const config: ExtraParamsConfig = {};
194+
195+
if (extraQuery) {
196+
try {
197+
config.query = JSON.parse(extraQuery) as Record<string, string | number | boolean | undefined>;
198+
} catch {
199+
this.error(`Invalid JSON for --extra-query: ${extraQuery}`);
200+
}
201+
}
202+
203+
if (extraBody) {
204+
try {
205+
config.body = JSON.parse(extraBody) as Record<string, unknown>;
206+
} catch {
207+
this.error(`Invalid JSON for --extra-body: ${extraBody}`);
208+
}
209+
}
210+
211+
return config;
212+
}
167213
}

packages/b2c-tooling/src/cli/ods-command.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ const DEFAULT_ODS_HOST = 'admin.dx.commercecloud.salesforce.com';
3131
export abstract class OdsCommand<T extends typeof Command> extends OAuthCommand<T> {
3232
static baseFlags = {
3333
...OAuthCommand.baseFlags,
34-
host: Flags.string({
34+
'sandbox-api-host': Flags.string({
3535
description: 'ODS API hostname',
3636
env: 'SFCC_SANDBOX_API_HOST',
3737
default: DEFAULT_ODS_HOST,
38-
helpGroup: 'ODS',
38+
// helpGroup: 'ODS',
3939
}),
4040
};
4141

@@ -64,7 +64,13 @@ export abstract class OdsCommand<T extends typeof Command> extends OAuthCommand<
6464
if (!this._odsClient) {
6565
this.requireOAuthCredentials();
6666
const authStrategy = this.getOAuthStrategy();
67-
this._odsClient = createOdsClient({host: this.odsHost}, authStrategy);
67+
this._odsClient = createOdsClient(
68+
{
69+
host: this.odsHost,
70+
extraParams: this.getExtraParams(),
71+
},
72+
authStrategy,
73+
);
6874
}
6975
return this._odsClient;
7076
}

packages/b2c-tooling/src/clients/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@
8787
* baseUrl: `https://${config.host}/api/v1`,
8888
* });
8989
*
90-
* // Add middleware - use a short identifier for log prefixes
91-
* client.use(createLoggingMiddleware('MYAPI'));
90+
* // Add middleware - auth first, logging last (so logging sees complete request)
9291
* client.use(createAuthMiddleware(auth));
92+
* client.use(createLoggingMiddleware('MYAPI'));
9393
*
9494
* return client;
9595
* }
@@ -109,7 +109,8 @@
109109
export {WebDavClient} from './webdav.js';
110110
export type {PropfindEntry} from './webdav.js';
111111

112-
export {createAuthMiddleware, createLoggingMiddleware} from './middleware.js';
112+
export {createAuthMiddleware, createLoggingMiddleware, createExtraParamsMiddleware} from './middleware.js';
113+
export type {ExtraParamsConfig} from './middleware.js';
113114

114115
export {createOcapiClient} from './ocapi.js';
115116
export type {

packages/b2c-tooling/src/clients/middleware.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ import type {Middleware} from 'openapi-fetch';
1010
import type {AuthStrategy} from '../auth/types.js';
1111
import {getLogger} from '../logging/logger.js';
1212

13+
/**
14+
* Configuration for extra parameters middleware.
15+
*/
16+
export interface ExtraParamsConfig {
17+
/** Extra query parameters to add to the URL */
18+
query?: Record<string, string | number | boolean | undefined>;
19+
/** Extra body fields to merge into JSON request bodies */
20+
body?: Record<string, unknown>;
21+
}
22+
1323
/**
1424
* Converts Headers to a plain object for logging.
1525
*/
@@ -107,3 +117,87 @@ export function createLoggingMiddleware(prefix?: string): Middleware {
107117
},
108118
};
109119
}
120+
121+
/**
122+
* Creates middleware that adds extra query parameters and/or body fields to requests.
123+
*
124+
* This is useful for internal/power-user scenarios where you need to pass
125+
* parameters that aren't in the typed OpenAPI schema.
126+
*
127+
* @param config - Configuration with extra query and/or body params
128+
* @returns Middleware that adds extra params to requests
129+
*
130+
* @example
131+
* ```typescript
132+
* const client = createOdsClient(config, auth);
133+
* client.use(createExtraParamsMiddleware({
134+
* query: { debug: 'true', internal_flag: '1' },
135+
* body: { _internal: { trace: true } }
136+
* }));
137+
* ```
138+
*/
139+
export function createExtraParamsMiddleware(config: ExtraParamsConfig): Middleware {
140+
const logger = getLogger();
141+
142+
return {
143+
async onRequest({request}) {
144+
let modifiedRequest = request;
145+
146+
// Add extra query parameters
147+
if (config.query && Object.keys(config.query).length > 0) {
148+
const url = new URL(request.url);
149+
for (const [key, value] of Object.entries(config.query)) {
150+
if (value !== undefined) {
151+
url.searchParams.set(key, String(value));
152+
}
153+
}
154+
logger.trace(
155+
{extraQuery: config.query, originalUrl: request.url, newUrl: url.toString()},
156+
'[ExtraParams] Adding extra query params to URL',
157+
);
158+
modifiedRequest = new Request(url.toString(), {
159+
method: request.method,
160+
headers: request.headers,
161+
body: request.body,
162+
duplex: request.body ? 'half' : undefined,
163+
} as RequestInit);
164+
}
165+
166+
// Merge extra body fields for JSON requests
167+
if (config.body && Object.keys(config.body).length > 0) {
168+
const contentType = modifiedRequest.headers.get('content-type');
169+
if (contentType?.includes('application/json') && modifiedRequest.body) {
170+
const clonedRequest = modifiedRequest.clone();
171+
const originalBody = await clonedRequest.text();
172+
try {
173+
const parsedBody = JSON.parse(originalBody) as Record<string, unknown>;
174+
const mergedBody = {...parsedBody, ...config.body};
175+
logger.trace(
176+
{originalBody: parsedBody, extraBody: config.body, mergedBody},
177+
'[ExtraParams] Merging extra body fields into request',
178+
);
179+
modifiedRequest = new Request(modifiedRequest.url, {
180+
method: modifiedRequest.method,
181+
headers: modifiedRequest.headers,
182+
body: JSON.stringify(mergedBody),
183+
});
184+
} catch {
185+
logger.warn('[ExtraParams] Could not parse request body as JSON, skipping body merge');
186+
}
187+
} else if (!modifiedRequest.body) {
188+
// No existing body, create one with extra fields
189+
logger.trace({body: config.body}, '[ExtraParams] Creating new body with extra fields');
190+
const headers = new Headers(modifiedRequest.headers);
191+
headers.set('content-type', 'application/json');
192+
modifiedRequest = new Request(modifiedRequest.url, {
193+
method: modifiedRequest.method,
194+
headers,
195+
body: JSON.stringify(config.body),
196+
});
197+
}
198+
}
199+
200+
return modifiedRequest;
201+
},
202+
};
203+
}

packages/b2c-tooling/src/clients/ocapi.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@ export function createOcapiClient(
102102
baseUrl: `https://${hostname}/s/-/dw/data/${apiVersion}`,
103103
});
104104

105-
client.use(createLoggingMiddleware('OCAPI'));
105+
// Middleware order: auth → logging (logging sees fully modified request)
106106
client.use(createAuthMiddleware(auth));
107+
client.use(createLoggingMiddleware('OCAPI'));
107108

108109
return client;
109110
}

packages/b2c-tooling/src/clients/ods.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
import createClient, {type Client} from 'openapi-fetch';
1212
import type {AuthStrategy} from '../auth/types.js';
1313
import type {paths, components} from './ods.generated.js';
14-
import {createAuthMiddleware, createLoggingMiddleware} from './middleware.js';
14+
import {createAuthMiddleware, createLoggingMiddleware, createExtraParamsMiddleware} from './middleware.js';
15+
import type {ExtraParamsConfig} from './middleware.js';
1516

1617
/**
1718
* Default ODS API host for US region.
@@ -62,6 +63,13 @@ export interface OdsClientConfig {
6263
* @example "admin.dx.commercecloud.salesforce.com"
6364
*/
6465
host?: string;
66+
67+
/**
68+
* Extra parameters to add to all requests.
69+
* Useful for internal/power-user scenarios where you need to pass
70+
* parameters that aren't in the typed OpenAPI schema.
71+
*/
72+
extraParams?: ExtraParamsConfig;
6573
}
6674

6775
/**
@@ -132,8 +140,13 @@ export function createOdsClient(config: OdsClientConfig, auth: AuthStrategy): Od
132140
baseUrl: `https://${host}/api/v1`,
133141
});
134142

135-
client.use(createLoggingMiddleware('ODS'));
143+
// Middleware order: extraParams → auth → logging
144+
// This ensures logging sees the fully modified request (with auth headers and extra params)
145+
if (config.extraParams) {
146+
client.use(createExtraParamsMiddleware(config.extraParams));
147+
}
136148
client.use(createAuthMiddleware(auth));
149+
client.use(createLoggingMiddleware('ODS'));
137150

138151
return client;
139152
}

packages/b2c-tooling/src/clients/slas-admin.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,9 @@ export function createSlasClient(config: SlasClientConfig, auth: AuthStrategy):
103103
baseUrl: `https://${config.shortCode}.api.commercecloud.salesforce.com/shopper/auth-admin/v1`,
104104
});
105105

106-
client.use(createLoggingMiddleware('SLAS'));
106+
// Middleware order: auth → logging (logging sees fully modified request)
107107
client.use(createAuthMiddleware(auth));
108+
client.use(createLoggingMiddleware('SLAS'));
108109

109110
return client;
110111
}

packages/b2c-tooling/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ export {
5050
WebDavClient,
5151
createOcapiClient,
5252
createAuthMiddleware,
53+
createExtraParamsMiddleware,
5354
createSlasClient,
5455
createOdsClient,
5556
} from './clients/index.js';
5657
export type {
5758
PropfindEntry,
59+
ExtraParamsConfig,
5860
OcapiClient,
5961
OcapiError,
6062
OcapiResponse,

0 commit comments

Comments
 (0)