Command Architecture
The CLI uses TypeScript, TypeDI for dependency injection, Commander.js for argument parsing, and Zod for validation.
Core Components
Commands
Commands implement TypedCommand<TInputs>:
interface TypedCommand<TInputs extends CommandInputs = CommandInputs> {
name: string;
description: string;
category: string;
aliases?: string[];
inputs: TInputs;
execute(inputs: TypedInputs<TInputs>): Promise<unknown>;
}
Services
Reusable business logic lives in injectable services:
src/services/
├── ticket-service-factory.ts
├── file-service.ts
├── input/input-service.ts
└── git-service.ts
Integrations
Third-party integrations include:
src/integrations/
├── github/
├── google/
└── yaml/
Dependency Injection
Use the @Service() decorator and constructor injection:
@Service()
export class MyCommand implements TypedCommand {
constructor(
private readonly fileService: FileService,
private readonly gitService: GitService,
) {}
}
The CLI uses separate containers for local commands and tests.
Configuration
Personal configuration lives in ~/.wseng. Project configuration lives in .wseng in the repository root. Access configuration through ConfigService:
@Service()
export class MyCommand {
constructor(private readonly config: ConfigService) {}
async execute() {
const personal = await this.config.getPersonalConfiguration();
const project = await this.config.getProjectConfiguration();
}
}
Command Categories
Registered commands use these categories:
commonfor general commands such ascommitandstart-ticketwsfor WS.Eng commands such assave-planandload-contextqcfor QC commandstkfor ticket commandscicdfor CI commands
The category determines the invocation form. For example, common commands use wseng commit, while ws commands use wseng ws save-plan.
Logging
Use log4js for logging:
import { getLogger } from "log4js";
const logger = getLogger("MyCommand");
logger.debug("Debug message");
logger.info("Info message");
logger.warn("Warning message");
logger.error("Error message");
Log levels can be set with --debug and --quiet.
Error Handling
Use custom errors to provide useful messages:
import { ValidationError } from "../../utils/error-utils";
if (!ticket) {
throw new ValidationError("No ticket found in context");
}
Testing
Test commands with a local build and command invocation, or call them programmatically through the container.
Project Structure
src/
├── commands/
│ ├── common/
│ ├── ws/
│ ├── qc/
│ ├── tk/
│ └── cicd/
├── services/
├── integrations/
├── models/
├── utils/
└── containers/
Command Organization
Simple commands can use one file. Complex commands can use a folder with command-specific services/, models/, and constants/ directories.
