Architecture
Deep dive into the modular CQRS architecture, service layer design, and
component interactions.
Architectural Overview
The AWS TypeScript CLI implements a modular CQRS architecture with
service layer coordination, following the project's commitment to
type safety, maintainability, and structured error handling.
CQRS Pattern Implementation
Command Query Responsibility Segregation
The CLI enforces clear separation between commands (write operations)
and queries (read operations):
// Query Operations (read-only)
interface QueryOperation {
readonly operation: "query";
readonly sideEffects: false;
}
// Examples: auth:status, auth:profiles
const statusQuery: QueryOperation = {
operation: "query",
sideEffects: false,
};
// Command Operations (state-changing)
interface CommandOperation {
readonly operation: "command";
readonly sideEffects: true;
}
// Examples: auth:login, auth:logout
const loginCommand: CommandOperation = {
operation: "command",
sideEffects: true,
};Command Implementation Pattern
Commands follow consistent orchestration through AuthService:
// Command: auth:login
export class LoginCommand extends Command {
async run(): Promise<void> {
const { flags } = await this.parse(LoginCommand);
// Delegate to service layer
const authService = new AuthService({
enableDebugLogging: flags.verbose,
enableProgressIndicators: true,
});
// Service orchestrates the workflow
await authService.login({
profile: flags.profile,
force: flags.force,
ssoConfig: flags.configure
? {
startUrl: flags["sso-start-url"],
region: flags["sso-region"],
accountId: flags["sso-account-id"],
roleName: flags["sso-role-name"],
}
: undefined,
});
}
}Query Implementation Pattern
Queries provide read-only access to authentication state:
// Query: auth:status
export class StatusCommand extends Command {
async run(): Promise<void> {
const { flags } = await this.parse(StatusCommand);
const authService = new AuthService({
enableDebugLogging: flags.verbose,
});
// Query operation - no state modification
const status = await authService.getStatus({
profile: flags.profile,
allProfiles: flags["all-profiles"],
detailed: flags.detailed,
});
// Output formatting only
this.displayStatus(status, flags.format);
}
}Service Layer Architecture
Service Coordination Pattern
The architecture implements composition over inheritance with clear service boundaries:
class AuthService {
private readonly cliWrapper: AuthCliWrapper;
private readonly credentialService: CredentialService;
private readonly profileManager: ProfileManager;
private readonly tokenManager: TokenManager;
constructor(options: AuthServiceOptions) {
// Dependency injection through composition
this.cliWrapper = new AuthCliWrapper(options.cliWrapper);
this.credentialService = new CredentialService(options.credentialService);
this.profileManager = new ProfileManager(options.profileManager);
this.tokenManager = new TokenManager(options.tokenManager);
}
// Orchestrates complex workflows
async login(input: AuthLogin): Promise<void> {
// 1. Check AWS CLI installation
await this.cliWrapper.checkInstallation();
// 2. Profile validation and configuration
const profileName = input.profile ?? this.credentialService.getActiveProfile();
// 3. SSO authentication workflow
await this.cliWrapper.ssoLogin(profileName);
// 4. Credential validation
await this.credentialService.validateCredentials(profileName);
// 5. State management
this.credentialService.setActiveProfile(profileName);
}
}Single Responsibility Principle
Each service has a focused responsibility:
AuthService - Orchestration
/**
* High-level authentication service for user-facing operations
*
* Orchestrates authentication workflows by coordinating AWS CLI wrapper,
* credential service, profile manager, and token manager.
*/
class AuthService {
// Coordinates between services
// Provides user-facing API
// Handles progress indicators
// Implements structured error handling
}ProfileManager - Configuration Management
/**
* AWS profile manager for multi-profile management
*
* Handles AWS profile discovery from configuration files, profile validation,
* and profile switching operations with structured error handling.
*/
class ProfileManager {
// Parses AWS config files
// Resolves SSO session inheritance
// Manages profile type detection
// Implements graceful error handling for file access
}TokenManager - Token Lifecycle
/**
* SSO token manager for token lifecycle management
*
* Manages SSO token expiry detection, refresh operations, and cache
* integration with AWS CLI SSO mechanisms.
*/
class TokenManager {
// Reads SSO token cache
// Detects token expiry
// Provides expiry warnings
// Integrates with AWS CLI cache management
}CredentialService - Validation
/**
* AWS credential validation and active profile management
*
* Validates AWS credentials using SDK credential provider chain
* and manages active profile state for CLI operations.
*/
class CredentialService {
// Validates AWS credentials
// Manages active profile state
// Integrates with AWS SDK credential providers
// Handles credential cache management
}Error Handling Architecture
Fail-Fast Validation Strategy
The architecture implements fail-fast validation at service boundaries:
// Input validation at service entry points
class AuthService {
async login(input: AuthLogin): Promise<void> {
// Validate input immediately
const validatedInput = AuthLoginSchema.parse(input);
try {
// Early AWS CLI check
await this.cliWrapper.checkInstallation();
// Early profile validation
const profileName = validatedInput.profile ?? this.credentialService.getActiveProfile();
if (!(await this.profileManager.profileExists(profileName))) {
throw new AuthenticationError(
`Profile '${profileName}' not found`,
"sso-login",
profileName,
);
}
// Continue with validated inputs
} catch (error) {
// Structured error handling with guidance
throw this.enhanceError(error);
}
}
}Graceful Degradation Patterns
Services implement graceful degradation for non-critical failures:
class ProfileManager {
async parseConfigFile(): Promise<AwsProfileConfig[]> {
try {
await fs.access(this.options.configFilePath);
} catch (error) {
// Graceful degradation for missing files
if (error instanceof Error && error.message.includes("ENOENT")) {
if (this.options.enableDebugLogging) {
console.debug(`AWS config file not found: ${this.options.configFilePath}`);
}
return []; // Continue with empty configuration
}
// Fail fast for unexpected errors
throw error;
}
// Continue with file parsing...
}
}Structured Error Types
The architecture provides structured error handling with structured types:
// Base error with architectural context
class AuthenticationError extends Error {
constructor(
message: string,
public readonly operation: string,
public readonly profile?: string,
public readonly cause?: Error,
) {
super(message);
this.name = "AuthenticationError";
}
}
// Error enhancement with resolution guidance
function getAuthErrorGuidance(error: AuthenticationError): string {
switch (error.operation) {
case "sso-login":
return "Try running 'aws configure sso' to set up your SSO profile";
case "credential-validation":
return "Run 'aws sso login --profile <profile>' to refresh your credentials";
default:
return "Check your AWS configuration and try again";
}
}Type Safety Implementation
TypeScript Usage
The architecture leverages TypeScript v5.9 for type safety:
// Strict input validation with Zod schemas
const AuthLoginSchema = z.object({
profile: z.string().optional(),
force: z.boolean().optional().default(false),
configure: z.boolean().optional().default(false),
ssoConfig: z
.object({
startUrl: z.string().url(),
region: z.string(),
accountId: z.string(),
roleName: z.string(),
})
.optional(),
});
type AuthLogin = z.infer<typeof AuthLoginSchema>;
// Service interfaces with type coverage
interface AuthService {
login(input: AuthLogin): Promise<void>;
getStatus(input: AuthStatus): Promise<AuthStatusResponse>;
logout(input: AuthLogout): Promise<void>;
listProfiles(input: AuthProfiles): Promise<ProfileInfo[]>;
switchProfile(input: AuthSwitch): Promise<void>;
}Interface Segregation
Services expose focused interfaces following the Interface Segregation Principle:
// Focused interfaces for specific capabilities
interface ProfileDiscovery {
discoverProfiles(): Promise<AwsProfileConfig[]>;
getProfileInfo(profileName: string): Promise<ProfileInfo>;
profileExists(profileName: string): Promise<boolean>;
}
interface TokenLifecycle {
getTokenInfo(startUrl: string): Promise<SsoTokenInfo | undefined>;
getTokenStatus(profileName: string, startUrl: string): Promise<TokenStatus>;
checkTokenExpiry(): Promise<TokenExpiryResult[]>;
}
interface CredentialValidation {
validateCredentials(profileName: string): Promise<void>;
getActiveProfile(): string;
setActiveProfile(profileName: string): void;
}Authentication Workflow Design
SSO Authentication Flow
The architecture implements a SSO workflow:
async login(input: AuthLogin): Promise<void> {
const spinner = this.createSpinner("Authenticating with AWS...");
try {
// Phase 1: Environment Validation
spinner.text = "Checking AWS CLI installation...";
await this.cliWrapper.checkInstallation();
// Phase 2: Profile Resolution
const profileName = input.profile ?? this.credentialService.getActiveProfile();
// Phase 3: Configuration Setup (if needed)
if (input.configure ||
!(await this.profileManager.profileExists(profileName))) {
spinner.text = `Configuring SSO for profile '${profileName}'...`;
await this.cliWrapper.configureSso(profileName, input.ssoConfig);
}
// Phase 4: Authentication Check
if (!input.force) {
spinner.text = "Checking existing authentication...";
try {
await this.credentialService.validateCredentials(profileName);
this.credentialService.setActiveProfile(profileName);
spinner.succeed(`Already authenticated with profile '${profileName}'`);
return;
} catch {
// Continue with login if validation fails
}
}
// Phase 5: SSO Login
spinner.text = `Logging in with SSO for profile '${profileName}'...`;
await this.cliWrapper.ssoLogin(profileName);
// Phase 6: Validation and State Setting
spinner.text = "Validating credentials...";
await this.credentialService.validateCredentials(profileName);
this.credentialService.setActiveProfile(profileName);
spinner.succeed(`Successfully authenticated with profile '${profileName}'`);
} catch (error) {
spinner.fail("Authentication failed");
throw this.enhanceErrorWithGuidance(error);
}
}Token Management Integration
The architecture integrates SSO token lifecycle management:
// Token status checking with architectural context
async getProfileStatus(profileName: string): Promise<ProfileInfo> {
const profileInfo = await this.profileManager.getProfileInfo(profileName);
// Credential validation
let credentialsValid = false;
try {
await this.credentialService.validateCredentials(profileName);
credentialsValid = true;
} catch {
credentialsValid = false;
}
// Token status for SSO profiles
let tokenExpiry: Date | undefined;
if (profileInfo.type === 'sso' && profileInfo.ssoStartUrl) {
const tokenStatus = await this.tokenManager.getTokenStatus(
profileName,
profileInfo.ssoStartUrl
);
tokenExpiry = tokenStatus.expiresAt;
}
return {
...profileInfo,
credentialsValid,
tokenExpiry,
};
}Testing Architecture
Service Layer Testing Strategy
The architecture enables multi-level testing through dependency injection:
// Service testing with mocked dependencies
describe("AuthService", () => {
let authService: AuthService;
let mockCliWrapper: MockAuthCliWrapper;
let mockCredentialService: MockCredentialService;
beforeEach(() => {
mockCliWrapper = new MockAuthCliWrapper();
mockCredentialService = new MockCredentialService();
authService = new AuthService({
cliWrapper: mockCliWrapper,
credentialService: mockCredentialService,
enableProgressIndicators: false, // Disable for testing
});
});
it("should orchestrate login workflow correctly", async () => {
// Arrange
mockCliWrapper.checkInstallation.mockResolvedValue({ installed: true });
mockCredentialService.validateCredentials.mockResolvedValue();
// Act
await authService.login({ profile: "test-profile" });
// Assert
expect(mockCliWrapper.ssoLogin).toHaveBeenCalledWith("test-profile");
expect(mockCredentialService.setActiveProfile).toHaveBeenCalledWith("test-profile");
});
});Integration Testing Architecture
The architecture supports integration testing with TestContainers:
// Integration testing with real AWS services
describe("ProfileManager Integration", () => {
let profileManager: ProfileManager;
let tempConfigPath: string;
beforeEach(async () => {
// Create temporary AWS config for testing
tempConfigPath = await createTempAwsConfig();
profileManager = new ProfileManager({
configFilePath: tempConfigPath,
enableDebugLogging: true,
});
});
it("should discover profiles from real config files", async () => {
const profiles = await profileManager.discoverProfiles();
expect(profiles).toHaveLength(3);
expect(profiles[0].type).toBe("sso");
});
});