Skip to content
Draft
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
15 changes: 10 additions & 5 deletions src/cli/aws/__tests__/transaction-search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ describe('enableTransactionSearch', () => {
const result = await enableTransactionSearch('us-east-1', '123456789012');

expect(result.success).toBe(false);
expect(result.error).toContain('Insufficient permissions to enable Application Signals');
// @ts-expect-error -- test accesses failure-branch field
expect(result.error.message).toContain('Insufficient permissions to enable Application Signals');
});

it('returns error when Application Signals fails with generic error', async () => {
Expand All @@ -172,7 +173,8 @@ describe('enableTransactionSearch', () => {
const result = await enableTransactionSearch('us-east-1', '123456789012');

expect(result.success).toBe(false);
expect(result.error).toContain('Failed to enable Application Signals');
// @ts-expect-error -- test accesses failure-branch field
expect(result.error.message).toContain('Failed to enable Application Signals');
});

it('returns error when CloudWatch Logs policy fails with AccessDenied', async () => {
Expand All @@ -184,7 +186,8 @@ describe('enableTransactionSearch', () => {
const result = await enableTransactionSearch('us-east-1', '123456789012');

expect(result.success).toBe(false);
expect(result.error).toContain('Insufficient permissions to configure CloudWatch Logs policy');
// @ts-expect-error -- test accesses failure-branch field
expect(result.error.message).toContain('Insufficient permissions to configure CloudWatch Logs policy');
});

it('returns error when trace destination fails', async () => {
Expand All @@ -195,7 +198,8 @@ describe('enableTransactionSearch', () => {
const result = await enableTransactionSearch('us-east-1', '123456789012');

expect(result.success).toBe(false);
expect(result.error).toContain('Failed to configure trace destination');
// @ts-expect-error -- test accesses failure-branch field
expect(result.error.message).toContain('Failed to configure trace destination');
});

it('returns error when indexing rule update fails', async () => {
Expand All @@ -215,7 +219,8 @@ describe('enableTransactionSearch', () => {
const result = await enableTransactionSearch('us-east-1', '123456789012');

expect(result.success).toBe(false);
expect(result.error).toContain('Failed to configure indexing rules');
// @ts-expect-error -- test accesses failure-branch field
expect(result.error.message).toContain('Failed to configure indexing rules');
});

it('does not proceed to later steps when an earlier step fails', async () => {
Expand Down
28 changes: 16 additions & 12 deletions src/cli/aws/transaction-search.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Result } from '../../lib/types';
import { getErrorMessage, isAccessDeniedError } from '../errors';
import { getCredentialProvider } from './account';
import { arnPrefix } from './partition';
Expand All @@ -14,10 +15,7 @@ import {
XRayClient,
} from '@aws-sdk/client-xray';

export interface TransactionSearchEnableResult {
success: boolean;
error?: string;
}
export type TransactionSearchEnableResult = Result;

const RESOURCE_POLICY_NAME = 'TransactionSearchXRayAccess';

Expand All @@ -44,9 +42,9 @@ export async function enableTransactionSearch(
} catch (err: unknown) {
const message = getErrorMessage(err);
if (isAccessDeniedError(err)) {
return { success: false, error: `Insufficient permissions to enable Application Signals: ${message}` };
return { success: false, error: new Error(`Insufficient permissions to enable Application Signals: ${message}`) };
}
return { success: false, error: `Failed to enable Application Signals: ${message}` };
return { success: false, error: new Error(`Failed to enable Application Signals: ${message}`) };
}

// Step 2: Create CloudWatch Logs resource policy for X-Ray (if needed)
Expand Down Expand Up @@ -80,9 +78,12 @@ export async function enableTransactionSearch(
} catch (err: unknown) {
const message = getErrorMessage(err);
if (isAccessDeniedError(err)) {
return { success: false, error: `Insufficient permissions to configure CloudWatch Logs policy: ${message}` };
return {
success: false,
error: new Error(`Insufficient permissions to configure CloudWatch Logs policy: ${message}`),
};
}
return { success: false, error: `Failed to configure CloudWatch Logs policy: ${message}` };
return { success: false, error: new Error(`Failed to configure CloudWatch Logs policy: ${message}`) };
}

const xrayClient = new XRayClient({ region, credentials });
Expand All @@ -96,9 +97,12 @@ export async function enableTransactionSearch(
} catch (err: unknown) {
const message = getErrorMessage(err);
if (isAccessDeniedError(err)) {
return { success: false, error: `Insufficient permissions to configure trace destination: ${message}` };
return {
success: false,
error: new Error(`Insufficient permissions to configure trace destination: ${message}`),
};
}
return { success: false, error: `Failed to configure trace destination: ${message}` };
return { success: false, error: new Error(`Failed to configure trace destination: ${message}`) };
}

// Step 4: Set indexing to 100% on the built-in Default rule (always exists, idempotent)
Expand All @@ -112,9 +116,9 @@ export async function enableTransactionSearch(
} catch (err: unknown) {
const message = getErrorMessage(err);
if (isAccessDeniedError(err)) {
return { success: false, error: `Insufficient permissions to configure indexing rules: ${message}` };
return { success: false, error: new Error(`Insufficient permissions to configure indexing rules: ${message}`) };
}
return { success: false, error: `Failed to configure indexing rules: ${message}` };
return { success: false, error: new Error(`Failed to configure indexing rules: ${message}`) };
}

return { success: true };
Expand Down
33 changes: 6 additions & 27 deletions src/cli/commands/add/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Result } from '../../../lib/types';
import type {
GatewayAuthorizerType,
ModelProvider,
Expand Down Expand Up @@ -41,12 +42,7 @@ export interface AddAgentOptions extends VpcOptions {
json?: boolean;
}

export interface AddAgentResult {
success: boolean;
agentName?: string;
agentPath?: string;
error?: string;
}
export type AddAgentResult = Result<{ agentName?: string; agentPath?: string }>;

// Gateway types
export interface AddGatewayOptions {
Expand All @@ -68,11 +64,7 @@ export interface AddGatewayOptions {
json?: boolean;
}

export interface AddGatewayResult {
success: boolean;
gatewayName?: string;
error?: string;
}
export type AddGatewayResult = Result<{ gatewayName?: string }>;

// Gateway Target types
export interface AddGatewayTargetOptions {
Expand Down Expand Up @@ -100,12 +92,7 @@ export interface AddGatewayTargetOptions {
json?: boolean;
}

export interface AddGatewayTargetResult {
success: boolean;
toolName?: string;
sourcePath?: string;
error?: string;
}
export type AddGatewayTargetResult = Result<{ toolName?: string; sourcePath?: string }>;

// Memory types (v2: no owner/user concept)
export interface AddMemoryOptions {
Expand All @@ -119,11 +106,7 @@ export interface AddMemoryOptions {
json?: boolean;
}

export interface AddMemoryResult {
success: boolean;
memoryName?: string;
error?: string;
}
export type AddMemoryResult = Result<{ memoryName?: string }>;

// Credential types (v2: credential, no owner/user concept)
export interface AddCredentialOptions {
Expand All @@ -140,8 +123,4 @@ export interface AddCredentialOptions {
/** @deprecated Use AddCredentialOptions */
export type AddIdentityOptions = AddCredentialOptions;

export interface AddCredentialResult {
success: boolean;
credentialName?: string;
error?: string;
}
export type AddCredentialResult = Result<{ credentialName?: string }>;
12 changes: 6 additions & 6 deletions src/cli/commands/create/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export async function createProject(options: CreateProjectOptions): Promise<Crea

// Fail on errors
if (!depCheck.passed) {
return { success: false, error: depCheck.errors.join('\n'), warnings: depWarnings };
return { success: false, error: new Error(depCheck.errors.join('\n')), warnings: depWarnings };
}
}

Expand Down Expand Up @@ -93,7 +93,7 @@ export async function createProject(options: CreateProjectOptions): Promise<Crea
const gitResult = await initGitRepo(projectRoot);
if (gitResult.status === 'error') {
onProgress?.('Initialize git repository', 'error');
return { success: false, error: gitResult.message, warnings: depWarnings };
return { success: false, error: new Error(gitResult.message), warnings: depWarnings };
}
onProgress?.('Initialize git repository', 'done');
}
Expand All @@ -104,7 +104,7 @@ export async function createProject(options: CreateProjectOptions): Promise<Crea
warnings: depWarnings.length > 0 ? depWarnings : undefined,
};
} catch (err) {
return { success: false, error: getErrorMessage(err), warnings: depWarnings };
return { success: false, error: new Error(getErrorMessage(err)), warnings: depWarnings };
}
}

Expand Down Expand Up @@ -174,7 +174,7 @@ export async function createProjectWithAgent(options: CreateWithAgentOptions): P

// Fail on errors
if (!depCheck.passed) {
return { success: false, error: depCheck.errors.join('\n'), warnings: depWarnings };
return { success: false, error: new Error(depCheck.errors.join('\n')), warnings: depWarnings };
}

// First create the base project (skip dependency check since we already did it)
Expand Down Expand Up @@ -217,7 +217,7 @@ export async function createProjectWithAgent(options: CreateWithAgentOptions): P
warnings: depWarnings.length > 0 ? depWarnings : undefined,
};
} catch (err) {
return { success: false, error: getErrorMessage(err), warnings: depWarnings };
return { success: false, error: new Error(getErrorMessage(err)), warnings: depWarnings };
}
}

Expand Down Expand Up @@ -310,7 +310,7 @@ export async function createProjectWithAgent(options: CreateWithAgentOptions): P
warnings: depWarnings.length > 0 ? depWarnings : undefined,
};
} catch (err) {
return { success: false, error: getErrorMessage(err), warnings: depWarnings };
return { success: false, error: new Error(getErrorMessage(err)), warnings: depWarnings };
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/cli/commands/create/command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ async function handleCreateCLI(options: CreateOptions): Promise<void> {
const result = getDryRunInfo({ name: name!, projectName, cwd, language: options.language });
if (options.json) {
console.log(JSON.stringify(result));
} else {
} else if (result.success) {
console.log('Dry run - would create:');
for (const path of result.wouldCreate ?? []) {
console.log(` ${path}`);
Expand Down Expand Up @@ -158,7 +158,7 @@ async function handleCreateCLI(options: CreateOptions): Promise<void> {
});

if (options.json) {
console.log(JSON.stringify(result));
console.log(JSON.stringify(result.success ? result : { ...result, error: result.error.message }));
} else if (result.success) {
printCreateSummary(projectName!, result.agentName, options.language, options.framework);
if (options.skipInstall) {
Expand All @@ -167,7 +167,7 @@ async function handleCreateCLI(options: CreateOptions): Promise<void> {
);
}
} else {
console.error(result.error);
console.error(result.error.message);
}

process.exit(result.success ? 0 : 1);
Expand Down
7 changes: 3 additions & 4 deletions src/cli/commands/create/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Result } from '../../../lib/types';
import type { VpcOptions } from '../shared/vpc-utils';

export interface CreateOptions extends VpcOptions {
Expand Down Expand Up @@ -28,12 +29,10 @@ export interface CreateOptions extends VpcOptions {
json?: boolean;
}

export interface CreateResult {
success: boolean;
export type CreateResult = Result<{
projectPath?: string;
agentName?: string;
error?: string;
dryRun?: boolean;
wouldCreate?: string[];
warnings?: string[];
}
}> & { warnings?: string[] };
25 changes: 13 additions & 12 deletions src/cli/commands/deploy/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise<Dep
logger.finalize(false);
return {
success: false,
error: `Target "${options.target}" not found in aws-targets.json`,
error: new Error(`Target "${options.target}" not found in aws-targets.json`),
logPath: logger.getRelativeLogPath(),
};
}
Expand Down Expand Up @@ -114,8 +114,9 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise<Dep
logger.finalize(false);
return {
success: false,
error:
'This will delete all deployed resources and the CloudFormation stack. Run with --yes to confirm teardown.',
error: new Error(
'This will delete all deployed resources and the CloudFormation stack. Run with --yes to confirm teardown.'
),
logPath: logger.getRelativeLogPath(),
};
}
Expand Down Expand Up @@ -169,7 +170,7 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise<Dep
errorResult?.error && typeof errorResult.error === 'string' ? errorResult.error : 'Identity setup failed';
endStep('error', errorMsg);
logger.finalize(false);
return { success: false, error: errorMsg, logPath: logger.getRelativeLogPath() };
return { success: false, error: new Error(errorMsg), logPath: logger.getRelativeLogPath() };
}
identityKmsKeyArn = identityResult.kmsKeyArn;

Expand Down Expand Up @@ -201,7 +202,7 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise<Dep
const errorMsg = 'OAuth credential setup failed. Check the log for details.';
endStep('error', errorMsg);
logger.finalize(false);
return { success: false, error: errorMsg, logPath: logger.getRelativeLogPath() };
return { success: false, error: new Error(errorMsg), logPath: logger.getRelativeLogPath() };
}

// Collect OAuth credential ARNs for deployed state
Expand Down Expand Up @@ -242,7 +243,7 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise<Dep
if (stackNames.length === 0) {
endStep('error', 'No stacks found');
logger.finalize(false);
return { success: false, error: 'No stacks found to deploy', logPath: logger.getRelativeLogPath() };
return { success: false, error: new Error('No stacks found to deploy'), logPath: logger.getRelativeLogPath() };
}
const stackName = stackNames[0]!;
endStep('success');
Expand All @@ -259,7 +260,7 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise<Dep
logger.finalize(false);
return {
success: false,
error: 'AWS environment needs bootstrapping. Run with --yes to auto-bootstrap.',
error: new Error('AWS environment needs bootstrapping. Run with --yes to auto-bootstrap.'),
logPath: logger.getRelativeLogPath(),
};
}
Expand All @@ -274,7 +275,7 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise<Dep
logger.finalize(false);
return {
success: false,
error: deployabilityCheck.message ?? 'Stack is not in a deployable state',
error: new Error(deployabilityCheck.message ?? 'Stack is not in a deployable state'),
logPath: logger.getRelativeLogPath(),
};
}
Expand Down Expand Up @@ -359,7 +360,7 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise<Dep
logger.finalize(false);
return {
success: false,
error: `Stack teardown failed: ${teardownError}`,
error: new Error(`Stack teardown failed: ${teardownError}`),
logPath: logger.getRelativeLogPath(),
};
}
Expand Down Expand Up @@ -639,8 +640,8 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise<Dep
agentNames,
hasGateways,
});
if (tsResult.error) {
logger.log(`Transaction search setup warning: ${tsResult.error}`, 'warn');
if (!tsResult.success) {
logger.log(`Transaction search setup warning: ${tsResult.error.message}`, 'warn');
} else {
notes.push(
'Transaction search enabled. It takes ~10 minutes for transaction search to be fully active and for traces from invocations to be indexed.'
Expand All @@ -666,7 +667,7 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise<Dep
} catch (err: unknown) {
logger.log(getErrorMessage(err), 'error');
logger.finalize(false);
return { success: false, error: getErrorMessage(err), logPath: logger.getRelativeLogPath() };
return { success: false, error: new Error(getErrorMessage(err)), logPath: logger.getRelativeLogPath() };
} finally {
if (toolkitWrapper) {
await toolkitWrapper.dispose();
Expand Down
Loading
Loading