Skip to content

Commit 5152a59

Browse files
committed
Refactor command structure and enhance logging
- Updated ListTasksCommand to use ILogger for logging and improved command configuration. - Refactored NextCommand to utilize ILogger and added command options for better usability. - Enhanced RefAuditCommand with ILogger and improved command configuration. - Updated RenderCommand to use ILogger and added command options for rendering tasks. - Refactored ShowTaskCommand to utilize ILogger and improved command configuration. - Enhanced SupersedeCommand with ILogger and added command options for superseding UIDs. - Refactored ValidateAndFixCommand to utilize ILogger and improved command options for validation. - Updated ValidateTasksCommand to use ILogger and improved command configuration. - Introduced command options interfaces for better type safety and clarity. - Improved error handling in provider errors and UID resolution errors. - Updated utility functions to use new type guards for null checks. - Enhanced task validation and fixing logic with improved logging and error handling. - Refactored todo commands to utilize ILogger and modernized command execution.
1 parent 2ac58cf commit 5152a59

27 files changed

+655
-427
lines changed

docs/command-options.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Command Option Type Definitions
2+
3+
This document describes the type definitions for Commander.js options and arguments for each command type in the DDD-Kit CLI.
4+
5+
## Overview
6+
7+
The `command-options.ts` file provides TypeScript interfaces that define the schema for command-line options and arguments. This improves type safety, provides better IDE support, and makes the CLI more maintainable.
8+
9+
## Available Command Option Types
10+
11+
### Core Commands
12+
13+
#### `NextCommandOptions`
14+
15+
Options for the `next` command that hydrates the next eligible task.
16+
17+
```typescript
18+
interface NextCommandOptions {
19+
provider?: string; // Task provider: todo, issues, projects
20+
filters?: string[]; // Filters for task selection
21+
branchPrefix?: string; // Branch prefix
22+
pin?: string; // Pin to specific ddd-kit commit/tag
23+
openPr?: boolean; // Open PR after hydration
24+
}
25+
```
26+
27+
#### `RenderCommandOptions`
28+
29+
Options for the `render` command that re-renders guidance for a specific task.
30+
31+
```typescript
32+
interface RenderCommandOptions {
33+
pin?: string; // Pin to specific ddd-kit commit/tag
34+
}
35+
```
36+
37+
#### `SupersedeCommandOptions`
38+
39+
Options for the `supersede` command (currently no options defined).
40+
41+
### Reference Commands
42+
43+
#### `RefAuditCommandOptions`
44+
45+
Options for the `ref audit` command (currently no options defined).
46+
47+
### Todo Commands
48+
49+
#### `TodoAddCommandOptions`
50+
51+
Options for the `todo add` command (currently no options defined).
52+
53+
#### `TodoCompleteCommandOptions`
54+
55+
Options for the `todo complete` command.
56+
57+
```typescript
58+
interface TodoCompleteCommandOptions {
59+
message?: string; // Completion message
60+
dryRun?: boolean; // Perform dry run without making changes
61+
}
62+
```
63+
64+
#### `TodoListCommandOptions`
65+
66+
Options for the `todo list` command (currently no options defined).
67+
68+
#### `TodoShowCommandOptions`
69+
70+
Options for the `todo show` command (currently no options defined).
71+
72+
### Validation Commands
73+
74+
#### `ValidateTasksCommandOptions`
75+
76+
Options for the `validate tasks` command (currently no options defined).
77+
78+
#### `ValidateFixCommandOptions`
79+
80+
Options for the `validate fix` command.
81+
82+
```typescript
83+
interface ValidateFixCommandOptions {
84+
fix?: boolean; // Apply fixes automatically
85+
dryRun?: boolean; // Perform dry run without making changes
86+
format?: 'json' | 'csv'; // Output format
87+
exclude?: string; // Pattern to exclude tasks
88+
}
89+
```
90+
91+
## Benefits
92+
93+
1. **Type Safety**: TypeScript compiler catches type errors at compile time
94+
2. **IDE Support**: Better intellisense and autocomplete in IDEs
95+
3. **Documentation**: Interface definitions serve as documentation
96+
4. **Maintainability**: Changes to command options are tracked by the type system
97+
5. **Consistency**: Ensures consistent option handling across commands
98+
99+
## Usage in Commands
100+
101+
Each command's `configure` method now uses typed parameters:
102+
103+
```typescript
104+
.action(async (options: NextCommandOptions) => {
105+
// TypeScript knows exactly what properties are available
106+
const cmd = new NextCommand();
107+
await cmd.execute(options);
108+
});
109+
```
110+
111+
This provides compile-time validation that the correct options are being passed to each command.

src/cli/command.factory.ts

Lines changed: 18 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -16,169 +16,30 @@ import { SupersedeCommand } from '../commands/supersede.command';
1616
* Centralizes command registration and configuration.
1717
*/
1818
export class CommandFactory {
19-
static createAddTaskCommand(): AddTaskCommand {
20-
return new AddTaskCommand();
21-
}
22-
23-
static createCompleteTaskCommand(): CompleteTaskCommand {
24-
return new CompleteTaskCommand();
25-
}
26-
27-
static createListTasksCommand(): ListTasksCommand {
28-
return new ListTasksCommand();
29-
}
30-
31-
static createShowTaskCommand(): ShowTaskCommand {
32-
return new ShowTaskCommand();
33-
}
34-
35-
static createValidateTasksCommand(): ValidateTasksCommand {
36-
return new ValidateTasksCommand();
37-
}
38-
39-
static createValidateAndFixCommand(): ValidateAndFixCommand {
40-
return new ValidateAndFixCommand();
41-
}
42-
43-
static createNextCommand(): NextCommand {
44-
return new NextCommand();
45-
}
46-
47-
static createRenderCommand(): RenderCommand {
48-
return new RenderCommand();
49-
}
50-
51-
static createRefAuditCommand(): RefAuditCommand {
52-
return new RefAuditCommand();
53-
}
54-
55-
static createSupersedeCommand(): SupersedeCommand {
56-
return new SupersedeCommand();
57-
}
58-
19+
/**
20+
* Configures all commands on the given Commander program instance.
21+
* @param program The Commander program instance to configure commands on.
22+
*/
5923
static configureProgram(program: Command): void {
60-
CommandFactory.configureCoreCommands(program);
61-
CommandFactory.configureTodoCommands(program);
62-
CommandFactory.configureValidateCommands(program);
63-
}
64-
65-
private static configureCoreCommands(program: Command): void {
66-
// next command
67-
program
68-
.command('next')
69-
.description('Hydrate the next eligible task')
70-
.option('--provider <provider>', 'Task provider: todo, issues, projects', 'todo')
71-
.option('--filters <filters...>', 'Filters for task selection')
72-
.option('--branch-prefix <prefix>', 'Branch prefix', 'feature/')
73-
.option('--pin <sha>', 'Pin to specific ddd-kit commit/tag')
74-
.option('--open-pr', 'Open PR after hydration')
75-
.action(async (options) => {
76-
const cmd = CommandFactory.createNextCommand();
77-
await cmd.execute(options);
78-
});
24+
// Core commands
25+
NextCommand.configure(program);
26+
RenderCommand.configure(program);
27+
SupersedeCommand.configure(program);
7928

80-
// render command
81-
program
82-
.command('render')
83-
.argument('<task>', 'Task ID to render')
84-
.description('Re-render guidance for a specific task')
85-
.option('--pin <sha>', 'Pin to specific ddd-kit commit/tag')
86-
.action(async (taskId, options) => {
87-
const cmd = CommandFactory.createRenderCommand();
88-
await cmd.execute(taskId, options);
89-
});
90-
91-
// ref command
29+
// Reference commands
9230
const ref = program.command('ref').description('Reference management');
93-
ref
94-
.command('audit')
95-
.description('Audit references across repo & tasks')
96-
.action(async () => {
97-
const cmd = CommandFactory.createRefAuditCommand();
98-
await cmd.execute();
99-
});
100-
101-
// supersede command
102-
program
103-
.command('supersede')
104-
.argument('<oldUid>', 'Old UID')
105-
.argument('<newUid>', 'New UID')
106-
.description('Supersede an old UID with a new one')
107-
.action(async (oldUid, newUid) => {
108-
const cmd = CommandFactory.createSupersedeCommand();
109-
await cmd.execute(oldUid, newUid);
110-
});
111-
}
31+
RefAuditCommand.configure(ref);
11232

113-
private static configureTodoCommands(program: Command): void {
114-
// todo commands
33+
// Todo commands
11534
const todo = program.command('todo').description('Task management commands');
35+
AddTaskCommand.configure(todo);
36+
CompleteTaskCommand.configure(todo);
37+
ListTasksCommand.configure(todo);
38+
ShowTaskCommand.configure(todo);
11639

117-
todo
118-
.command('add')
119-
.argument('<file>', 'File containing the task to add')
120-
.description('Add a new task from a file')
121-
.action(async (file) => {
122-
const cmd = CommandFactory.createAddTaskCommand();
123-
await cmd.execute({ file });
124-
});
125-
126-
todo
127-
.command('complete')
128-
.argument('<id>', 'Task ID to complete')
129-
.option('--message <message>', 'Completion message')
130-
.option('--dry-run', 'Perform dry run without making changes')
131-
.description('Mark a task as completed')
132-
.action((id, options) => {
133-
const cmd = CommandFactory.createCompleteTaskCommand();
134-
cmd.execute({ id, opts: { dryRun: options.dryRun, message: options.message } });
135-
});
136-
137-
todo
138-
.command('list')
139-
.description('List all tasks')
140-
.action(async () => {
141-
const cmd = CommandFactory.createListTasksCommand();
142-
await cmd.execute();
143-
});
144-
145-
todo
146-
.command('show')
147-
.argument('<id>', 'Task ID to show')
148-
.description('Show details of a specific task')
149-
.action(async (id) => {
150-
const cmd = CommandFactory.createShowTaskCommand();
151-
await cmd.execute({ id });
152-
});
153-
}
154-
155-
private static configureValidateCommands(program: Command): void {
156-
// validate commands
40+
// Validate commands
15741
const validate = program.command('validate').description('Validation commands');
158-
159-
validate
160-
.command('tasks')
161-
.description('Validate all tasks')
162-
.action(async () => {
163-
const cmd = CommandFactory.createValidateTasksCommand();
164-
await cmd.execute();
165-
});
166-
167-
validate
168-
.command('fix')
169-
.description('Validate and fix tasks')
170-
.option('--fix', 'Apply fixes automatically')
171-
.option('--dry-run', 'Perform dry run without making changes')
172-
.option('--format <format>', 'Output format: json, csv', 'json')
173-
.option('--exclude <pattern>', 'Pattern to exclude tasks')
174-
.action(async (options) => {
175-
const cmd = CommandFactory.createValidateAndFixCommand();
176-
await cmd.execute({
177-
dryRun: options.dryRun,
178-
exclude: options.exclude,
179-
fix: options.fix,
180-
summary: { format: options.format },
181-
});
182-
});
42+
ValidateTasksCommand.configure(validate);
43+
ValidateAndFixCommand.configure(validate);
18344
}
18445
}

src/cli/command.registry.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ export class CommandRegistry {
88

99
/**
1010
* Registers a command in the registry.
11-
* @param cmd - The command to register.
11+
* @param command - The command to register.
1212
*/
13-
register(cmd: ICommand): void {
14-
this.commands.set(cmd.name, cmd);
13+
register(command: ICommand): void {
14+
this.commands.set(command.name, command);
1515
}
1616

1717
/**

src/commands/add-task.command.ts

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,49 @@
11
import chalk from 'chalk';
2+
import { Command } from 'commander';
23

4+
import { ILogger } from '../interfaces/ILogger';
35
import { getLogger } from '../utils/logger';
46
import { addTaskFromFile } from '../utils/todo';
5-
import { ICommand } from '../interfaces/ICommand';
7+
import { TodoAddCommandArgs } from '../interfaces/command-options';
68

79
/**
8-
* Command for adding a new task from a file to the TODO.md.
10+
* Modern command for adding a new task from a file to the TODO.md.
911
*/
10-
export class AddTaskCommand implements ICommand {
11-
name = 'todo:add';
12-
description = 'Add task from file';
13-
14-
/**
15-
* Creates a new AddTaskCommand instance.
16-
* @param file - Optional file path containing the task to add. Can also be provided in execute args.
17-
*/
18-
constructor(private readonly file?: string) {} // eslint-disable-line no-unused-vars
12+
export class AddTaskCommand {
13+
constructor(private readonly logger: ILogger) {}
1914

2015
/**
2116
* Executes the add task command.
2217
* Reads a task from a file and adds it to the TODO.md file.
23-
* @param args - Optional arguments containing the file path.
2418
*/
25-
execute(args?: { file?: string }): Promise<void> {
26-
const file = args?.file ?? this.file;
19+
execute(args: TodoAddCommandArgs): Promise<void> {
2720
try {
28-
const log = getLogger();
29-
const added = addTaskFromFile(String(file), log);
21+
const added = addTaskFromFile(args.file, this.logger);
3022
if (added) {
31-
console.log(chalk.green(`Task added to TODO.md from ${file}`));
23+
console.log(chalk.green(`Task added to TODO.md from ${args.file}`));
24+
this.logger.info('Task added successfully', { file: args.file });
3225
} else {
33-
console.error(chalk.red(`Failed to add task from ${file}`));
26+
console.error(chalk.red(`Failed to add task from ${args.file}`));
27+
this.logger.error('Failed to add task', { file: args.file });
3428
process.exitCode = 1;
3529
}
36-
} catch (e: unknown) {
37-
let msg = String(e);
38-
if (typeof e === 'object' && e !== null && 'message' in e) {
39-
const obj = e as { message?: unknown };
40-
if (typeof obj.message === 'string') msg = obj.message;
41-
}
42-
console.error(chalk.red('Error adding task:'), msg || e);
30+
} catch (error: unknown) {
31+
const message = error instanceof Error ? error.message : String(error);
32+
console.error(chalk.red('Error adding task:'), message);
33+
this.logger.error('Error adding task', { error: message, file: args.file });
4334
process.exitCode = 2;
4435
}
4536
return Promise.resolve();
4637
}
38+
39+
static configure(parent: Command): void {
40+
parent
41+
.command('add')
42+
.argument('<file>', 'File containing the task to add')
43+
.description('Add a new task from a file')
44+
.action(async (file: string) => {
45+
const cmd = new AddTaskCommand(getLogger());
46+
await cmd.execute({ file });
47+
});
48+
}
4749
}

0 commit comments

Comments
 (0)