feat: create tm-core and apps/cli (#1093)
- add typescript - add npm workspaces
This commit is contained in:
49
apps/cli/package.json
Normal file
49
apps/cli/package.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@tm/cli",
|
||||
"version": "1.0.0",
|
||||
"description": "Task Master CLI - Command line interface for task management",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": ["dist", "README.md"],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "biome check src",
|
||||
"format": "biome format --write src",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tm/core": "*",
|
||||
"boxen": "^7.1.1",
|
||||
"chalk": "^5.3.0",
|
||||
"cli-table3": "^0.6.5",
|
||||
"commander": "^12.1.0",
|
||||
"inquirer": "^9.2.10",
|
||||
"ora": "^8.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@types/inquirer": "^9.0.3",
|
||||
"@types/node": "^22.10.5",
|
||||
"tsup": "^8.3.0",
|
||||
"tsx": "^4.20.4",
|
||||
"typescript": "^5.7.3",
|
||||
"vitest": "^2.1.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"keywords": ["task-master", "cli", "task-management", "productivity"],
|
||||
"author": "",
|
||||
"license": "MIT"
|
||||
}
|
||||
327
apps/cli/src/commands/list.command.ts
Normal file
327
apps/cli/src/commands/list.command.ts
Normal file
@@ -0,0 +1,327 @@
|
||||
/**
|
||||
* @fileoverview ListTasks command using Commander's native class pattern
|
||||
* Extends Commander.Command for better integration with the framework
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import {
|
||||
createTaskMasterCore,
|
||||
type Task,
|
||||
type TaskStatus,
|
||||
type TaskMasterCore,
|
||||
TASK_STATUSES,
|
||||
OUTPUT_FORMATS,
|
||||
STATUS_ICONS,
|
||||
type OutputFormat
|
||||
} from '@tm/core';
|
||||
import * as ui from '../utils/ui.js';
|
||||
|
||||
/**
|
||||
* Options interface for the list command
|
||||
*/
|
||||
export interface ListCommandOptions {
|
||||
status?: string;
|
||||
tag?: string;
|
||||
withSubtasks?: boolean;
|
||||
format?: OutputFormat;
|
||||
silent?: boolean;
|
||||
project?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result type from list command
|
||||
*/
|
||||
export interface ListTasksResult {
|
||||
tasks: Task[];
|
||||
total: number;
|
||||
filtered: number;
|
||||
tag?: string;
|
||||
storageType: 'file' | 'api';
|
||||
}
|
||||
|
||||
/**
|
||||
* ListTasksCommand extending Commander's Command class
|
||||
* This is a thin presentation layer over @tm/core
|
||||
*/
|
||||
export class ListTasksCommand extends Command {
|
||||
private tmCore?: TaskMasterCore;
|
||||
private lastResult?: ListTasksResult;
|
||||
|
||||
constructor(name?: string) {
|
||||
super(name || 'list');
|
||||
|
||||
// Configure the command
|
||||
this.description('List tasks with optional filtering')
|
||||
.alias('ls')
|
||||
.option('-s, --status <status>', 'Filter by status (comma-separated)')
|
||||
.option('-t, --tag <tag>', 'Filter by tag')
|
||||
.option('--with-subtasks', 'Include subtasks in the output')
|
||||
.option(
|
||||
'-f, --format <format>',
|
||||
'Output format (text, json, compact)',
|
||||
'text'
|
||||
)
|
||||
.option('--silent', 'Suppress output (useful for programmatic usage)')
|
||||
.option('-p, --project <path>', 'Project root directory', process.cwd())
|
||||
.action(async (options: ListCommandOptions) => {
|
||||
await this.executeCommand(options);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the list command
|
||||
*/
|
||||
private async executeCommand(options: ListCommandOptions): Promise<void> {
|
||||
try {
|
||||
// Validate options
|
||||
if (!this.validateOptions(options)) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Initialize tm-core
|
||||
await this.initializeCore(options.project || process.cwd());
|
||||
|
||||
// Get tasks from core
|
||||
const result = await this.getTasks(options);
|
||||
|
||||
// Store result for programmatic access
|
||||
this.setLastResult(result);
|
||||
|
||||
// Display results
|
||||
if (!options.silent) {
|
||||
this.displayResults(result, options);
|
||||
}
|
||||
} catch (error: any) {
|
||||
const msg = error?.getSanitizedDetails?.() ?? {
|
||||
message: error?.message ?? String(error)
|
||||
};
|
||||
console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
|
||||
if (error.stack && process.env.DEBUG) {
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate command options
|
||||
*/
|
||||
private validateOptions(options: ListCommandOptions): boolean {
|
||||
// Validate format
|
||||
if (
|
||||
options.format &&
|
||||
!OUTPUT_FORMATS.includes(options.format as OutputFormat)
|
||||
) {
|
||||
console.error(chalk.red(`Invalid format: ${options.format}`));
|
||||
console.error(chalk.gray(`Valid formats: ${OUTPUT_FORMATS.join(', ')}`));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate status
|
||||
if (options.status) {
|
||||
const statuses = options.status.split(',').map((s: string) => s.trim());
|
||||
|
||||
for (const status of statuses) {
|
||||
if (status !== 'all' && !TASK_STATUSES.includes(status as TaskStatus)) {
|
||||
console.error(chalk.red(`Invalid status: ${status}`));
|
||||
console.error(
|
||||
chalk.gray(`Valid statuses: ${TASK_STATUSES.join(', ')}`)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize TaskMasterCore
|
||||
*/
|
||||
private async initializeCore(projectRoot: string): Promise<void> {
|
||||
if (!this.tmCore) {
|
||||
this.tmCore = await createTaskMasterCore({ projectPath: projectRoot });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tasks from tm-core
|
||||
*/
|
||||
private async getTasks(
|
||||
options: ListCommandOptions
|
||||
): Promise<ListTasksResult> {
|
||||
if (!this.tmCore) {
|
||||
throw new Error('TaskMasterCore not initialized');
|
||||
}
|
||||
|
||||
// Build filter
|
||||
const filter =
|
||||
options.status && options.status !== 'all'
|
||||
? {
|
||||
status: options.status
|
||||
.split(',')
|
||||
.map((s: string) => s.trim() as TaskStatus)
|
||||
}
|
||||
: undefined;
|
||||
|
||||
// Call tm-core
|
||||
const result = await this.tmCore.getTaskList({
|
||||
tag: options.tag,
|
||||
filter,
|
||||
includeSubtasks: options.withSubtasks
|
||||
});
|
||||
|
||||
return result as ListTasksResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display results based on format
|
||||
*/
|
||||
private displayResults(
|
||||
result: ListTasksResult,
|
||||
options: ListCommandOptions
|
||||
): void {
|
||||
const format = (options.format || 'text') as OutputFormat | 'text';
|
||||
|
||||
switch (format) {
|
||||
case 'json':
|
||||
this.displayJson(result);
|
||||
break;
|
||||
|
||||
case 'compact':
|
||||
this.displayCompact(result.tasks, options.withSubtasks);
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
default:
|
||||
this.displayText(result, options.withSubtasks);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display in JSON format
|
||||
*/
|
||||
private displayJson(data: ListTasksResult): void {
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
tasks: data.tasks,
|
||||
metadata: {
|
||||
total: data.total,
|
||||
filtered: data.filtered,
|
||||
tag: data.tag,
|
||||
storageType: data.storageType
|
||||
}
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display in compact format
|
||||
*/
|
||||
private displayCompact(tasks: Task[], withSubtasks?: boolean): void {
|
||||
tasks.forEach((task) => {
|
||||
const icon = STATUS_ICONS[task.status];
|
||||
console.log(`${chalk.cyan(task.id)} ${icon} ${task.title}`);
|
||||
|
||||
if (withSubtasks && task.subtasks?.length) {
|
||||
task.subtasks.forEach((subtask) => {
|
||||
const subIcon = STATUS_ICONS[subtask.status];
|
||||
console.log(
|
||||
` ${chalk.gray(`${task.id}.${subtask.id}`)} ${subIcon} ${chalk.gray(subtask.title)}`
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display in text format with tables
|
||||
*/
|
||||
private displayText(data: ListTasksResult, withSubtasks?: boolean): void {
|
||||
const { tasks, total, filtered, tag, storageType } = data;
|
||||
|
||||
// Header
|
||||
ui.displayBanner(`Task List${tag ? ` (${tag})` : ''}`);
|
||||
|
||||
// Statistics
|
||||
console.log(chalk.blue.bold('\n📊 Statistics:\n'));
|
||||
console.log(` Total tasks: ${chalk.cyan(total)}`);
|
||||
console.log(` Filtered: ${chalk.cyan(filtered)}`);
|
||||
if (tag) {
|
||||
console.log(` Tag: ${chalk.cyan(tag)}`);
|
||||
}
|
||||
console.log(` Storage: ${chalk.cyan(storageType)}`);
|
||||
|
||||
// No tasks message
|
||||
if (tasks.length === 0) {
|
||||
ui.displayWarning('No tasks found matching the criteria.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Task table
|
||||
console.log(chalk.blue.bold(`\n📋 Tasks (${tasks.length}):\n`));
|
||||
console.log(
|
||||
ui.createTaskTable(tasks, {
|
||||
showSubtasks: withSubtasks,
|
||||
showDependencies: true
|
||||
})
|
||||
);
|
||||
|
||||
// Progress bar
|
||||
const completedCount = tasks.filter(
|
||||
(t: Task) => t.status === 'done'
|
||||
).length;
|
||||
console.log(chalk.blue.bold('\n📊 Overall Progress:\n'));
|
||||
console.log(` ${ui.createProgressBar(completedCount, tasks.length)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last result for programmatic access
|
||||
*/
|
||||
private setLastResult(result: ListTasksResult): void {
|
||||
this.lastResult = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last result (for programmatic usage)
|
||||
*/
|
||||
getLastResult(): ListTasksResult | undefined {
|
||||
return this.lastResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources
|
||||
*/
|
||||
async cleanup(): Promise<void> {
|
||||
if (this.tmCore) {
|
||||
await this.tmCore.close();
|
||||
this.tmCore = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method to register this command on an existing program
|
||||
* This is for gradual migration - allows commands.js to use this
|
||||
*/
|
||||
static registerOn(program: Command): Command {
|
||||
const listCommand = new ListTasksCommand();
|
||||
program.addCommand(listCommand);
|
||||
return listCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative registration that returns the command for chaining
|
||||
* Can also configure the command name if needed
|
||||
*/
|
||||
static register(program: Command, name?: string): ListTasksCommand {
|
||||
const listCommand = new ListTasksCommand(name);
|
||||
program.addCommand(listCommand);
|
||||
return listCommand;
|
||||
}
|
||||
}
|
||||
18
apps/cli/src/index.ts
Normal file
18
apps/cli/src/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @fileoverview Main entry point for @tm/cli package
|
||||
* Exports all public APIs for the CLI presentation layer
|
||||
*/
|
||||
|
||||
// Commands
|
||||
export { ListTasksCommand } from './commands/list.command.js';
|
||||
|
||||
// UI utilities (for other commands to use)
|
||||
export * as ui from './utils/ui.js';
|
||||
|
||||
// Re-export commonly used types from tm-core
|
||||
export type {
|
||||
Task,
|
||||
TaskStatus,
|
||||
TaskPriority,
|
||||
TaskMasterCore
|
||||
} from '@tm/core';
|
||||
326
apps/cli/src/utils/ui.ts
Normal file
326
apps/cli/src/utils/ui.ts
Normal file
@@ -0,0 +1,326 @@
|
||||
/**
|
||||
* @fileoverview UI utilities for Task Master CLI
|
||||
* Provides formatting, display, and visual components for the command line interface
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import Table from 'cli-table3';
|
||||
import type { Task, TaskStatus, TaskPriority } from '@tm/core';
|
||||
|
||||
/**
|
||||
* Get colored status display with ASCII icons (matches scripts/modules/ui.js style)
|
||||
*/
|
||||
export function getStatusWithColor(
|
||||
status: TaskStatus,
|
||||
forTable: boolean = false
|
||||
): string {
|
||||
const statusConfig = {
|
||||
done: {
|
||||
color: chalk.green,
|
||||
icon: String.fromCharCode(8730),
|
||||
tableIcon: String.fromCharCode(8730)
|
||||
}, // √
|
||||
pending: { color: chalk.yellow, icon: 'o', tableIcon: 'o' },
|
||||
'in-progress': {
|
||||
color: chalk.hex('#FFA500'),
|
||||
icon: String.fromCharCode(9654),
|
||||
tableIcon: '>'
|
||||
}, // ▶
|
||||
deferred: { color: chalk.gray, icon: 'x', tableIcon: 'x' },
|
||||
blocked: { color: chalk.red, icon: '!', tableIcon: '!' },
|
||||
review: { color: chalk.magenta, icon: '?', tableIcon: '?' },
|
||||
cancelled: { color: chalk.gray, icon: 'X', tableIcon: 'X' }
|
||||
};
|
||||
|
||||
const config = statusConfig[status] || {
|
||||
color: chalk.red,
|
||||
icon: 'X',
|
||||
tableIcon: 'X'
|
||||
};
|
||||
|
||||
// Use simple ASCII characters for stable display
|
||||
const simpleIcons = {
|
||||
done: String.fromCharCode(8730), // √
|
||||
pending: 'o',
|
||||
'in-progress': '>',
|
||||
deferred: 'x',
|
||||
blocked: '!',
|
||||
review: '?',
|
||||
cancelled: 'X'
|
||||
};
|
||||
|
||||
const icon = forTable ? simpleIcons[status] || 'X' : config.icon;
|
||||
return config.color(`${icon} ${status}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get colored priority display
|
||||
*/
|
||||
export function getPriorityWithColor(priority: TaskPriority): string {
|
||||
const priorityColors: Record<TaskPriority, (text: string) => string> = {
|
||||
critical: chalk.red.bold,
|
||||
high: chalk.red,
|
||||
medium: chalk.yellow,
|
||||
low: chalk.gray
|
||||
};
|
||||
|
||||
const colorFn = priorityColors[priority] || chalk.white;
|
||||
return colorFn(priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get colored complexity display
|
||||
*/
|
||||
export function getComplexityWithColor(complexity: number | string): string {
|
||||
const score =
|
||||
typeof complexity === 'string' ? parseInt(complexity, 10) : complexity;
|
||||
|
||||
if (isNaN(score)) {
|
||||
return chalk.gray('N/A');
|
||||
}
|
||||
|
||||
if (score >= 8) {
|
||||
return chalk.red.bold(`${score} (High)`);
|
||||
} else if (score >= 5) {
|
||||
return chalk.yellow(`${score} (Medium)`);
|
||||
} else {
|
||||
return chalk.green(`${score} (Low)`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate text to specified length
|
||||
*/
|
||||
export function truncate(text: string, maxLength: number): string {
|
||||
if (text.length <= maxLength) {
|
||||
return text;
|
||||
}
|
||||
return text.substring(0, maxLength - 3) + '...';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a progress bar
|
||||
*/
|
||||
export function createProgressBar(
|
||||
completed: number,
|
||||
total: number,
|
||||
width: number = 30
|
||||
): string {
|
||||
if (total === 0) {
|
||||
return chalk.gray('No tasks');
|
||||
}
|
||||
|
||||
const percentage = Math.round((completed / total) * 100);
|
||||
const filled = Math.round((completed / total) * width);
|
||||
const empty = width - filled;
|
||||
|
||||
const bar = chalk.green('█').repeat(filled) + chalk.gray('░').repeat(empty);
|
||||
|
||||
return `${bar} ${chalk.cyan(`${percentage}%`)} (${completed}/${total})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a fancy banner
|
||||
*/
|
||||
export function displayBanner(title: string = 'Task Master'): void {
|
||||
console.log(
|
||||
boxen(chalk.white.bold(title), {
|
||||
padding: 1,
|
||||
margin: { top: 1, bottom: 1 },
|
||||
borderStyle: 'round',
|
||||
borderColor: 'blue',
|
||||
textAlignment: 'center'
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an error message (matches scripts/modules/ui.js style)
|
||||
*/
|
||||
export function displayError(message: string, details?: string): void {
|
||||
console.error(
|
||||
boxen(
|
||||
chalk.red.bold('X Error: ') +
|
||||
chalk.white(message) +
|
||||
(details ? '\n\n' + chalk.gray(details) : ''),
|
||||
{
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'red'
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a success message
|
||||
*/
|
||||
export function displaySuccess(message: string): void {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green.bold(String.fromCharCode(8730) + ' ') + chalk.white(message),
|
||||
{
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'green'
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a warning message
|
||||
*/
|
||||
export function displayWarning(message: string): void {
|
||||
console.log(
|
||||
boxen(chalk.yellow.bold('⚠ ') + chalk.white(message), {
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'yellow'
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display info message
|
||||
*/
|
||||
export function displayInfo(message: string): void {
|
||||
console.log(
|
||||
boxen(chalk.blue.bold('i ') + chalk.white(message), {
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'blue'
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format dependencies with their status
|
||||
*/
|
||||
export function formatDependenciesWithStatus(
|
||||
dependencies: string[] | number[],
|
||||
tasks: Task[]
|
||||
): string {
|
||||
if (!dependencies || dependencies.length === 0) {
|
||||
return chalk.gray('none');
|
||||
}
|
||||
|
||||
const taskMap = new Map(tasks.map((t) => [t.id.toString(), t]));
|
||||
|
||||
return dependencies
|
||||
.map((depId) => {
|
||||
const task = taskMap.get(depId.toString());
|
||||
if (!task) {
|
||||
return chalk.red(`${depId} (not found)`);
|
||||
}
|
||||
|
||||
const statusIcon =
|
||||
task.status === 'done'
|
||||
? '✓'
|
||||
: task.status === 'in-progress'
|
||||
? '►'
|
||||
: '○';
|
||||
|
||||
return `${depId}${statusIcon}`;
|
||||
})
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a task table for display
|
||||
*/
|
||||
export function createTaskTable(
|
||||
tasks: Task[],
|
||||
options?: {
|
||||
showSubtasks?: boolean;
|
||||
showComplexity?: boolean;
|
||||
showDependencies?: boolean;
|
||||
}
|
||||
): string {
|
||||
const {
|
||||
showSubtasks = false,
|
||||
showComplexity = false,
|
||||
showDependencies = true
|
||||
} = options || {};
|
||||
|
||||
// Calculate dynamic column widths based on terminal width
|
||||
const terminalWidth = process.stdout.columns || 100;
|
||||
const baseColWidths = showComplexity
|
||||
? [8, Math.floor(terminalWidth * 0.35), 18, 12, 15, 12] // ID, Title, Status, Priority, Dependencies, Complexity
|
||||
: [8, Math.floor(terminalWidth * 0.4), 18, 12, 20]; // ID, Title, Status, Priority, Dependencies
|
||||
|
||||
const headers = [
|
||||
chalk.blue.bold('ID'),
|
||||
chalk.blue.bold('Title'),
|
||||
chalk.blue.bold('Status'),
|
||||
chalk.blue.bold('Priority')
|
||||
];
|
||||
const colWidths = baseColWidths.slice(0, 4);
|
||||
|
||||
if (showDependencies) {
|
||||
headers.push(chalk.blue.bold('Dependencies'));
|
||||
colWidths.push(baseColWidths[4]);
|
||||
}
|
||||
|
||||
if (showComplexity) {
|
||||
headers.push(chalk.blue.bold('Complexity'));
|
||||
colWidths.push(baseColWidths[5] || 12);
|
||||
}
|
||||
|
||||
const table = new Table({
|
||||
head: headers,
|
||||
style: { head: [], border: [] },
|
||||
colWidths,
|
||||
wordWrap: true
|
||||
});
|
||||
|
||||
tasks.forEach((task) => {
|
||||
const row: string[] = [
|
||||
chalk.cyan(task.id.toString()),
|
||||
truncate(task.title, colWidths[1] - 3),
|
||||
getStatusWithColor(task.status, true), // Use table version
|
||||
getPriorityWithColor(task.priority)
|
||||
];
|
||||
|
||||
if (showDependencies) {
|
||||
row.push(formatDependenciesWithStatus(task.dependencies, tasks));
|
||||
}
|
||||
|
||||
if (showComplexity && 'complexity' in task) {
|
||||
row.push(getComplexityWithColor(task.complexity as number | string));
|
||||
}
|
||||
|
||||
table.push(row);
|
||||
|
||||
// Add subtasks if requested
|
||||
if (showSubtasks && task.subtasks && task.subtasks.length > 0) {
|
||||
task.subtasks.forEach((subtask) => {
|
||||
const subRow: string[] = [
|
||||
chalk.gray(` └─ ${subtask.id}`),
|
||||
chalk.gray(truncate(subtask.title, colWidths[1] - 6)),
|
||||
chalk.gray(getStatusWithColor(subtask.status, true)),
|
||||
chalk.gray(subtask.priority || 'medium')
|
||||
];
|
||||
|
||||
if (showDependencies) {
|
||||
subRow.push(
|
||||
chalk.gray(
|
||||
subtask.dependencies && subtask.dependencies.length > 0
|
||||
? subtask.dependencies.map((dep) => String(dep)).join(', ')
|
||||
: 'None'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (showComplexity) {
|
||||
subRow.push(chalk.gray('--'));
|
||||
}
|
||||
|
||||
table.push(subRow);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return table.toString();
|
||||
}
|
||||
27
apps/cli/tsconfig.json
Normal file
27
apps/cli/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "tests"]
|
||||
}
|
||||
15
apps/cli/tsup.config.ts
Normal file
15
apps/cli/tsup.config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
target: 'node18',
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
dts: true,
|
||||
shims: true,
|
||||
esbuildOptions(options) {
|
||||
options.platform = 'node';
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user