Skip to content

Commit 89600c1

Browse files
authored
Fix linting warnings and test configuration issues (#44)
* Fix linting warnings and test configuration issues - Add explicit node_modules ignore to ESLint config to prevent parsing errors with marked-terminal in monorepo root - Fix TypeScript test configs to properly handle rootDir inheritance by setting rootDir to ".." and including both test and src files - Update MCP test tsconfig to extend parent for development exports - Add cliui type declaration to MCP package - Refactor job/run.ts to reduce complexity from 23 to ≤20 by extracting handleExecutionError, handleWaitError, and waitForJobCompletion methods - Refactor ods/info.ts to reduce complexity from 31 to ≤20 by extracting renderUserInfo, renderSystemInfo, renderField, and renderArrayField methods - Refactor slas/client/update.ts to reduce complexity from 31 to ≤20 by extracting normalization and merge helper methods * shared cliui tempporarily; shared types dir
1 parent 44e88ad commit 89600c1

14 files changed

Lines changed: 248 additions & 229 deletions

File tree

packages/b2c-cli/eslint.config.mjs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@ const gitignorePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)),
1515
headerPlugin.rules.header.meta.schema = false;
1616

1717
export default [
18-
includeIgnoreFile(gitignorePath),
18+
// Global ignores must come first - these patterns apply to all subsequent configs
19+
// node_modules must be explicitly ignored because the .gitignore pattern only covers
20+
// packages/b2c-cli/node_modules, not the monorepo root node_modules
1921
{
20-
ignores: ['test/functional/fixtures/**/*.js'],
22+
ignores: ['**/node_modules/**', 'test/functional/fixtures/**/*.js'],
2123
},
24+
includeIgnoreFile(gitignorePath),
2225
...oclif,
2326
prettierPlugin,
2427
{

packages/b2c-cli/src/commands/job/run.ts

Lines changed: 91 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
55
*/
66
import {Args, Flags} from '@oclif/core';
7-
import {JobCommand} from '@salesforce/b2c-tooling-sdk/cli';
7+
import {JobCommand, type B2COperationContext} from '@salesforce/b2c-tooling-sdk/cli';
88
import {
99
executeJob,
1010
waitForJob,
@@ -112,19 +112,7 @@ export default class JobRun extends JobCommand<typeof JobRun> {
112112
waitForRunning: !noWaitRunning,
113113
});
114114
} catch (error) {
115-
// Run afterOperation hooks with failure
116-
await this.runAfterHooks(context, {
117-
success: false,
118-
error: error instanceof Error ? error : new Error(String(error)),
119-
duration: Date.now() - context.startTime,
120-
});
121-
122-
if (error instanceof Error) {
123-
this.error(
124-
t('commands.job.run.executionFailed', 'Failed to execute job: {{message}}', {message: error.message}),
125-
);
126-
}
127-
throw error;
115+
this.handleExecutionError(error, context);
128116
}
129117

130118
this.log(
@@ -136,59 +124,13 @@ export default class JobRun extends JobCommand<typeof JobRun> {
136124

137125
// Wait for completion if requested
138126
if (wait) {
139-
this.log(t('commands.job.run.waiting', 'Waiting for job to complete...'));
140-
141-
try {
142-
execution = await waitForJob(this.instance, jobId, execution.id!, {
143-
timeout: timeout ? timeout * 1000 : undefined,
144-
onProgress: (exec, elapsed) => {
145-
if (!this.jsonEnabled()) {
146-
const elapsedSec = Math.floor(elapsed / 1000);
147-
this.log(
148-
t('commands.job.run.progress', ' Status: {{status}} ({{elapsed}}s elapsed)', {
149-
status: exec.execution_status,
150-
elapsed: elapsedSec.toString(),
151-
}),
152-
);
153-
}
154-
},
155-
});
156-
157-
const durationSec = execution.duration ? (execution.duration / 1000).toFixed(1) : 'N/A';
158-
this.log(
159-
t('commands.job.run.completed', 'Job completed: {{status}} (duration: {{duration}}s)', {
160-
status: execution.exit_status?.code || execution.execution_status,
161-
duration: durationSec,
162-
}),
163-
);
164-
165-
// Run afterOperation hooks with success
166-
await this.runAfterHooks(context, {
167-
success: true,
168-
duration: Date.now() - context.startTime,
169-
data: execution,
170-
});
171-
} catch (error) {
172-
// Run afterOperation hooks with failure
173-
await this.runAfterHooks(context, {
174-
success: false,
175-
error: error instanceof Error ? error : new Error(String(error)),
176-
duration: Date.now() - context.startTime,
177-
data: error instanceof JobExecutionError ? error.execution : undefined,
178-
});
179-
180-
if (error instanceof JobExecutionError) {
181-
if (showLog) {
182-
await this.showJobLog(error.execution);
183-
}
184-
this.error(
185-
t('commands.job.run.jobFailed', 'Job failed: {{status}}', {
186-
status: error.execution.exit_status?.code || 'ERROR',
187-
}),
188-
);
189-
}
190-
throw error;
191-
}
127+
execution = await this.waitForJobCompletion({
128+
jobId,
129+
executionId: execution.id!,
130+
timeout,
131+
showLog,
132+
context,
133+
});
192134
} else {
193135
// Not waiting - run afterOperation hooks with current state
194136
await this.runAfterHooks(context, {
@@ -198,12 +140,43 @@ export default class JobRun extends JobCommand<typeof JobRun> {
198140
});
199141
}
200142

201-
// JSON output handled by oclif
202-
if (this.jsonEnabled()) {
203-
return execution;
143+
return execution;
144+
}
145+
146+
private handleExecutionError(error: unknown, context: B2COperationContext): never {
147+
// Run afterOperation hooks with failure (fire-and-forget, errors ignored)
148+
this.runAfterHooks(context, {
149+
success: false,
150+
error: error instanceof Error ? error : new Error(String(error)),
151+
duration: Date.now() - context.startTime,
152+
}).catch(() => {});
153+
154+
if (error instanceof Error) {
155+
this.error(t('commands.job.run.executionFailed', 'Failed to execute job: {{message}}', {message: error.message}));
204156
}
157+
throw error;
158+
}
205159

206-
return execution;
160+
private async handleWaitError(error: unknown, showLog: boolean, context: B2COperationContext): Promise<never> {
161+
// Run afterOperation hooks with failure
162+
await this.runAfterHooks(context, {
163+
success: false,
164+
error: error instanceof Error ? error : new Error(String(error)),
165+
duration: Date.now() - context.startTime,
166+
data: error instanceof JobExecutionError ? error.execution : undefined,
167+
});
168+
169+
if (error instanceof JobExecutionError) {
170+
if (showLog) {
171+
await this.showJobLog(error.execution);
172+
}
173+
this.error(
174+
t('commands.job.run.jobFailed', 'Job failed: {{status}}', {
175+
status: error.execution.exit_status?.code || 'ERROR',
176+
}),
177+
);
178+
}
179+
throw error;
207180
}
208181

209182
private parseBody(body: string): Record<string, unknown> {
@@ -228,4 +201,51 @@ export default class JobRun extends JobCommand<typeof JobRun> {
228201
};
229202
});
230203
}
204+
205+
private async waitForJobCompletion(options: {
206+
jobId: string;
207+
executionId: string;
208+
timeout: number | undefined;
209+
showLog: boolean;
210+
context: B2COperationContext;
211+
}): Promise<JobExecution> {
212+
const {jobId, executionId, timeout, showLog, context} = options;
213+
this.log(t('commands.job.run.waiting', 'Waiting for job to complete...'));
214+
215+
try {
216+
const execution = await waitForJob(this.instance, jobId, executionId, {
217+
timeout: timeout ? timeout * 1000 : undefined,
218+
onProgress: (exec, elapsed) => {
219+
if (!this.jsonEnabled()) {
220+
const elapsedSec = Math.floor(elapsed / 1000);
221+
this.log(
222+
t('commands.job.run.progress', ' Status: {{status}} ({{elapsed}}s elapsed)', {
223+
status: exec.execution_status,
224+
elapsed: elapsedSec.toString(),
225+
}),
226+
);
227+
}
228+
},
229+
});
230+
231+
const durationSec = execution.duration ? (execution.duration / 1000).toFixed(1) : 'N/A';
232+
this.log(
233+
t('commands.job.run.completed', 'Job completed: {{status}} (duration: {{duration}}s)', {
234+
status: execution.exit_status?.code || execution.execution_status,
235+
duration: durationSec,
236+
}),
237+
);
238+
239+
// Run afterOperation hooks with success
240+
await this.runAfterHooks(context, {
241+
success: true,
242+
duration: Date.now() - context.startTime,
243+
data: execution,
244+
});
245+
246+
return execution;
247+
} catch (error) {
248+
return this.handleWaitError(error, showLog, context);
249+
}
250+
}
231251
}

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

Lines changed: 44 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -84,83 +84,65 @@ export default class OdsInfo extends OdsCommand<typeof OdsInfo> {
8484
// User Info Section
8585
ui.div({text: 'User Information', padding: [1, 0, 0, 0]});
8686
ui.div({text: '─'.repeat(40), padding: [0, 0, 0, 0]});
87+
this.renderUserInfo(ui, info.user);
8788

88-
if (info.user?.user) {
89-
ui.div(
90-
{text: 'Name:', width: 20, padding: [0, 2, 0, 0]},
91-
{text: info.user.user.name || '-', padding: [0, 0, 0, 0]},
92-
);
93-
ui.div(
94-
{text: 'Email:', width: 20, padding: [0, 2, 0, 0]},
95-
{text: info.user.user.email || '-', padding: [0, 0, 0, 0]},
96-
);
97-
ui.div(
98-
{text: 'User ID:', width: 20, padding: [0, 2, 0, 0]},
99-
{text: info.user.user.id || '-', padding: [0, 0, 0, 0]},
100-
);
101-
}
89+
// System Info Section
90+
ui.div({text: '', padding: [0, 0, 0, 0]});
91+
ui.div({text: 'System Information', padding: [1, 0, 0, 0]});
92+
ui.div({text: '─'.repeat(40), padding: [0, 0, 0, 0]});
93+
this.renderSystemInfo(ui, info.system);
10294

103-
if (info.user?.client) {
104-
ui.div(
105-
{text: 'Client ID:', width: 20, padding: [0, 2, 0, 0]},
106-
{text: info.user.client.id || '-', padding: [0, 0, 0, 0]},
107-
);
108-
}
95+
ux.stdout(ui.toString());
96+
}
10997

110-
if (info.user?.roles && info.user.roles.length > 0) {
111-
ui.div(
112-
{text: 'Roles:', width: 20, padding: [0, 2, 0, 0]},
113-
{text: info.user.roles.join(', '), padding: [0, 0, 0, 0]},
114-
);
98+
private renderArrayField(ui: ReturnType<typeof cliui>, label: string, values: string[] | undefined): void {
99+
if (values && values.length > 0) {
100+
this.renderField(ui, label, values.join(', '));
115101
}
102+
}
116103

117-
if (info.user?.realms && info.user.realms.length > 0) {
118-
ui.div(
119-
{text: 'Realms:', width: 20, padding: [0, 2, 0, 0]},
120-
{text: info.user.realms.join(', '), padding: [0, 0, 0, 0]},
121-
);
122-
}
104+
private renderField(ui: ReturnType<typeof cliui>, label: string, value: string): void {
105+
ui.div({text: label, width: 20, padding: [0, 2, 0, 0]}, {text: value, padding: [0, 0, 0, 0]});
106+
}
123107

124-
if (info.user?.sandboxes && info.user.sandboxes.length > 0) {
125-
ui.div(
126-
{text: 'Sandboxes:', width: 20, padding: [0, 2, 0, 0]},
127-
{text: info.user.sandboxes.length.toString(), padding: [0, 0, 0, 0]},
128-
);
108+
private renderSystemInfo(ui: ReturnType<typeof cliui>, system: SystemInfoSpec | undefined): void {
109+
if (!system) return;
110+
111+
if (system.region) {
112+
this.renderField(ui, 'Region:', system.region);
129113
}
130114

131-
// System Info Section
132-
ui.div({text: '', padding: [0, 0, 0, 0]});
133-
ui.div({text: 'System Information', padding: [1, 0, 0, 0]});
134-
ui.div({text: '─'.repeat(40), padding: [0, 0, 0, 0]});
115+
this.renderArrayField(ui, 'Inbound IPs:', system.inboundIps);
116+
this.renderArrayField(ui, 'Outbound IPs:', system.outboundIps);
135117

136-
if (info.system?.region) {
137-
ui.div({text: 'Region:', width: 20, padding: [0, 2, 0, 0]}, {text: info.system.region, padding: [0, 0, 0, 0]});
118+
// Sandbox IPs with truncation
119+
if (system.sandboxIps && system.sandboxIps.length > 0) {
120+
const truncated = system.sandboxIps.length > 5;
121+
const displayValue = system.sandboxIps.slice(0, 5).join(', ') + (truncated ? '...' : '');
122+
this.renderField(ui, 'Sandbox IPs:', displayValue);
138123
}
124+
}
139125

140-
if (info.system?.inboundIps && info.system.inboundIps.length > 0) {
141-
ui.div(
142-
{text: 'Inbound IPs:', width: 20, padding: [0, 2, 0, 0]},
143-
{text: info.system.inboundIps.join(', '), padding: [0, 0, 0, 0]},
144-
);
145-
}
126+
private renderUserInfo(ui: ReturnType<typeof cliui>, user: undefined | UserInfoSpec): void {
127+
if (!user) return;
146128

147-
if (info.system?.outboundIps && info.system.outboundIps.length > 0) {
148-
ui.div(
149-
{text: 'Outbound IPs:', width: 20, padding: [0, 2, 0, 0]},
150-
{text: info.system.outboundIps.join(', '), padding: [0, 0, 0, 0]},
151-
);
129+
// User details
130+
if (user.user) {
131+
this.renderField(ui, 'Name:', user.user.name || '-');
132+
this.renderField(ui, 'Email:', user.user.email || '-');
133+
this.renderField(ui, 'User ID:', user.user.id || '-');
152134
}
153135

154-
if (info.system?.sandboxIps && info.system.sandboxIps.length > 0) {
155-
ui.div(
156-
{text: 'Sandbox IPs:', width: 20, padding: [0, 2, 0, 0]},
157-
{
158-
text: info.system.sandboxIps.slice(0, 5).join(', ') + (info.system.sandboxIps.length > 5 ? '...' : ''),
159-
padding: [0, 0, 0, 0],
160-
},
161-
);
136+
// Client info
137+
if (user.client) {
138+
this.renderField(ui, 'Client ID:', user.client.id || '-');
162139
}
163140

164-
ux.stdout(ui.toString());
141+
// Arrays with length checks
142+
this.renderArrayField(ui, 'Roles:', user.roles);
143+
this.renderArrayField(ui, 'Realms:', user.realms);
144+
if (user.sandboxes && user.sandboxes.length > 0) {
145+
this.renderField(ui, 'Sandboxes:', user.sandboxes.length.toString());
146+
}
165147
}
166148
}

0 commit comments

Comments
 (0)