Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions src/errors/CliError.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,63 @@
import { StepZenError } from './StepZenError';

/**
* Structured error data extracted from StepZen CLI output
*/
export interface StepZenCliErrorDetails {
/** The original error message */
originalMessage: string;
/** The type of error (authorization, graphql, etc.) */
errorType?: string;
/** Specific error details for GraphQL errors */
graphqlErrors?: string[];
/** Authorization error details */
authError?: string;
}

/**
* Error class for CLI-related errors
* Represents errors that occur when interacting with the StepZen CLI
*/
export class CliError extends StepZenError {
/** Structured error details extracted from CLI output */
public details?: StepZenCliErrorDetails;

/**
* Creates a new CLI error
*
* @param message The error message
* @param code A unique code representing the specific error
* @param cause The underlying cause of this error (optional)
* @param details Structured error details extracted from CLI output (optional)
*/
constructor(message: string, code: string = 'CLI_ERROR', cause?: unknown) {
constructor(message: string, code: string = 'CLI_ERROR', cause?: unknown, details?: StepZenCliErrorDetails) {
super(message, code, cause);
this.name = 'CliError';
this.details = details;
}
}

/**
* Creates a string representation of the error including details if available
*/
public toString(): string {
let result = `${this.name}[${this.code}]: ${this.message}`;

if (this.details) {
if (this.details.errorType) {
result += `\nError Type: ${this.details.errorType}`;
}

if (this.details.authError) {
result += `\nAuthorization Error: ${this.details.authError}`;
}

if (this.details.graphqlErrors && this.details.graphqlErrors.length > 0) {
result += `\nGraphQL Errors:\n${this.details.graphqlErrors.map(err => `- ${err}`).join('\n')}`;
}
}

return result;
}
}

// Made with Bob
57 changes: 55 additions & 2 deletions src/errors/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ export function handleError(error: unknown): StepZenError {
// Step 2: Log the full error with stack trace
logger.error(`${normalizedError.name}[${normalizedError.code}]: ${normalizedError.message}`, normalizedError);

// For CLI errors with details, log the structured information
if (normalizedError instanceof CliError && normalizedError.details) {
if (normalizedError.details.errorType === 'authorization') {
logger.error(`Authorization Error: ${normalizedError.details.authError || 'Access denied'}`);
} else if (normalizedError.details.errorType === 'graphql' && normalizedError.details.graphqlErrors) {
logger.error('GraphQL Errors:');
normalizedError.details.graphqlErrors.forEach(err => {
logger.error(`- ${err}`);
});
}
}

// Step 3: Show VS Code notification with friendly message
showErrorNotification(normalizedError);

Expand Down Expand Up @@ -109,10 +121,16 @@ function showErrorNotification(error: StepZenError): void {
// Generate a user-friendly message based on error type
let friendlyMessage = getFriendlyErrorMessage(error);

// For CLI errors with details, add more context to the notification
let detailMessage = error.message;
if (error instanceof CliError && error.details) {
detailMessage = getDetailedErrorMessage(error);
}

// Show notification with "Show Logs" action
vscode.window.showErrorMessage(
friendlyMessage,
{ modal: false, detail: error.message },
{ modal: false, detail: detailMessage },
'Show Logs'
).then(selection => {
if (selection === 'Show Logs') {
Expand All @@ -121,6 +139,30 @@ function showErrorNotification(error: StepZenError): void {
});
}

/**
* Get a detailed error message for CLI errors with structured details
*
* @param error The CliError with details
* @returns A detailed error message
*/
function getDetailedErrorMessage(error: CliError): string {
if (!error.details) {
return error.message;
}

let detailMessage = error.message;

// Add specific details based on error type
if (error.details.errorType === 'authorization') {
detailMessage = `Authorization Error: ${error.details.authError || 'Access denied'}`;
}
else if (error.details.errorType === 'graphql' && error.details.graphqlErrors) {
detailMessage = 'GraphQL Errors:\n' + error.details.graphqlErrors.map(err => `- ${err}`).join('\n');
}

return detailMessage;
}

/**
* Get a user-friendly error message based on error type
*
Expand All @@ -130,6 +172,15 @@ function showErrorNotification(error: StepZenError): void {
function getFriendlyErrorMessage(error: StepZenError): string {
// Customize message based on error type
if (error instanceof CliError) {
// For CLI errors with details, provide more specific messages
if (error.details) {
if (error.details.errorType === 'authorization') {
return `StepZen Authorization Error: ${error.details.authError || 'Access denied'}`;
}
else if (error.details.errorType === 'graphql') {
return 'StepZen GraphQL Error: The request contains errors';
}
}
return `StepZen CLI Error: ${error.message}`;
}

Expand All @@ -143,4 +194,6 @@ function getFriendlyErrorMessage(error: StepZenError): string {

// Default message for base StepZenError
return `StepZen Error: ${error.message}`;
}
}

// Made with Bob
68 changes: 60 additions & 8 deletions src/services/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';
import { logger } from './logger';
import { CliError } from '../errors';
import { CliError, StepZenCliErrorDetails } from '../errors';
import { TEMP_FILE_PATTERNS, TIMEOUTS } from "../utils/constants";

/**
Expand Down Expand Up @@ -289,6 +289,50 @@ export class StepzenCliService {
}
}

/**
* Parse StepZen CLI error output to extract structured error information
*
* @param stderr The stderr output from the CLI
* @returns Structured error details
*/
private parseCliErrorOutput(stderr: string): StepZenCliErrorDetails {
const details: StepZenCliErrorDetails = {
originalMessage: stderr
};

// Check for authorization errors
if (stderr.includes('Action denied: You are not authorized')) {
details.errorType = 'authorization';
// Extract the specific auth error message
const authMatch = stderr.match(/Action denied: ([^\n]+)/);
if (authMatch && authMatch[1]) {
details.authError = authMatch[1].trim();
}
}
// Check for GraphQL errors
else if (stderr.includes('Errors returned by the GraphQL server:')) {
details.errorType = 'graphql';
details.graphqlErrors = [];

// Extract GraphQL errors - they typically appear after this line
const lines = stderr.split('\n');
let collectingErrors = false;

for (const line of lines) {
const trimmedLine = line.trim();
if (collectingErrors && trimmedLine.startsWith('-')) {
// This is a GraphQL error line, extract the error message
details.graphqlErrors.push(trimmedLine.substring(1).trim());
}

if (trimmedLine.includes('Errors returned by the GraphQL server:')) {
collectingErrors = true;
}
}
}

return details;
}

/**
* Spawn a StepZen CLI process and capture output
Expand Down Expand Up @@ -350,15 +394,21 @@ export class StepzenCliService {

proc.on('close', (code) => {
if (code !== 0) {
// Create a more descriptive error with both exit code and stderr content
const errorMsg = stderr.trim()
? `StepZen CLI exited with code ${code}: ${stderr.trim()}`
: `StepZen CLI exited with code ${code}`;

// Parse the stderr to extract structured error information
const errorDetails = this.parseCliErrorOutput(stderr.trim());

// Create a more descriptive error message
let errorMsg = `StepZen CLI exited with code ${code}`;
if (stderr.trim()) {
errorMsg += `: ${stderr.trim()}`;
}

// Create a CliError with the structured error details
reject(new CliError(
errorMsg,
'COMMAND_FAILED',
stderr ? new Error(stderr) : undefined
stderr ? new Error(stderr) : undefined,
errorDetails
));
} else {
logger.debug(`StepZen CLI process completed with exit code 0`);
Expand All @@ -367,4 +417,6 @@ export class StepzenCliService {
});
});
}
}
}

// Made with Bob
Loading