feat: create tm-core and apps/cli (#1093)
- add typescript - add npm workspaces
This commit is contained in:
142
packages/tm-core/src/utils/id-generator.ts
Normal file
142
packages/tm-core/src/utils/id-generator.ts
Normal 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];
|
||||
}
|
||||
44
packages/tm-core/src/utils/index.ts
Normal file
44
packages/tm-core/src/utils/index.ts
Normal 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));
|
||||
}
|
||||
Reference in New Issue
Block a user