feat: create tm-core and apps/cli (#1093)

- add typescript
- add npm workspaces
This commit is contained in:
Ralph Khreish
2025-09-01 21:44:43 +02:00
parent a7ad4c8e92
commit 0f3ab00f26
162 changed files with 22235 additions and 706 deletions

View File

@@ -0,0 +1,142 @@
/**
* @fileoverview ID generation utilities for Task Master
* Provides functions to generate unique identifiers for tasks and subtasks
*/
import { randomBytes } from 'node:crypto';
/**
* Generates a unique task ID using the format: TASK-{timestamp}-{random}
*
* @returns A unique task ID string
* @example
* ```typescript
* const taskId = generateTaskId();
* // Returns something like: "TASK-1704067200000-A7B3"
* ```
*/
export function generateTaskId(): string {
const timestamp = Date.now();
const random = generateRandomString(4);
return `TASK-${timestamp}-${random}`;
}
/**
* Generates a subtask ID using the format: {parentId}.{sequential}
*
* @param parentId - The ID of the parent task
* @param existingSubtasks - Array of existing subtask IDs to determine the next sequential number
* @returns A unique subtask ID string
* @example
* ```typescript
* const subtaskId = generateSubtaskId("TASK-123-A7B3", ["TASK-123-A7B3.1"]);
* // Returns: "TASK-123-A7B3.2"
* ```
*/
export function generateSubtaskId(
parentId: string,
existingSubtasks: string[] = []
): string {
// Find existing subtasks for this parent
const parentSubtasks = existingSubtasks.filter((id) =>
id.startsWith(`${parentId}.`)
);
// Extract sequential numbers and find the highest
const sequentialNumbers = parentSubtasks
.map((id) => {
const parts = id.split('.');
const lastPart = parts[parts.length - 1];
return Number.parseInt(lastPart, 10);
})
.filter((num) => !Number.isNaN(num))
.sort((a, b) => a - b);
// Determine the next sequential number
const nextSequential =
sequentialNumbers.length > 0 ? Math.max(...sequentialNumbers) + 1 : 1;
return `${parentId}.${nextSequential}`;
}
/**
* Generates a random alphanumeric string of specified length
* Uses crypto.randomBytes for cryptographically secure randomness
*
* @param length - The desired length of the random string
* @returns A random alphanumeric string
* @internal
*/
function generateRandomString(length: number): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const bytes = randomBytes(length);
let result = '';
for (let i = 0; i < length; i++) {
result += chars[bytes[i] % chars.length];
}
return result;
}
/**
* Validates a task ID format
*
* @param id - The ID to validate
* @returns True if the ID matches the expected task ID format
* @example
* ```typescript
* isValidTaskId("TASK-1704067200000-A7B3"); // true
* isValidTaskId("invalid-id"); // false
* ```
*/
export function isValidTaskId(id: string): boolean {
const taskIdRegex = /^TASK-\d{13}-[A-Z0-9]{4}$/;
return taskIdRegex.test(id);
}
/**
* Validates a subtask ID format
*
* @param id - The ID to validate
* @returns True if the ID matches the expected subtask ID format
* @example
* ```typescript
* isValidSubtaskId("TASK-1704067200000-A7B3.1"); // true
* isValidSubtaskId("TASK-1704067200000-A7B3.1.2"); // true (nested subtask)
* isValidSubtaskId("invalid.id"); // false
* ```
*/
export function isValidSubtaskId(id: string): boolean {
const parts = id.split('.');
if (parts.length < 2) return false;
// First part should be a valid task ID
const taskIdPart = parts[0];
if (!isValidTaskId(taskIdPart)) return false;
// Remaining parts should be positive integers
const sequentialParts = parts.slice(1);
return sequentialParts.every((part) => {
const num = Number.parseInt(part, 10);
return !Number.isNaN(num) && num > 0 && part === num.toString();
});
}
/**
* Extracts the parent task ID from a subtask ID
*
* @param subtaskId - The subtask ID
* @returns The parent task ID, or null if the input is not a valid subtask ID
* @example
* ```typescript
* getParentTaskId("TASK-1704067200000-A7B3.1.2"); // "TASK-1704067200000-A7B3"
* getParentTaskId("TASK-1704067200000-A7B3"); // null (not a subtask)
* ```
*/
export function getParentTaskId(subtaskId: string): string | null {
if (!isValidSubtaskId(subtaskId)) return null;
const parts = subtaskId.split('.');
return parts[0];
}

View File

@@ -0,0 +1,44 @@
/**
* @fileoverview Utility functions for the tm-core package
* This file exports all utility functions and helper classes
*/
// Utility implementations will be defined here
// export * from './validation.js';
// export * from './formatting.js';
// export * from './file-utils.js';
// export * from './async-utils.js';
// Placeholder exports - these will be implemented in later tasks
/**
* Generates a unique ID for tasks
* @deprecated This is a placeholder function that will be properly implemented in later tasks
*/
export function generateTaskId(): string {
return `task-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
}
/**
* Validates a task ID format
* @deprecated This is a placeholder function that will be properly implemented in later tasks
*/
export function isValidTaskId(id: string): boolean {
return typeof id === 'string' && id.length > 0;
}
/**
* Formats a date for task timestamps
* @deprecated This is a placeholder function that will be properly implemented in later tasks
*/
export function formatDate(date: Date = new Date()): string {
return date.toISOString();
}
/**
* Deep clones an object
* @deprecated This is a placeholder function that will be properly implemented in later tasks
*/
export function deepClone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}