feat: improve table UI
This commit is contained in:
@@ -6,10 +6,7 @@
|
|||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./src/index.ts",
|
"types": "./src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": "./src/index.ts"
|
||||||
"types": "./src/index.ts",
|
|
||||||
"import": "./dist/index.js"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"files": ["dist", "README.md"],
|
"files": ["dist", "README.md"],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -46,5 +43,10 @@
|
|||||||
},
|
},
|
||||||
"keywords": ["task-master", "cli", "task-management", "productivity"],
|
"keywords": ["task-master", "cli", "task-management", "productivity"],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"typesVersions": {
|
||||||
|
"*": {
|
||||||
|
"*": ["src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,16 +280,8 @@ export class ListTasksCommand extends Command {
|
|||||||
const depStats = calculateDependencyStatistics(tasks);
|
const depStats = calculateDependencyStatistics(tasks);
|
||||||
const priorityBreakdown = getPriorityBreakdown(tasks);
|
const priorityBreakdown = getPriorityBreakdown(tasks);
|
||||||
|
|
||||||
// Find next task (simplified for now)
|
// Find next task following the same logic as findNextTask
|
||||||
const nextTask: NextTaskInfo | undefined = tasks
|
const nextTask = this.findNextTask(tasks);
|
||||||
.filter(t => t.status === 'pending' && (!t.dependencies || t.dependencies.length === 0))
|
|
||||||
.map(t => ({
|
|
||||||
id: t.id,
|
|
||||||
title: t.title,
|
|
||||||
priority: t.priority,
|
|
||||||
dependencies: t.dependencies,
|
|
||||||
complexity: undefined // Add if available
|
|
||||||
}))[0];
|
|
||||||
|
|
||||||
// Display dashboard boxes
|
// Display dashboard boxes
|
||||||
displayDashboards(taskStats, subtaskStats, priorityBreakdown, depStats, nextTask);
|
displayDashboards(taskStats, subtaskStats, priorityBreakdown, depStats, nextTask);
|
||||||
@@ -311,6 +303,123 @@ export class ListTasksCommand extends Command {
|
|||||||
this.lastResult = result;
|
this.lastResult = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the next task to work on
|
||||||
|
* Implements the same logic as scripts/modules/task-manager/find-next-task.js
|
||||||
|
*/
|
||||||
|
private findNextTask(tasks: Task[]): NextTaskInfo | undefined {
|
||||||
|
const priorityValues: Record<string, number> = {
|
||||||
|
critical: 4,
|
||||||
|
high: 3,
|
||||||
|
medium: 2,
|
||||||
|
low: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build set of completed task IDs (including subtasks)
|
||||||
|
const completedIds = new Set<string>();
|
||||||
|
tasks.forEach(t => {
|
||||||
|
if (t.status === 'done' || t.status === 'completed') {
|
||||||
|
completedIds.add(String(t.id));
|
||||||
|
}
|
||||||
|
if (t.subtasks) {
|
||||||
|
t.subtasks.forEach(st => {
|
||||||
|
if (st.status === 'done' || st.status === 'completed') {
|
||||||
|
completedIds.add(`${t.id}.${st.id}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// First, look for eligible subtasks in in-progress parent tasks
|
||||||
|
const candidateSubtasks: NextTaskInfo[] = [];
|
||||||
|
|
||||||
|
tasks
|
||||||
|
.filter(t => t.status === 'in-progress' && t.subtasks && t.subtasks.length > 0)
|
||||||
|
.forEach(parent => {
|
||||||
|
parent.subtasks!.forEach(st => {
|
||||||
|
const stStatus = (st.status || 'pending').toLowerCase();
|
||||||
|
if (stStatus !== 'pending' && stStatus !== 'in-progress') return;
|
||||||
|
|
||||||
|
// Check if dependencies are satisfied
|
||||||
|
const fullDeps = st.dependencies?.map(d => {
|
||||||
|
// Handle both numeric and string IDs
|
||||||
|
if (typeof d === 'string' && d.includes('.')) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
return `${parent.id}.${d}`;
|
||||||
|
}) ?? [];
|
||||||
|
|
||||||
|
const depsSatisfied = fullDeps.length === 0 ||
|
||||||
|
fullDeps.every(depId => completedIds.has(String(depId)));
|
||||||
|
|
||||||
|
if (depsSatisfied) {
|
||||||
|
candidateSubtasks.push({
|
||||||
|
id: `${parent.id}.${st.id}`,
|
||||||
|
title: st.title || `Subtask ${st.id}`,
|
||||||
|
priority: st.priority || parent.priority || 'medium',
|
||||||
|
dependencies: fullDeps.map(d => String(d))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (candidateSubtasks.length > 0) {
|
||||||
|
// Sort by priority, then by dependencies count, then by ID
|
||||||
|
candidateSubtasks.sort((a, b) => {
|
||||||
|
const pa = priorityValues[a.priority || 'medium'] ?? 2;
|
||||||
|
const pb = priorityValues[b.priority || 'medium'] ?? 2;
|
||||||
|
if (pb !== pa) return pb - pa;
|
||||||
|
|
||||||
|
const depCountA = a.dependencies?.length || 0;
|
||||||
|
const depCountB = b.dependencies?.length || 0;
|
||||||
|
if (depCountA !== depCountB) return depCountA - depCountB;
|
||||||
|
|
||||||
|
return String(a.id).localeCompare(String(b.id));
|
||||||
|
});
|
||||||
|
return candidateSubtasks[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to finding eligible top-level tasks
|
||||||
|
const eligibleTasks = tasks.filter(task => {
|
||||||
|
// Skip non-eligible statuses
|
||||||
|
const status = (task.status || 'pending').toLowerCase();
|
||||||
|
if (status !== 'pending' && status !== 'in-progress') return false;
|
||||||
|
|
||||||
|
// Check dependencies
|
||||||
|
const deps = task.dependencies || [];
|
||||||
|
const depsSatisfied = deps.length === 0 ||
|
||||||
|
deps.every(depId => completedIds.has(String(depId)));
|
||||||
|
|
||||||
|
return depsSatisfied;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (eligibleTasks.length === 0) return undefined;
|
||||||
|
|
||||||
|
// Sort eligible tasks
|
||||||
|
eligibleTasks.sort((a, b) => {
|
||||||
|
// Priority (higher first)
|
||||||
|
const pa = priorityValues[a.priority || 'medium'] ?? 2;
|
||||||
|
const pb = priorityValues[b.priority || 'medium'] ?? 2;
|
||||||
|
if (pb !== pa) return pb - pa;
|
||||||
|
|
||||||
|
// Dependencies count (fewer first)
|
||||||
|
const depCountA = a.dependencies?.length || 0;
|
||||||
|
const depCountB = b.dependencies?.length || 0;
|
||||||
|
if (depCountA !== depCountB) return depCountA - depCountB;
|
||||||
|
|
||||||
|
// ID (lower first)
|
||||||
|
return Number(a.id) - Number(b.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
const nextTask = eligibleTasks[0];
|
||||||
|
return {
|
||||||
|
id: nextTask.id,
|
||||||
|
title: nextTask.title,
|
||||||
|
priority: nextTask.priority,
|
||||||
|
dependencies: nextTask.dependencies?.map(d => String(d))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the last result (for programmatic usage)
|
* Get the last result (for programmatic usage)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -46,13 +46,113 @@ export interface NextTaskInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a progress bar with percentage
|
* Status breakdown for progress bars
|
||||||
*/
|
*/
|
||||||
function createProgressBar(percentage: number, width: number = 30): string {
|
export interface StatusBreakdown {
|
||||||
const filled = Math.round((percentage / 100) * width);
|
'in-progress'?: number;
|
||||||
const empty = width - filled;
|
pending?: number;
|
||||||
|
blocked?: number;
|
||||||
|
deferred?: number;
|
||||||
|
cancelled?: number;
|
||||||
|
review?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a progress bar with color-coded status segments
|
||||||
|
*/
|
||||||
|
function createProgressBar(
|
||||||
|
completionPercentage: number,
|
||||||
|
width: number = 30,
|
||||||
|
statusBreakdown?: StatusBreakdown
|
||||||
|
): string {
|
||||||
|
// If no breakdown provided, use simple green bar
|
||||||
|
if (!statusBreakdown) {
|
||||||
|
const filled = Math.round((completionPercentage / 100) * width);
|
||||||
|
const empty = width - filled;
|
||||||
|
return chalk.green('█').repeat(filled) + chalk.gray('░').repeat(empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the bar with different colored sections
|
||||||
|
// Order matches the status display: Done, Cancelled, Deferred, In Progress, Review, Pending, Blocked
|
||||||
|
let bar = '';
|
||||||
|
let charsUsed = 0;
|
||||||
|
|
||||||
|
// 1. Green filled blocks for completed tasks (done)
|
||||||
|
const completedChars = Math.round((completionPercentage / 100) * width);
|
||||||
|
if (completedChars > 0) {
|
||||||
|
bar += chalk.green('█').repeat(completedChars);
|
||||||
|
charsUsed += completedChars;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Gray filled blocks for cancelled (won't be done)
|
||||||
|
if (statusBreakdown.cancelled && charsUsed < width) {
|
||||||
|
const cancelledChars = Math.round(
|
||||||
|
(statusBreakdown.cancelled / 100) * width
|
||||||
|
);
|
||||||
|
const actualChars = Math.min(cancelledChars, width - charsUsed);
|
||||||
|
if (actualChars > 0) {
|
||||||
|
bar += chalk.gray('█').repeat(actualChars);
|
||||||
|
charsUsed += actualChars;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Gray filled blocks for deferred (won't be done now)
|
||||||
|
if (statusBreakdown.deferred && charsUsed < width) {
|
||||||
|
const deferredChars = Math.round((statusBreakdown.deferred / 100) * width);
|
||||||
|
const actualChars = Math.min(deferredChars, width - charsUsed);
|
||||||
|
if (actualChars > 0) {
|
||||||
|
bar += chalk.gray('█').repeat(actualChars);
|
||||||
|
charsUsed += actualChars;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Blue filled blocks for in-progress (actively working)
|
||||||
|
if (statusBreakdown['in-progress'] && charsUsed < width) {
|
||||||
|
const inProgressChars = Math.round(
|
||||||
|
(statusBreakdown['in-progress'] / 100) * width
|
||||||
|
);
|
||||||
|
const actualChars = Math.min(inProgressChars, width - charsUsed);
|
||||||
|
if (actualChars > 0) {
|
||||||
|
bar += chalk.blue('█').repeat(actualChars);
|
||||||
|
charsUsed += actualChars;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Magenta empty blocks for review (almost done)
|
||||||
|
if (statusBreakdown.review && charsUsed < width) {
|
||||||
|
const reviewChars = Math.round((statusBreakdown.review / 100) * width);
|
||||||
|
const actualChars = Math.min(reviewChars, width - charsUsed);
|
||||||
|
if (actualChars > 0) {
|
||||||
|
bar += chalk.magenta('░').repeat(actualChars);
|
||||||
|
charsUsed += actualChars;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Yellow empty blocks for pending (ready to start)
|
||||||
|
if (statusBreakdown.pending && charsUsed < width) {
|
||||||
|
const pendingChars = Math.round((statusBreakdown.pending / 100) * width);
|
||||||
|
const actualChars = Math.min(pendingChars, width - charsUsed);
|
||||||
|
if (actualChars > 0) {
|
||||||
|
bar += chalk.yellow('░').repeat(actualChars);
|
||||||
|
charsUsed += actualChars;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Red empty blocks for blocked (can't start yet)
|
||||||
|
if (statusBreakdown.blocked && charsUsed < width) {
|
||||||
|
const blockedChars = Math.round((statusBreakdown.blocked / 100) * width);
|
||||||
|
const actualChars = Math.min(blockedChars, width - charsUsed);
|
||||||
|
if (actualChars > 0) {
|
||||||
|
bar += chalk.red('░').repeat(actualChars);
|
||||||
|
charsUsed += actualChars;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill any remaining space with gray empty yellow blocks
|
||||||
|
if (charsUsed < width) {
|
||||||
|
bar += chalk.yellow('░').repeat(width - charsUsed);
|
||||||
|
}
|
||||||
|
|
||||||
const bar = chalk.green('█').repeat(filled) + chalk.gray('░').repeat(empty);
|
|
||||||
return bar;
|
return bar;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +172,7 @@ export function calculateTaskStatistics(tasks: Task[]): TaskStatistics {
|
|||||||
completionPercentage: 0
|
completionPercentage: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
tasks.forEach(task => {
|
tasks.forEach((task) => {
|
||||||
switch (task.status) {
|
switch (task.status) {
|
||||||
case 'done':
|
case 'done':
|
||||||
stats.done++;
|
stats.done++;
|
||||||
@@ -98,9 +198,8 @@ export function calculateTaskStatistics(tasks: Task[]): TaskStatistics {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
stats.completionPercentage = stats.total > 0
|
stats.completionPercentage =
|
||||||
? Math.round((stats.done / stats.total) * 100)
|
stats.total > 0 ? Math.round((stats.done / stats.total) * 100) : 0;
|
||||||
: 0;
|
|
||||||
|
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
@@ -121,9 +220,9 @@ export function calculateSubtaskStatistics(tasks: Task[]): TaskStatistics {
|
|||||||
completionPercentage: 0
|
completionPercentage: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
tasks.forEach(task => {
|
tasks.forEach((task) => {
|
||||||
if (task.subtasks && task.subtasks.length > 0) {
|
if (task.subtasks && task.subtasks.length > 0) {
|
||||||
task.subtasks.forEach(subtask => {
|
task.subtasks.forEach((subtask) => {
|
||||||
stats.total++;
|
stats.total++;
|
||||||
switch (subtask.status) {
|
switch (subtask.status) {
|
||||||
case 'done':
|
case 'done':
|
||||||
@@ -152,9 +251,8 @@ export function calculateSubtaskStatistics(tasks: Task[]): TaskStatistics {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
stats.completionPercentage = stats.total > 0
|
stats.completionPercentage =
|
||||||
? Math.round((stats.done / stats.total) * 100)
|
stats.total > 0 ? Math.round((stats.done / stats.total) * 100) : 0;
|
||||||
: 0;
|
|
||||||
|
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
@@ -162,34 +260,39 @@ export function calculateSubtaskStatistics(tasks: Task[]): TaskStatistics {
|
|||||||
/**
|
/**
|
||||||
* Calculate dependency statistics
|
* Calculate dependency statistics
|
||||||
*/
|
*/
|
||||||
export function calculateDependencyStatistics(tasks: Task[]): DependencyStatistics {
|
export function calculateDependencyStatistics(
|
||||||
|
tasks: Task[]
|
||||||
|
): DependencyStatistics {
|
||||||
const completedTaskIds = new Set(
|
const completedTaskIds = new Set(
|
||||||
tasks.filter(t => t.status === 'done').map(t => t.id)
|
tasks.filter((t) => t.status === 'done').map((t) => t.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
const tasksWithNoDeps = tasks.filter(
|
const tasksWithNoDeps = tasks.filter(
|
||||||
t => t.status !== 'done' && (!t.dependencies || t.dependencies.length === 0)
|
(t) =>
|
||||||
|
t.status !== 'done' && (!t.dependencies || t.dependencies.length === 0)
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
const tasksWithAllDepsSatisfied = tasks.filter(
|
const tasksWithAllDepsSatisfied = tasks.filter(
|
||||||
t => t.status !== 'done' &&
|
(t) =>
|
||||||
|
t.status !== 'done' &&
|
||||||
t.dependencies &&
|
t.dependencies &&
|
||||||
t.dependencies.length > 0 &&
|
t.dependencies.length > 0 &&
|
||||||
t.dependencies.every(depId => completedTaskIds.has(depId))
|
t.dependencies.every((depId) => completedTaskIds.has(depId))
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
const tasksBlockedByDeps = tasks.filter(
|
const tasksBlockedByDeps = tasks.filter(
|
||||||
t => t.status !== 'done' &&
|
(t) =>
|
||||||
|
t.status !== 'done' &&
|
||||||
t.dependencies &&
|
t.dependencies &&
|
||||||
t.dependencies.length > 0 &&
|
t.dependencies.length > 0 &&
|
||||||
!t.dependencies.every(depId => completedTaskIds.has(depId))
|
!t.dependencies.every((depId) => completedTaskIds.has(depId))
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
// Calculate most depended-on task
|
// Calculate most depended-on task
|
||||||
const dependencyCount: Record<string, number> = {};
|
const dependencyCount: Record<string, number> = {};
|
||||||
tasks.forEach(task => {
|
tasks.forEach((task) => {
|
||||||
if (task.dependencies && task.dependencies.length > 0) {
|
if (task.dependencies && task.dependencies.length > 0) {
|
||||||
task.dependencies.forEach(depId => {
|
task.dependencies.forEach((depId) => {
|
||||||
const key = String(depId);
|
const key = String(depId);
|
||||||
dependencyCount[key] = (dependencyCount[key] || 0) + 1;
|
dependencyCount[key] = (dependencyCount[key] || 0) + 1;
|
||||||
});
|
});
|
||||||
@@ -211,9 +314,8 @@ export function calculateDependencyStatistics(tasks: Task[]): DependencyStatisti
|
|||||||
(sum, task) => sum + (task.dependencies ? task.dependencies.length : 0),
|
(sum, task) => sum + (task.dependencies ? task.dependencies.length : 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
const avgDependenciesPerTask = tasks.length > 0
|
const avgDependenciesPerTask =
|
||||||
? totalDependencies / tasks.length
|
tasks.length > 0 ? totalDependencies / tasks.length : 0;
|
||||||
: 0;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tasksWithNoDeps,
|
tasksWithNoDeps,
|
||||||
@@ -228,7 +330,9 @@ export function calculateDependencyStatistics(tasks: Task[]): DependencyStatisti
|
|||||||
/**
|
/**
|
||||||
* Get priority counts
|
* Get priority counts
|
||||||
*/
|
*/
|
||||||
export function getPriorityBreakdown(tasks: Task[]): Record<TaskPriority, number> {
|
export function getPriorityBreakdown(
|
||||||
|
tasks: Task[]
|
||||||
|
): Record<TaskPriority, number> {
|
||||||
const breakdown: Record<TaskPriority, number> = {
|
const breakdown: Record<TaskPriority, number> = {
|
||||||
critical: 0,
|
critical: 0,
|
||||||
high: 0,
|
high: 0,
|
||||||
@@ -236,7 +340,7 @@ export function getPriorityBreakdown(tasks: Task[]): Record<TaskPriority, number
|
|||||||
low: 0
|
low: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
tasks.forEach(task => {
|
tasks.forEach((task) => {
|
||||||
const priority = task.priority || 'medium';
|
const priority = task.priority || 'medium';
|
||||||
breakdown[priority]++;
|
breakdown[priority]++;
|
||||||
});
|
});
|
||||||
@@ -244,6 +348,57 @@ export function getPriorityBreakdown(tasks: Task[]): Record<TaskPriority, number
|
|||||||
return breakdown;
|
return breakdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate status breakdown as percentages
|
||||||
|
*/
|
||||||
|
function calculateStatusBreakdown(stats: TaskStatistics): StatusBreakdown {
|
||||||
|
if (stats.total === 0) return {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
'in-progress': (stats.inProgress / stats.total) * 100,
|
||||||
|
pending: (stats.pending / stats.total) * 100,
|
||||||
|
blocked: (stats.blocked / stats.total) * 100,
|
||||||
|
deferred: (stats.deferred / stats.total) * 100,
|
||||||
|
cancelled: (stats.cancelled / stats.total) * 100,
|
||||||
|
review: ((stats.review || 0) / stats.total) * 100
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format status counts in the correct order with colors
|
||||||
|
* @param stats - The statistics object containing counts
|
||||||
|
* @param isSubtask - Whether this is for subtasks (affects "Done" vs "Completed" label)
|
||||||
|
*/
|
||||||
|
function formatStatusLine(
|
||||||
|
stats: TaskStatistics,
|
||||||
|
isSubtask: boolean = false
|
||||||
|
): string {
|
||||||
|
const parts: string[] = [];
|
||||||
|
|
||||||
|
// Order: Done, Cancelled, Deferred, In Progress, Review, Pending, Blocked
|
||||||
|
if (isSubtask) {
|
||||||
|
parts.push(`Completed: ${chalk.green(`${stats.done}/${stats.total}`)}`);
|
||||||
|
} else {
|
||||||
|
parts.push(`Done: ${chalk.green(stats.done)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
parts.push(`Cancelled: ${chalk.gray(stats.cancelled)}`);
|
||||||
|
parts.push(`Deferred: ${chalk.gray(stats.deferred)}`);
|
||||||
|
|
||||||
|
// Add line break for second row
|
||||||
|
const firstLine = parts.join(' ');
|
||||||
|
parts.length = 0;
|
||||||
|
|
||||||
|
parts.push(`In Progress: ${chalk.blue(stats.inProgress)}`);
|
||||||
|
parts.push(`Review: ${chalk.magenta(stats.review || 0)}`);
|
||||||
|
parts.push(`Pending: ${chalk.yellow(stats.pending)}`);
|
||||||
|
parts.push(`Blocked: ${chalk.red(stats.blocked)}`);
|
||||||
|
|
||||||
|
const secondLine = parts.join(' ');
|
||||||
|
|
||||||
|
return firstLine + '\n' + secondLine;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the project dashboard box
|
* Display the project dashboard box
|
||||||
*/
|
*/
|
||||||
@@ -252,21 +407,36 @@ export function displayProjectDashboard(
|
|||||||
subtaskStats: TaskStatistics,
|
subtaskStats: TaskStatistics,
|
||||||
priorityBreakdown: Record<TaskPriority, number>
|
priorityBreakdown: Record<TaskPriority, number>
|
||||||
): string {
|
): string {
|
||||||
const taskProgressBar = createProgressBar(taskStats.completionPercentage);
|
// Calculate status breakdowns using the helper function
|
||||||
const subtaskProgressBar = createProgressBar(subtaskStats.completionPercentage);
|
const taskStatusBreakdown = calculateStatusBreakdown(taskStats);
|
||||||
|
const subtaskStatusBreakdown = calculateStatusBreakdown(subtaskStats);
|
||||||
|
|
||||||
const taskPercentage = `${taskStats.completionPercentage}% ${taskStats.done}%`;
|
// Create progress bars with the breakdowns
|
||||||
const subtaskPercentage = `${subtaskStats.completionPercentage}% ${subtaskStats.done}%`;
|
const taskProgressBar = createProgressBar(
|
||||||
|
taskStats.completionPercentage,
|
||||||
|
30,
|
||||||
|
taskStatusBreakdown
|
||||||
|
);
|
||||||
|
const subtaskProgressBar = createProgressBar(
|
||||||
|
subtaskStats.completionPercentage,
|
||||||
|
30,
|
||||||
|
subtaskStatusBreakdown
|
||||||
|
);
|
||||||
|
|
||||||
|
const taskPercentage = `${taskStats.completionPercentage}% ${taskStats.done}/${taskStats.total}`;
|
||||||
|
const subtaskPercentage = `${subtaskStats.completionPercentage}% ${subtaskStats.done}/${subtaskStats.total}`;
|
||||||
|
|
||||||
const content =
|
const content =
|
||||||
chalk.white.bold('Project Dashboard') + '\n' +
|
chalk.white.bold('Project Dashboard') +
|
||||||
|
'\n' +
|
||||||
`Tasks Progress: ${taskProgressBar} ${chalk.yellow(taskPercentage)}\n` +
|
`Tasks Progress: ${taskProgressBar} ${chalk.yellow(taskPercentage)}\n` +
|
||||||
`Done: ${chalk.green(taskStats.done)} In Progress: ${chalk.blue(taskStats.inProgress)} Pending: ${chalk.yellow(taskStats.pending)} Blocked: ${chalk.red(taskStats.blocked)} Deferred: ${chalk.gray(taskStats.deferred)}\n` +
|
formatStatusLine(taskStats, false) +
|
||||||
`Cancelled: ${chalk.gray(taskStats.cancelled)}\n\n` +
|
'\n\n' +
|
||||||
`Subtasks Progress: ${subtaskProgressBar} ${chalk.cyan(subtaskPercentage)}\n` +
|
`Subtasks Progress: ${subtaskProgressBar} ${chalk.cyan(subtaskPercentage)}\n` +
|
||||||
`Completed: ${chalk.green(`${subtaskStats.done}/${subtaskStats.total}`)} In Progress: ${chalk.blue(subtaskStats.inProgress)} Pending: ${chalk.yellow(subtaskStats.pending)} Blocked: ${chalk.red(subtaskStats.blocked)}\n` +
|
formatStatusLine(subtaskStats, true) +
|
||||||
`Deferred: ${chalk.gray(subtaskStats.deferred)} Cancelled: ${chalk.gray(subtaskStats.cancelled)}\n\n` +
|
'\n\n' +
|
||||||
chalk.cyan.bold('Priority Breakdown:') + '\n' +
|
chalk.cyan.bold('Priority Breakdown:') +
|
||||||
|
'\n' +
|
||||||
`${chalk.red('•')} ${chalk.white('High priority:')} ${priorityBreakdown.high}\n` +
|
`${chalk.red('•')} ${chalk.white('High priority:')} ${priorityBreakdown.high}\n` +
|
||||||
`${chalk.yellow('•')} ${chalk.white('Medium priority:')} ${priorityBreakdown.medium}\n` +
|
`${chalk.yellow('•')} ${chalk.white('Medium priority:')} ${priorityBreakdown.medium}\n` +
|
||||||
`${chalk.green('•')} ${chalk.white('Low priority:')} ${priorityBreakdown.low}`;
|
`${chalk.green('•')} ${chalk.white('Low priority:')} ${priorityBreakdown.low}`;
|
||||||
@@ -282,23 +452,32 @@ export function displayDependencyDashboard(
|
|||||||
nextTask?: NextTaskInfo
|
nextTask?: NextTaskInfo
|
||||||
): string {
|
): string {
|
||||||
const content =
|
const content =
|
||||||
chalk.white.bold('Dependency Status & Next Task') + '\n' +
|
chalk.white.bold('Dependency Status & Next Task') +
|
||||||
chalk.cyan.bold('Dependency Metrics:') + '\n' +
|
'\n' +
|
||||||
|
chalk.cyan.bold('Dependency Metrics:') +
|
||||||
|
'\n' +
|
||||||
`${chalk.green('•')} ${chalk.white('Tasks with no dependencies:')} ${depStats.tasksWithNoDeps}\n` +
|
`${chalk.green('•')} ${chalk.white('Tasks with no dependencies:')} ${depStats.tasksWithNoDeps}\n` +
|
||||||
`${chalk.green('•')} ${chalk.white('Tasks ready to work on:')} ${depStats.tasksReadyToWork}\n` +
|
`${chalk.green('•')} ${chalk.white('Tasks ready to work on:')} ${depStats.tasksReadyToWork}\n` +
|
||||||
`${chalk.yellow('•')} ${chalk.white('Tasks blocked by dependencies:')} ${depStats.tasksBlockedByDeps}\n` +
|
`${chalk.yellow('•')} ${chalk.white('Tasks blocked by dependencies:')} ${depStats.tasksBlockedByDeps}\n` +
|
||||||
`${chalk.magenta('•')} ${chalk.white('Most depended-on task:')} ${
|
`${chalk.magenta('•')} ${chalk.white('Most depended-on task:')} ${
|
||||||
depStats.mostDependedOnTaskId
|
depStats.mostDependedOnTaskId
|
||||||
? chalk.cyan(`#${depStats.mostDependedOnTaskId} (${depStats.mostDependedOnCount} dependents)`)
|
? chalk.cyan(
|
||||||
|
`#${depStats.mostDependedOnTaskId} (${depStats.mostDependedOnCount} dependents)`
|
||||||
|
)
|
||||||
: chalk.gray('None')
|
: chalk.gray('None')
|
||||||
}\n` +
|
}\n` +
|
||||||
`${chalk.blue('•')} ${chalk.white('Avg dependencies per task:')} ${depStats.avgDependenciesPerTask.toFixed(1)}\n\n` +
|
`${chalk.blue('•')} ${chalk.white('Avg dependencies per task:')} ${depStats.avgDependenciesPerTask.toFixed(1)}\n\n` +
|
||||||
chalk.cyan.bold('Next Task to Work On:') + '\n' +
|
chalk.cyan.bold('Next Task to Work On:') +
|
||||||
|
'\n' +
|
||||||
`ID: ${nextTask ? chalk.cyan(String(nextTask.id)) : chalk.gray('N/A')} - ${
|
`ID: ${nextTask ? chalk.cyan(String(nextTask.id)) : chalk.gray('N/A')} - ${
|
||||||
nextTask ? chalk.white.bold(nextTask.title) : chalk.yellow('No task available')
|
nextTask
|
||||||
|
? chalk.white.bold(nextTask.title)
|
||||||
|
: chalk.yellow('No task available')
|
||||||
}\n` +
|
}\n` +
|
||||||
`Priority: ${nextTask?.priority || chalk.gray('N/A')} Dependencies: ${
|
`Priority: ${nextTask?.priority || chalk.gray('N/A')} Dependencies: ${
|
||||||
nextTask?.dependencies?.length ? chalk.cyan(nextTask.dependencies.join(', ')) : chalk.gray('None')
|
nextTask?.dependencies?.length
|
||||||
|
? chalk.cyan(nextTask.dependencies.join(', '))
|
||||||
|
: chalk.gray('None')
|
||||||
}\n` +
|
}\n` +
|
||||||
`Complexity: ${nextTask?.complexity || chalk.gray('N/A')}`;
|
`Complexity: ${nextTask?.complexity || chalk.gray('N/A')}`;
|
||||||
|
|
||||||
@@ -315,8 +494,15 @@ export function displayDashboards(
|
|||||||
depStats: DependencyStatistics,
|
depStats: DependencyStatistics,
|
||||||
nextTask?: NextTaskInfo
|
nextTask?: NextTaskInfo
|
||||||
): void {
|
): void {
|
||||||
const projectDashboardContent = displayProjectDashboard(taskStats, subtaskStats, priorityBreakdown);
|
const projectDashboardContent = displayProjectDashboard(
|
||||||
const dependencyDashboardContent = displayDependencyDashboard(depStats, nextTask);
|
taskStats,
|
||||||
|
subtaskStats,
|
||||||
|
priorityBreakdown
|
||||||
|
);
|
||||||
|
const dependencyDashboardContent = displayDependencyDashboard(
|
||||||
|
depStats,
|
||||||
|
nextTask
|
||||||
|
);
|
||||||
|
|
||||||
// Get terminal width
|
// Get terminal width
|
||||||
const terminalWidth = process.stdout.columns || 80;
|
const terminalWidth = process.stdout.columns || 80;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import chalk from 'chalk';
|
|||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import figlet from 'figlet';
|
import figlet from 'figlet';
|
||||||
import gradient from 'gradient-string';
|
import gradient from 'gradient-string';
|
||||||
|
import packageJson from '../../../package.json';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Header configuration options
|
* Header configuration options
|
||||||
@@ -40,7 +41,7 @@ function createBanner(): string {
|
|||||||
*/
|
*/
|
||||||
export function displayHeader(options: HeaderOptions = {}): void {
|
export function displayHeader(options: HeaderOptions = {}): void {
|
||||||
const {
|
const {
|
||||||
version = '0.26.0',
|
version = packageJson.version,
|
||||||
projectName = 'Taskmaster',
|
projectName = 'Taskmaster',
|
||||||
tag,
|
tag,
|
||||||
filePath,
|
filePath,
|
||||||
@@ -84,9 +85,11 @@ export function displayHeader(options: HeaderOptions = {}): void {
|
|||||||
console.log(tagInfo);
|
console.log(tagInfo);
|
||||||
|
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
console.log(
|
// Convert to absolute path if it's relative
|
||||||
`Listing tasks from: ${chalk.dim(filePath)}`
|
const absolutePath = filePath.startsWith('/')
|
||||||
);
|
? filePath
|
||||||
|
: `${process.cwd()}/${filePath}`;
|
||||||
|
console.log(`Listing tasks from: ${chalk.dim(absolutePath)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(); // Empty line for spacing
|
console.log(); // Empty line for spacing
|
||||||
|
|||||||
@@ -20,7 +20,11 @@
|
|||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"types": ["node"]
|
"types": ["node"],
|
||||||
|
"paths": {
|
||||||
|
"@tm/core": ["../../packages/tm-core/src/index.ts"],
|
||||||
|
"@tm/core/*": ["../../packages/tm-core/src/*"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules", "dist", "tests"]
|
"exclude": ["node_modules", "dist", "tests"]
|
||||||
|
|||||||
@@ -20,357 +20,8 @@
|
|||||||
* Main entry point for globally installed package
|
* Main entry point for globally installed package
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { fileURLToPath } from 'url';
|
// Direct imports instead of spawning child processes
|
||||||
import { dirname, resolve } from 'path';
|
import { runCLI } from '../scripts/modules/commands.js';
|
||||||
import { createRequire } from 'module';
|
|
||||||
import { spawn } from 'child_process';
|
|
||||||
import { Command } from 'commander';
|
|
||||||
import { displayHelp, displayBanner } from '../scripts/modules/ui.js';
|
|
||||||
import { registerCommands } from '../scripts/modules/commands.js';
|
|
||||||
import { detectCamelCaseFlags } from '../scripts/modules/utils.js';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
// Simply run the CLI directly
|
||||||
const __dirname = dirname(__filename);
|
runCLI();
|
||||||
const require = createRequire(import.meta.url);
|
|
||||||
|
|
||||||
// Get package information
|
|
||||||
const packageJson = require('../package.json');
|
|
||||||
const version = packageJson.version;
|
|
||||||
|
|
||||||
// Get paths to script files
|
|
||||||
const devScriptPath = resolve(__dirname, '../scripts/dev.js');
|
|
||||||
const initScriptPath = resolve(__dirname, '../scripts/init.js');
|
|
||||||
|
|
||||||
// Helper function to run dev.js with arguments
|
|
||||||
function runDevScript(args) {
|
|
||||||
// Debug: Show the transformed arguments when DEBUG=1 is set
|
|
||||||
if (process.env.DEBUG === '1') {
|
|
||||||
console.error('\nDEBUG - CLI Wrapper Analysis:');
|
|
||||||
console.error('- Original command: ' + process.argv.join(' '));
|
|
||||||
console.error('- Transformed args: ' + args.join(' '));
|
|
||||||
console.error(
|
|
||||||
'- dev.js will receive: node ' +
|
|
||||||
devScriptPath +
|
|
||||||
' ' +
|
|
||||||
args.join(' ') +
|
|
||||||
'\n'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For testing: If TEST_MODE is set, just print args and exit
|
|
||||||
if (process.env.TEST_MODE === '1') {
|
|
||||||
console.log('Would execute:');
|
|
||||||
console.log(`node ${devScriptPath} ${args.join(' ')}`);
|
|
||||||
process.exit(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const child = spawn('node', [devScriptPath, ...args], {
|
|
||||||
stdio: 'inherit',
|
|
||||||
cwd: process.cwd()
|
|
||||||
});
|
|
||||||
|
|
||||||
child.on('close', (code) => {
|
|
||||||
process.exit(code);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to detect camelCase and convert to kebab-case
|
|
||||||
const toKebabCase = (str) => str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a wrapper action that passes the command to dev.js
|
|
||||||
* @param {string} commandName - The name of the command
|
|
||||||
* @returns {Function} Wrapper action function
|
|
||||||
*/
|
|
||||||
function createDevScriptAction(commandName) {
|
|
||||||
return (options, cmd) => {
|
|
||||||
// Check for camelCase flags and error out with helpful message
|
|
||||||
const camelCaseFlags = detectCamelCaseFlags(process.argv);
|
|
||||||
|
|
||||||
// If camelCase flags were found, show error and exit
|
|
||||||
if (camelCaseFlags.length > 0) {
|
|
||||||
console.error('\nError: Please use kebab-case for CLI flags:');
|
|
||||||
camelCaseFlags.forEach((flag) => {
|
|
||||||
console.error(` Instead of: --${flag.original}`);
|
|
||||||
console.error(` Use: --${flag.kebabCase}`);
|
|
||||||
});
|
|
||||||
console.error(
|
|
||||||
'\nExample: task-master parse-prd --num-tasks=5 instead of --numTasks=5\n'
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since we've ensured no camelCase flags, we can now just:
|
|
||||||
// 1. Start with the command name
|
|
||||||
const args = [commandName];
|
|
||||||
|
|
||||||
// 3. Get positional arguments and explicit flags from the command line
|
|
||||||
const commandArgs = [];
|
|
||||||
const positionals = new Set(); // Track positional args we've seen
|
|
||||||
|
|
||||||
// Find the command in raw process.argv to extract args
|
|
||||||
const commandIndex = process.argv.indexOf(commandName);
|
|
||||||
if (commandIndex !== -1) {
|
|
||||||
// Process all args after the command name
|
|
||||||
for (let i = commandIndex + 1; i < process.argv.length; i++) {
|
|
||||||
const arg = process.argv[i];
|
|
||||||
|
|
||||||
if (arg.startsWith('--')) {
|
|
||||||
// It's a flag - pass through as is
|
|
||||||
commandArgs.push(arg);
|
|
||||||
// Skip the next arg if this is a flag with a value (not --flag=value format)
|
|
||||||
if (
|
|
||||||
!arg.includes('=') &&
|
|
||||||
i + 1 < process.argv.length &&
|
|
||||||
!process.argv[i + 1].startsWith('--')
|
|
||||||
) {
|
|
||||||
commandArgs.push(process.argv[++i]);
|
|
||||||
}
|
|
||||||
} else if (!positionals.has(arg)) {
|
|
||||||
// It's a positional argument we haven't seen
|
|
||||||
commandArgs.push(arg);
|
|
||||||
positionals.add(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all command line args we collected
|
|
||||||
args.push(...commandArgs);
|
|
||||||
|
|
||||||
// 4. Add default options from Commander if not specified on command line
|
|
||||||
// Track which options we've seen on the command line
|
|
||||||
const userOptions = new Set();
|
|
||||||
for (const arg of commandArgs) {
|
|
||||||
if (arg.startsWith('--')) {
|
|
||||||
// Extract option name (without -- and value)
|
|
||||||
const name = arg.split('=')[0].slice(2);
|
|
||||||
userOptions.add(name);
|
|
||||||
|
|
||||||
// Add the kebab-case version too, to prevent duplicates
|
|
||||||
const kebabName = name.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
||||||
userOptions.add(kebabName);
|
|
||||||
|
|
||||||
// Add the camelCase version as well
|
|
||||||
const camelName = kebabName.replace(/-([a-z])/g, (_, letter) =>
|
|
||||||
letter.toUpperCase()
|
|
||||||
);
|
|
||||||
userOptions.add(camelName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add Commander-provided defaults for options not specified by user
|
|
||||||
Object.entries(options).forEach(([key, value]) => {
|
|
||||||
// Debug output to see what keys we're getting
|
|
||||||
if (process.env.DEBUG === '1') {
|
|
||||||
console.error(`DEBUG - Processing option: ${key} = ${value}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case for numTasks > num-tasks (a known problem case)
|
|
||||||
if (key === 'numTasks') {
|
|
||||||
if (process.env.DEBUG === '1') {
|
|
||||||
console.error('DEBUG - Converting numTasks to num-tasks');
|
|
||||||
}
|
|
||||||
if (!userOptions.has('num-tasks') && !userOptions.has('numTasks')) {
|
|
||||||
args.push(`--num-tasks=${value}`);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip built-in Commander properties and options the user provided
|
|
||||||
if (
|
|
||||||
['parent', 'commands', 'options', 'rawArgs'].includes(key) ||
|
|
||||||
userOptions.has(key)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also check the kebab-case version of this key
|
|
||||||
const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
||||||
if (userOptions.has(kebabKey)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add default values, using kebab-case for the parameter name
|
|
||||||
if (value !== undefined) {
|
|
||||||
if (typeof value === 'boolean') {
|
|
||||||
if (value === true) {
|
|
||||||
args.push(`--${kebabKey}`);
|
|
||||||
} else if (value === false && key === 'generate') {
|
|
||||||
args.push('--skip-generate');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Always use kebab-case for option names
|
|
||||||
args.push(`--${kebabKey}=${value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Special handling for parent parameter (uses -p)
|
|
||||||
if (options.parent && !args.includes('-p') && !userOptions.has('parent')) {
|
|
||||||
args.push('-p', options.parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug output for troubleshooting
|
|
||||||
if (process.env.DEBUG === '1') {
|
|
||||||
console.error('DEBUG - Command args:', commandArgs);
|
|
||||||
console.error('DEBUG - User options:', Array.from(userOptions));
|
|
||||||
console.error('DEBUG - Commander options:', options);
|
|
||||||
console.error('DEBUG - Final args:', args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the script with our processed args
|
|
||||||
runDevScript(args);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// // Special case for the 'init' command which uses a different script
|
|
||||||
// function registerInitCommand(program) {
|
|
||||||
// program
|
|
||||||
// .command('init')
|
|
||||||
// .description('Initialize a new project')
|
|
||||||
// .option('-y, --yes', 'Skip prompts and use default values')
|
|
||||||
// .option('-n, --name <name>', 'Project name')
|
|
||||||
// .option('-d, --description <description>', 'Project description')
|
|
||||||
// .option('-v, --version <version>', 'Project version')
|
|
||||||
// .option('-a, --author <author>', 'Author name')
|
|
||||||
// .option('--skip-install', 'Skip installing dependencies')
|
|
||||||
// .option('--dry-run', 'Show what would be done without making changes')
|
|
||||||
// .action((options) => {
|
|
||||||
// // Pass through any options to the init script
|
|
||||||
// const args = [
|
|
||||||
// '--yes',
|
|
||||||
// 'name',
|
|
||||||
// 'description',
|
|
||||||
// 'version',
|
|
||||||
// 'author',
|
|
||||||
// 'skip-install',
|
|
||||||
// 'dry-run'
|
|
||||||
// ]
|
|
||||||
// .filter((opt) => options[opt])
|
|
||||||
// .map((opt) => {
|
|
||||||
// if (opt === 'yes' || opt === 'skip-install' || opt === 'dry-run') {
|
|
||||||
// return `--${opt}`;
|
|
||||||
// }
|
|
||||||
// return `--${opt}=${options[opt]}`;
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const child = spawn('node', [initScriptPath, ...args], {
|
|
||||||
// stdio: 'inherit',
|
|
||||||
// cwd: process.cwd()
|
|
||||||
// });
|
|
||||||
|
|
||||||
// child.on('close', (code) => {
|
|
||||||
// process.exit(code);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Set up the command-line interface
|
|
||||||
const program = new Command();
|
|
||||||
|
|
||||||
program
|
|
||||||
.name('task-master')
|
|
||||||
.description('Claude Task Master CLI')
|
|
||||||
.version(version)
|
|
||||||
.addHelpText('afterAll', () => {
|
|
||||||
// Use the same help display function as dev.js for consistency
|
|
||||||
displayHelp();
|
|
||||||
return ''; // Return empty string to prevent commander's default help
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add custom help option to directly call our help display
|
|
||||||
program.helpOption('-h, --help', 'Display help information');
|
|
||||||
program.on('--help', () => {
|
|
||||||
displayHelp();
|
|
||||||
});
|
|
||||||
|
|
||||||
// // Add special case commands
|
|
||||||
// registerInitCommand(program);
|
|
||||||
|
|
||||||
program
|
|
||||||
.command('dev')
|
|
||||||
.description('Run the dev.js script')
|
|
||||||
.action(() => {
|
|
||||||
const args = process.argv.slice(process.argv.indexOf('dev') + 1);
|
|
||||||
runDevScript(args);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use a temporary Command instance to get all command definitions
|
|
||||||
const tempProgram = new Command();
|
|
||||||
registerCommands(tempProgram);
|
|
||||||
|
|
||||||
// For each command in the temp instance, add a modified version to our actual program
|
|
||||||
tempProgram.commands.forEach((cmd) => {
|
|
||||||
if (['dev'].includes(cmd.name())) {
|
|
||||||
// Skip commands we've already defined specially
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new command with the same name and description
|
|
||||||
const newCmd = program.command(cmd.name()).description(cmd.description());
|
|
||||||
|
|
||||||
// Copy all options
|
|
||||||
cmd.options.forEach((opt) => {
|
|
||||||
newCmd.option(opt.flags, opt.description, opt.defaultValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set the action to proxy to dev.js
|
|
||||||
newCmd.action(createDevScriptAction(cmd.name()));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Parse the command line arguments
|
|
||||||
program.parse(process.argv);
|
|
||||||
|
|
||||||
// Add global error handling for unknown commands and options
|
|
||||||
process.on('uncaughtException', (err) => {
|
|
||||||
// Check if this is a commander.js unknown option error
|
|
||||||
if (err.code === 'commander.unknownOption') {
|
|
||||||
const option = err.message.match(/'([^']+)'/)?.[1];
|
|
||||||
const commandArg = process.argv.find(
|
|
||||||
(arg) =>
|
|
||||||
!arg.startsWith('-') &&
|
|
||||||
arg !== 'task-master' &&
|
|
||||||
!arg.includes('/') &&
|
|
||||||
arg !== 'node'
|
|
||||||
);
|
|
||||||
const command = commandArg || 'unknown';
|
|
||||||
|
|
||||||
console.error(chalk.red(`Error: Unknown option '${option}'`));
|
|
||||||
console.error(
|
|
||||||
chalk.yellow(
|
|
||||||
`Run 'task-master ${command} --help' to see available options for this command`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is a commander.js unknown command error
|
|
||||||
if (err.code === 'commander.unknownCommand') {
|
|
||||||
const command = err.message.match(/'([^']+)'/)?.[1];
|
|
||||||
|
|
||||||
console.error(chalk.red(`Error: Unknown command '${command}'`));
|
|
||||||
console.error(
|
|
||||||
chalk.yellow(`Run 'task-master --help' to see available commands`)
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle other uncaught exceptions
|
|
||||||
console.error(chalk.red(`Error: ${err.message}`));
|
|
||||||
if (process.env.DEBUG === '1') {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show help if no command was provided (just 'task-master' with no args)
|
|
||||||
if (process.argv.length <= 2) {
|
|
||||||
displayBanner();
|
|
||||||
displayHelp();
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add exports at the end of the file
|
|
||||||
export { detectCamelCaseFlags };
|
|
||||||
@@ -7,50 +7,17 @@
|
|||||||
"types": "./src/index.ts",
|
"types": "./src/index.ts",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": "./src/index.ts",
|
||||||
"types": "./src/index.ts",
|
"./auth": "./src/auth/index.ts",
|
||||||
"import": "./dist/index.js"
|
"./storage": "./src/storage/index.ts",
|
||||||
},
|
"./config": "./src/config/index.ts",
|
||||||
"./auth": {
|
"./providers": "./src/providers/index.ts",
|
||||||
"types": "./src/auth/index.ts",
|
"./services": "./src/services/index.ts",
|
||||||
"import": "./dist/auth/index.js"
|
"./errors": "./src/errors/index.ts",
|
||||||
},
|
"./logger": "./src/logger/index.ts",
|
||||||
"./storage": {
|
"./types": "./src/types/index.ts",
|
||||||
"types": "./src/storage/index.ts",
|
"./interfaces": "./src/interfaces/index.ts",
|
||||||
"import": "./dist/storage/index.js"
|
"./utils": "./src/utils/index.ts"
|
||||||
},
|
|
||||||
"./config": {
|
|
||||||
"types": "./src/config/index.ts",
|
|
||||||
"import": "./dist/config/index.js"
|
|
||||||
},
|
|
||||||
"./providers": {
|
|
||||||
"types": "./src/providers/index.ts",
|
|
||||||
"import": "./dist/providers/index.js"
|
|
||||||
},
|
|
||||||
"./services": {
|
|
||||||
"types": "./src/services/index.ts",
|
|
||||||
"import": "./dist/services/index.js"
|
|
||||||
},
|
|
||||||
"./errors": {
|
|
||||||
"types": "./src/errors/index.ts",
|
|
||||||
"import": "./dist/errors/index.js"
|
|
||||||
},
|
|
||||||
"./logger": {
|
|
||||||
"types": "./src/logger/index.ts",
|
|
||||||
"import": "./dist/logger/index.js"
|
|
||||||
},
|
|
||||||
"./types": {
|
|
||||||
"types": "./src/types/index.ts",
|
|
||||||
"import": "./dist/types/index.js"
|
|
||||||
},
|
|
||||||
"./interfaces": {
|
|
||||||
"types": "./src/interfaces/index.ts",
|
|
||||||
"import": "./dist/interfaces/index.js"
|
|
||||||
},
|
|
||||||
"./utils": {
|
|
||||||
"types": "./src/utils/index.ts",
|
|
||||||
"import": "./dist/utils/index.js"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { defineConfig } from 'tsup';
|
import { defineConfig } from 'tsup';
|
||||||
import { baseConfig, mergeConfig } from '@tm/build-config';
|
import { baseConfig, mergeConfig } from '@tm/build-config';
|
||||||
import { load as dotenvLoad } from 'dotenv-mono';
|
import { load as dotenvLoad } from 'dotenv-mono';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
dotenvLoad();
|
dotenvLoad();
|
||||||
|
|
||||||
@@ -16,28 +20,38 @@ const getBuildTimeEnvs = () => {
|
|||||||
return envs;
|
return envs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default defineConfig(
|
export default defineConfig(
|
||||||
mergeConfig(baseConfig, {
|
mergeConfig(baseConfig, {
|
||||||
entry: {
|
entry: {
|
||||||
'task-master': 'bin/task-master.js',
|
'task-master': 'scripts/dev.js',
|
||||||
'mcp-server': 'mcp-server/server.js'
|
'mcp-server': 'mcp-server/server.js'
|
||||||
},
|
},
|
||||||
outDir: 'dist',
|
outDir: 'dist',
|
||||||
publicDir: 'public',
|
publicDir: 'public',
|
||||||
// Bundle our monorepo packages but keep node_modules external
|
// Override the base config's external to bundle our workspace packages
|
||||||
noExternal: [/@tm\/.*/],
|
noExternal: [/^@tm\//],
|
||||||
// Ensure no code splitting
|
external: [/^@supabase\//], // Keep Supabase external to avoid dynamic require issues
|
||||||
splitting: false,
|
env: getBuildTimeEnvs(),
|
||||||
// Better watch configuration
|
esbuildOptions(options) {
|
||||||
ignoreWatch: [
|
// Set up path aliases for workspace packages
|
||||||
'dist',
|
options.alias = {
|
||||||
'node_modules',
|
'@tm/core': path.resolve(__dirname, 'packages/tm-core/src/index.ts'),
|
||||||
'.git',
|
'@tm/core/auth': path.resolve(__dirname, 'packages/tm-core/src/auth/index.ts'),
|
||||||
'tests',
|
'@tm/core/storage': path.resolve(__dirname, 'packages/tm-core/src/storage/index.ts'),
|
||||||
'*.test.*',
|
'@tm/core/config': path.resolve(__dirname, 'packages/tm-core/src/config/index.ts'),
|
||||||
'*.spec.*'
|
'@tm/core/providers': path.resolve(__dirname, 'packages/tm-core/src/providers/index.ts'),
|
||||||
],
|
'@tm/core/services': path.resolve(__dirname, 'packages/tm-core/src/services/index.ts'),
|
||||||
env: getBuildTimeEnvs()
|
'@tm/core/errors': path.resolve(__dirname, 'packages/tm-core/src/errors/index.ts'),
|
||||||
|
'@tm/core/logger': path.resolve(__dirname, 'packages/tm-core/src/logger/index.ts'),
|
||||||
|
'@tm/core/types': path.resolve(__dirname, 'packages/tm-core/src/types/index.ts'),
|
||||||
|
'@tm/core/interfaces': path.resolve(__dirname, 'packages/tm-core/src/interfaces/index.ts'),
|
||||||
|
'@tm/core/utils': path.resolve(__dirname, 'packages/tm-core/src/utils/index.ts'),
|
||||||
|
'@tm/cli': path.resolve(__dirname, 'apps/cli/src/index.ts'),
|
||||||
|
'@tm/cli/commands': path.resolve(__dirname, 'apps/cli/src/commands/index.ts'),
|
||||||
|
'@tm/cli/utils': path.resolve(__dirname, 'apps/cli/src/utils/index.ts'),
|
||||||
|
'@tm/cli/ui': path.resolve(__dirname, 'apps/cli/src/ui/index.ts'),
|
||||||
|
'@tm/build-config': path.resolve(__dirname, 'packages/build-config/src/tsup.base.ts'),
|
||||||
|
};
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user