@taazkareem/clickup-mcp-server 0.8.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -17
- package/README.md +33 -38
- package/build/enhanced_server.js +262 -0
- package/build/index.js +9 -3
- package/build/license.js +172 -0
- package/build/middleware/auth.js +211 -0
- package/build/routes/auth.js +306 -0
- package/build/schemas/member.js +13 -0
- package/build/server.js +15 -1
- package/build/server.log +15 -0
- package/build/services/auth/oauth2.js +236 -0
- package/build/services/auth/session.js +337 -0
- package/build/services/clickup/adapter.js +281 -0
- package/build/services/clickup/factory.js +339 -0
- package/build/services/clickup/task/task-attachments.js +20 -12
- package/build/services/clickup/task/task-comments.js +19 -9
- package/build/services/clickup/task/task-core.js +68 -4
- package/build/services/clickup/task/task-custom-fields.js +23 -13
- package/build/services/clickup/task/task-search.js +79 -71
- package/build/services/clickup/task/task-service.js +88 -9
- package/build/services/clickup/task/task-tags.js +25 -13
- package/build/sse_server.js +4 -4
- package/build/tools/documents.js +11 -4
- package/build/tools/health.js +23 -0
- package/build/tools/member.js +2 -4
- package/build/tools/task/bulk-operations.js +5 -5
- package/build/tools/task/handlers.js +62 -12
- package/build/tools/task/single-operations.js +9 -9
- package/build/tools/task/time-tracking.js +61 -170
- package/build/tools/task/utilities.js +56 -22
- package/build/utils/date-utils.js +341 -141
- package/build/utils/schema-compatibility.js +222 -0
- package/build/utils/universal-schema-compatibility.js +171 -0
- package/build/virtual-sdk/generator.js +53 -0
- package/build/virtual-sdk/registry.js +45 -0
- package/package.json +2 -2
|
@@ -15,6 +15,7 @@ import { timeTrackingService } from "../../services/shared.js";
|
|
|
15
15
|
import { getTaskId } from "./utilities.js";
|
|
16
16
|
import { Logger } from "../../logger.js";
|
|
17
17
|
import { parseDueDate } from "../../utils/date-utils.js";
|
|
18
|
+
import { sponsorService } from "../../utils/sponsor-service.js";
|
|
18
19
|
// Logger instance
|
|
19
20
|
const logger = new Logger('TimeTrackingTools');
|
|
20
21
|
/**
|
|
@@ -196,12 +197,7 @@ export async function handleGetTaskTimeEntries(params) {
|
|
|
196
197
|
// Resolve task ID
|
|
197
198
|
const taskId = await getTaskId(params.taskId, params.taskName, params.listName);
|
|
198
199
|
if (!taskId) {
|
|
199
|
-
return
|
|
200
|
-
success: false,
|
|
201
|
-
error: {
|
|
202
|
-
message: "Task not found. Please provide a valid taskId or taskName + listName combination."
|
|
203
|
-
}
|
|
204
|
-
};
|
|
200
|
+
return sponsorService.createErrorResponse("Task not found. Please provide a valid taskId or taskName + listName combination.");
|
|
205
201
|
}
|
|
206
202
|
// Parse date filters
|
|
207
203
|
let startDate;
|
|
@@ -215,46 +211,37 @@ export async function handleGetTaskTimeEntries(params) {
|
|
|
215
211
|
// Get time entries
|
|
216
212
|
const result = await timeTrackingService.getTimeEntries(taskId, startDate, endDate);
|
|
217
213
|
if (!result.success) {
|
|
218
|
-
return
|
|
219
|
-
success: false,
|
|
220
|
-
error: {
|
|
221
|
-
message: result.error?.message || "Failed to get time entries"
|
|
222
|
-
}
|
|
223
|
-
};
|
|
214
|
+
return sponsorService.createErrorResponse(result.error?.message || "Failed to get time entries");
|
|
224
215
|
}
|
|
225
216
|
const timeEntries = result.data || [];
|
|
226
217
|
// Format the response
|
|
227
|
-
return {
|
|
218
|
+
return sponsorService.createResponse({
|
|
228
219
|
success: true,
|
|
220
|
+
count: timeEntries.length,
|
|
229
221
|
time_entries: timeEntries.map(entry => ({
|
|
230
222
|
id: entry.id,
|
|
231
|
-
description: entry.description,
|
|
223
|
+
description: entry.description || "",
|
|
232
224
|
start: entry.start,
|
|
233
225
|
end: entry.end,
|
|
234
|
-
duration: formatDuration(entry.duration),
|
|
235
|
-
duration_ms: entry.duration,
|
|
236
|
-
billable: entry.billable,
|
|
237
|
-
tags: entry.tags,
|
|
238
|
-
user: {
|
|
226
|
+
duration: formatDuration(entry.duration || 0),
|
|
227
|
+
duration_ms: entry.duration || 0,
|
|
228
|
+
billable: entry.billable || false,
|
|
229
|
+
tags: entry.tags || [],
|
|
230
|
+
user: entry.user ? {
|
|
239
231
|
id: entry.user.id,
|
|
240
232
|
username: entry.user.username
|
|
241
|
-
},
|
|
242
|
-
task: {
|
|
233
|
+
} : null,
|
|
234
|
+
task: entry.task ? {
|
|
243
235
|
id: entry.task.id,
|
|
244
236
|
name: entry.task.name,
|
|
245
|
-
status: entry.task.status
|
|
246
|
-
}
|
|
237
|
+
status: entry.task.status?.status || "Unknown"
|
|
238
|
+
} : null
|
|
247
239
|
}))
|
|
248
|
-
};
|
|
240
|
+
}, true);
|
|
249
241
|
}
|
|
250
242
|
catch (error) {
|
|
251
243
|
logger.error("Error getting task time entries", error);
|
|
252
|
-
return
|
|
253
|
-
success: false,
|
|
254
|
-
error: {
|
|
255
|
-
message: error.message || "An unknown error occurred"
|
|
256
|
-
}
|
|
257
|
-
};
|
|
244
|
+
return sponsorService.createErrorResponse(error.message || "An unknown error occurred");
|
|
258
245
|
}
|
|
259
246
|
}
|
|
260
247
|
/**
|
|
@@ -266,31 +253,22 @@ export async function handleStartTimeTracking(params) {
|
|
|
266
253
|
// Resolve task ID
|
|
267
254
|
const taskId = await getTaskId(params.taskId, params.taskName, params.listName);
|
|
268
255
|
if (!taskId) {
|
|
269
|
-
return
|
|
270
|
-
success: false,
|
|
271
|
-
error: {
|
|
272
|
-
message: "Task not found. Please provide a valid taskId or taskName + listName combination."
|
|
273
|
-
}
|
|
274
|
-
};
|
|
256
|
+
return sponsorService.createErrorResponse("Task not found. Please provide a valid taskId or taskName + listName combination.");
|
|
275
257
|
}
|
|
276
258
|
// Check for currently running timer
|
|
277
259
|
const currentTimerResult = await timeTrackingService.getCurrentTimeEntry();
|
|
278
260
|
if (currentTimerResult.success && currentTimerResult.data) {
|
|
279
|
-
return {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
},
|
|
289
|
-
start: currentTimerResult.data.start,
|
|
290
|
-
description: currentTimerResult.data.description
|
|
291
|
-
}
|
|
261
|
+
return sponsorService.createErrorResponse("A timer is already running. Please stop the current timer before starting a new one.", {
|
|
262
|
+
timer: {
|
|
263
|
+
id: currentTimerResult.data.id,
|
|
264
|
+
task: {
|
|
265
|
+
id: currentTimerResult.data.task.id,
|
|
266
|
+
name: currentTimerResult.data.task.name
|
|
267
|
+
},
|
|
268
|
+
start: currentTimerResult.data.start,
|
|
269
|
+
description: currentTimerResult.data.description
|
|
292
270
|
}
|
|
293
|
-
};
|
|
271
|
+
});
|
|
294
272
|
}
|
|
295
273
|
// Prepare request data
|
|
296
274
|
const requestData = {
|
|
@@ -302,25 +280,16 @@ export async function handleStartTimeTracking(params) {
|
|
|
302
280
|
// Start time tracking
|
|
303
281
|
const result = await timeTrackingService.startTimeTracking(requestData);
|
|
304
282
|
if (!result.success) {
|
|
305
|
-
return
|
|
306
|
-
success: false,
|
|
307
|
-
error: {
|
|
308
|
-
message: result.error?.message || "Failed to start time tracking"
|
|
309
|
-
}
|
|
310
|
-
};
|
|
283
|
+
return sponsorService.createErrorResponse(result.error?.message || "Failed to start time tracking");
|
|
311
284
|
}
|
|
312
285
|
const timeEntry = result.data;
|
|
313
286
|
if (!timeEntry) {
|
|
314
|
-
return
|
|
315
|
-
success: false,
|
|
316
|
-
error: {
|
|
317
|
-
message: "No time entry data returned from API"
|
|
318
|
-
}
|
|
319
|
-
};
|
|
287
|
+
return sponsorService.createErrorResponse("No time entry data returned from API");
|
|
320
288
|
}
|
|
321
289
|
// Format the response
|
|
322
|
-
return {
|
|
290
|
+
return sponsorService.createResponse({
|
|
323
291
|
success: true,
|
|
292
|
+
message: "Time tracking started successfully",
|
|
324
293
|
time_entry: {
|
|
325
294
|
id: timeEntry.id,
|
|
326
295
|
description: timeEntry.description,
|
|
@@ -333,16 +302,11 @@ export async function handleStartTimeTracking(params) {
|
|
|
333
302
|
billable: timeEntry.billable,
|
|
334
303
|
tags: timeEntry.tags
|
|
335
304
|
}
|
|
336
|
-
};
|
|
305
|
+
}, true);
|
|
337
306
|
}
|
|
338
307
|
catch (error) {
|
|
339
308
|
logger.error("Error starting time tracking", error);
|
|
340
|
-
return
|
|
341
|
-
success: false,
|
|
342
|
-
error: {
|
|
343
|
-
message: error.message || "An unknown error occurred"
|
|
344
|
-
}
|
|
345
|
-
};
|
|
309
|
+
return sponsorService.createErrorResponse(error.message || "An unknown error occurred");
|
|
346
310
|
}
|
|
347
311
|
}
|
|
348
312
|
/**
|
|
@@ -354,12 +318,7 @@ export async function handleStopTimeTracking(params) {
|
|
|
354
318
|
// Check for currently running timer
|
|
355
319
|
const currentTimerResult = await timeTrackingService.getCurrentTimeEntry();
|
|
356
320
|
if (currentTimerResult.success && !currentTimerResult.data) {
|
|
357
|
-
return
|
|
358
|
-
success: false,
|
|
359
|
-
error: {
|
|
360
|
-
message: "No timer is currently running. Start a timer before trying to stop it."
|
|
361
|
-
}
|
|
362
|
-
};
|
|
321
|
+
return sponsorService.createErrorResponse("No timer is currently running. Start a timer before trying to stop it.");
|
|
363
322
|
}
|
|
364
323
|
// Prepare request data
|
|
365
324
|
const requestData = {
|
|
@@ -369,25 +328,16 @@ export async function handleStopTimeTracking(params) {
|
|
|
369
328
|
// Stop time tracking
|
|
370
329
|
const result = await timeTrackingService.stopTimeTracking(requestData);
|
|
371
330
|
if (!result.success) {
|
|
372
|
-
return
|
|
373
|
-
success: false,
|
|
374
|
-
error: {
|
|
375
|
-
message: result.error?.message || "Failed to stop time tracking"
|
|
376
|
-
}
|
|
377
|
-
};
|
|
331
|
+
return sponsorService.createErrorResponse(result.error?.message || "Failed to stop time tracking");
|
|
378
332
|
}
|
|
379
333
|
const timeEntry = result.data;
|
|
380
334
|
if (!timeEntry) {
|
|
381
|
-
return
|
|
382
|
-
success: false,
|
|
383
|
-
error: {
|
|
384
|
-
message: "No time entry data returned from API"
|
|
385
|
-
}
|
|
386
|
-
};
|
|
335
|
+
return sponsorService.createErrorResponse("No time entry data returned from API");
|
|
387
336
|
}
|
|
388
337
|
// Format the response
|
|
389
|
-
return {
|
|
338
|
+
return sponsorService.createResponse({
|
|
390
339
|
success: true,
|
|
340
|
+
message: "Time tracking stopped successfully",
|
|
391
341
|
time_entry: {
|
|
392
342
|
id: timeEntry.id,
|
|
393
343
|
description: timeEntry.description,
|
|
@@ -402,16 +352,11 @@ export async function handleStopTimeTracking(params) {
|
|
|
402
352
|
billable: timeEntry.billable,
|
|
403
353
|
tags: timeEntry.tags
|
|
404
354
|
}
|
|
405
|
-
};
|
|
355
|
+
}, true);
|
|
406
356
|
}
|
|
407
357
|
catch (error) {
|
|
408
358
|
logger.error("Error stopping time tracking", error);
|
|
409
|
-
return
|
|
410
|
-
success: false,
|
|
411
|
-
error: {
|
|
412
|
-
message: error.message || "An unknown error occurred"
|
|
413
|
-
}
|
|
414
|
-
};
|
|
359
|
+
return sponsorService.createErrorResponse(error.message || "An unknown error occurred");
|
|
415
360
|
}
|
|
416
361
|
}
|
|
417
362
|
/**
|
|
@@ -423,32 +368,17 @@ export async function handleAddTimeEntry(params) {
|
|
|
423
368
|
// Resolve task ID
|
|
424
369
|
const taskId = await getTaskId(params.taskId, params.taskName, params.listName);
|
|
425
370
|
if (!taskId) {
|
|
426
|
-
return
|
|
427
|
-
success: false,
|
|
428
|
-
error: {
|
|
429
|
-
message: "Task not found. Please provide a valid taskId or taskName + listName combination."
|
|
430
|
-
}
|
|
431
|
-
};
|
|
371
|
+
return sponsorService.createErrorResponse("Task not found. Please provide a valid taskId or taskName + listName combination.");
|
|
432
372
|
}
|
|
433
373
|
// Parse start time
|
|
434
374
|
const startTime = parseDueDate(params.start);
|
|
435
375
|
if (!startTime) {
|
|
436
|
-
return
|
|
437
|
-
success: false,
|
|
438
|
-
error: {
|
|
439
|
-
message: "Invalid start time format. Use a Unix timestamp (in milliseconds) or a natural language date string."
|
|
440
|
-
}
|
|
441
|
-
};
|
|
376
|
+
return sponsorService.createErrorResponse("Invalid start time format. Use a Unix timestamp (in milliseconds) or a natural language date string.");
|
|
442
377
|
}
|
|
443
378
|
// Parse duration
|
|
444
379
|
const durationMs = parseDuration(params.duration);
|
|
445
380
|
if (durationMs === 0) {
|
|
446
|
-
return
|
|
447
|
-
success: false,
|
|
448
|
-
error: {
|
|
449
|
-
message: "Invalid duration format. Use 'Xh Ym' format (e.g., '1h 30m') or just minutes (e.g., '90m')."
|
|
450
|
-
}
|
|
451
|
-
};
|
|
381
|
+
return sponsorService.createErrorResponse("Invalid duration format. Use 'Xh Ym' format (e.g., '1h 30m') or just minutes (e.g., '90m').");
|
|
452
382
|
}
|
|
453
383
|
// Prepare request data
|
|
454
384
|
const requestData = {
|
|
@@ -462,25 +392,16 @@ export async function handleAddTimeEntry(params) {
|
|
|
462
392
|
// Add time entry
|
|
463
393
|
const result = await timeTrackingService.addTimeEntry(requestData);
|
|
464
394
|
if (!result.success) {
|
|
465
|
-
return
|
|
466
|
-
success: false,
|
|
467
|
-
error: {
|
|
468
|
-
message: result.error?.message || "Failed to add time entry"
|
|
469
|
-
}
|
|
470
|
-
};
|
|
395
|
+
return sponsorService.createErrorResponse(result.error?.message || "Failed to add time entry");
|
|
471
396
|
}
|
|
472
397
|
const timeEntry = result.data;
|
|
473
398
|
if (!timeEntry) {
|
|
474
|
-
return
|
|
475
|
-
success: false,
|
|
476
|
-
error: {
|
|
477
|
-
message: "No time entry data returned from API"
|
|
478
|
-
}
|
|
479
|
-
};
|
|
399
|
+
return sponsorService.createErrorResponse("No time entry data returned from API");
|
|
480
400
|
}
|
|
481
401
|
// Format the response
|
|
482
|
-
return {
|
|
402
|
+
return sponsorService.createResponse({
|
|
483
403
|
success: true,
|
|
404
|
+
message: "Time entry added successfully",
|
|
484
405
|
time_entry: {
|
|
485
406
|
id: timeEntry.id,
|
|
486
407
|
description: timeEntry.description,
|
|
@@ -495,16 +416,11 @@ export async function handleAddTimeEntry(params) {
|
|
|
495
416
|
billable: timeEntry.billable,
|
|
496
417
|
tags: timeEntry.tags
|
|
497
418
|
}
|
|
498
|
-
};
|
|
419
|
+
}, true);
|
|
499
420
|
}
|
|
500
421
|
catch (error) {
|
|
501
422
|
logger.error("Error adding time entry", error);
|
|
502
|
-
return
|
|
503
|
-
success: false,
|
|
504
|
-
error: {
|
|
505
|
-
message: error.message || "An unknown error occurred"
|
|
506
|
-
}
|
|
507
|
-
};
|
|
423
|
+
return sponsorService.createErrorResponse(error.message || "An unknown error occurred");
|
|
508
424
|
}
|
|
509
425
|
}
|
|
510
426
|
/**
|
|
@@ -515,37 +431,22 @@ export async function handleDeleteTimeEntry(params) {
|
|
|
515
431
|
try {
|
|
516
432
|
const { timeEntryId } = params;
|
|
517
433
|
if (!timeEntryId) {
|
|
518
|
-
return
|
|
519
|
-
success: false,
|
|
520
|
-
error: {
|
|
521
|
-
message: "Time entry ID is required."
|
|
522
|
-
}
|
|
523
|
-
};
|
|
434
|
+
return sponsorService.createErrorResponse("Time entry ID is required.");
|
|
524
435
|
}
|
|
525
436
|
// Delete time entry
|
|
526
437
|
const result = await timeTrackingService.deleteTimeEntry(timeEntryId);
|
|
527
438
|
if (!result.success) {
|
|
528
|
-
return
|
|
529
|
-
success: false,
|
|
530
|
-
error: {
|
|
531
|
-
message: result.error?.message || "Failed to delete time entry"
|
|
532
|
-
}
|
|
533
|
-
};
|
|
439
|
+
return sponsorService.createErrorResponse(result.error?.message || "Failed to delete time entry");
|
|
534
440
|
}
|
|
535
441
|
// Format the response
|
|
536
|
-
return {
|
|
442
|
+
return sponsorService.createResponse({
|
|
537
443
|
success: true,
|
|
538
444
|
message: "Time entry deleted successfully."
|
|
539
|
-
};
|
|
445
|
+
}, true);
|
|
540
446
|
}
|
|
541
447
|
catch (error) {
|
|
542
448
|
logger.error("Error deleting time entry", error);
|
|
543
|
-
return
|
|
544
|
-
success: false,
|
|
545
|
-
error: {
|
|
546
|
-
message: error.message || "An unknown error occurred"
|
|
547
|
-
}
|
|
548
|
-
};
|
|
449
|
+
return sponsorService.createErrorResponse(error.message || "An unknown error occurred");
|
|
549
450
|
}
|
|
550
451
|
}
|
|
551
452
|
/**
|
|
@@ -557,25 +458,20 @@ export async function handleGetCurrentTimeEntry(params) {
|
|
|
557
458
|
// Get current time entry
|
|
558
459
|
const result = await timeTrackingService.getCurrentTimeEntry();
|
|
559
460
|
if (!result.success) {
|
|
560
|
-
return
|
|
561
|
-
success: false,
|
|
562
|
-
error: {
|
|
563
|
-
message: result.error?.message || "Failed to get current time entry"
|
|
564
|
-
}
|
|
565
|
-
};
|
|
461
|
+
return sponsorService.createErrorResponse(result.error?.message || "Failed to get current time entry");
|
|
566
462
|
}
|
|
567
463
|
const timeEntry = result.data;
|
|
568
464
|
// If no timer is running
|
|
569
465
|
if (!timeEntry) {
|
|
570
|
-
return {
|
|
466
|
+
return sponsorService.createResponse({
|
|
571
467
|
success: true,
|
|
572
468
|
timer_running: false,
|
|
573
469
|
message: "No timer is currently running."
|
|
574
|
-
};
|
|
470
|
+
}, true);
|
|
575
471
|
}
|
|
576
472
|
// Format the response
|
|
577
473
|
const elapsedTime = calculateElapsedTime(timeEntry.start);
|
|
578
|
-
return {
|
|
474
|
+
return sponsorService.createResponse({
|
|
579
475
|
success: true,
|
|
580
476
|
timer_running: true,
|
|
581
477
|
time_entry: {
|
|
@@ -591,16 +487,11 @@ export async function handleGetCurrentTimeEntry(params) {
|
|
|
591
487
|
billable: timeEntry.billable,
|
|
592
488
|
tags: timeEntry.tags
|
|
593
489
|
}
|
|
594
|
-
};
|
|
490
|
+
}, true);
|
|
595
491
|
}
|
|
596
492
|
catch (error) {
|
|
597
493
|
logger.error("Error getting current time entry", error);
|
|
598
|
-
return
|
|
599
|
-
success: false,
|
|
600
|
-
error: {
|
|
601
|
-
message: error.message || "An unknown error occurred"
|
|
602
|
-
}
|
|
603
|
-
};
|
|
494
|
+
return sponsorService.createErrorResponse(error.message || "An unknown error occurred");
|
|
604
495
|
}
|
|
605
496
|
}
|
|
606
497
|
/**
|
|
@@ -65,6 +65,62 @@ export function formatTaskData(task, additional = {}) {
|
|
|
65
65
|
...additional
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
|
+
//=============================================================================
|
|
69
|
+
// TASK ID DETECTION UTILITIES
|
|
70
|
+
//=============================================================================
|
|
71
|
+
/**
|
|
72
|
+
* Detects if a task ID is a custom task ID based on common patterns
|
|
73
|
+
* Custom task IDs typically:
|
|
74
|
+
* - Contain hyphens (e.g., "DEV-1234", "PROJ-456")
|
|
75
|
+
* - Have uppercase prefixes followed by numbers
|
|
76
|
+
* - Are not 9-character alphanumeric strings (regular ClickUp task IDs)
|
|
77
|
+
*
|
|
78
|
+
* @param taskId The task ID to check
|
|
79
|
+
* @returns true if the ID appears to be a custom task ID
|
|
80
|
+
*/
|
|
81
|
+
export function isCustomTaskId(taskId) {
|
|
82
|
+
if (!taskId || typeof taskId !== 'string') {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
// Trim whitespace
|
|
86
|
+
taskId = taskId.trim();
|
|
87
|
+
// Regular ClickUp task IDs are typically 9 characters, alphanumeric
|
|
88
|
+
// Custom task IDs usually have different patterns
|
|
89
|
+
// Check if it's a standard 9-character ClickUp ID (letters and numbers only)
|
|
90
|
+
const standardIdPattern = /^[a-zA-Z0-9]{9}$/;
|
|
91
|
+
if (standardIdPattern.test(taskId)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
// Check for common custom task ID patterns:
|
|
95
|
+
// 1. Contains hyphens (most common pattern: PREFIX-NUMBER)
|
|
96
|
+
if (taskId.includes('-')) {
|
|
97
|
+
// Additional validation: should have letters before hyphen and numbers after
|
|
98
|
+
const hyphenPattern = /^[A-Za-z]+[-][0-9]+$/;
|
|
99
|
+
return hyphenPattern.test(taskId);
|
|
100
|
+
}
|
|
101
|
+
// 2. Contains underscores (another common pattern: PREFIX_NUMBER)
|
|
102
|
+
if (taskId.includes('_')) {
|
|
103
|
+
const underscorePattern = /^[A-Za-z]+[_][0-9]+$/;
|
|
104
|
+
return underscorePattern.test(taskId);
|
|
105
|
+
}
|
|
106
|
+
// 3. Contains uppercase letters followed by numbers (without separators)
|
|
107
|
+
const customIdPattern = /^[A-Z]+\d+$/;
|
|
108
|
+
if (customIdPattern.test(taskId)) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
// 4. Mixed case with numbers but not 9 characters (less common)
|
|
112
|
+
const mixedCasePattern = /^[A-Za-z]+\d+$/;
|
|
113
|
+
if (mixedCasePattern.test(taskId) && taskId.length !== 9) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
// 5. Contains dots (some organizations use PROJECT.TASK format)
|
|
117
|
+
if (taskId.includes('.')) {
|
|
118
|
+
const dotPattern = /^[A-Za-z]+[.][0-9]+$/;
|
|
119
|
+
return dotPattern.test(taskId);
|
|
120
|
+
}
|
|
121
|
+
// If none of the patterns match, assume it's a regular task ID
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
68
124
|
/**
|
|
69
125
|
* Validates task identification parameters
|
|
70
126
|
*
|
|
@@ -169,28 +225,6 @@ export function parseBulkOptions(rawOptions) {
|
|
|
169
225
|
}
|
|
170
226
|
return rawOptions;
|
|
171
227
|
}
|
|
172
|
-
//=============================================================================
|
|
173
|
-
// ID DETECTION UTILITIES
|
|
174
|
-
//=============================================================================
|
|
175
|
-
/**
|
|
176
|
-
* Determines if an ID is a custom ID based on its format
|
|
177
|
-
* Custom IDs typically have an uppercase prefix followed by a hyphen and number (e.g., DEV-1234)
|
|
178
|
-
* Regular task IDs are always 9 characters long
|
|
179
|
-
*
|
|
180
|
-
* @param id The task ID to check
|
|
181
|
-
* @returns True if the ID appears to be a custom ID
|
|
182
|
-
*/
|
|
183
|
-
export function isCustomTaskId(id) {
|
|
184
|
-
if (!id)
|
|
185
|
-
return false;
|
|
186
|
-
// Regular task IDs are exactly 9 characters
|
|
187
|
-
if (id.length === 9) {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
// Custom IDs have an uppercase prefix followed by a hyphen and numbers
|
|
191
|
-
const customIdPattern = /^[A-Z]+-\d+$/;
|
|
192
|
-
return customIdPattern.test(id);
|
|
193
|
-
}
|
|
194
228
|
/**
|
|
195
229
|
* Resolves a list ID from either direct ID or name
|
|
196
230
|
* Handles validation and throws appropriate errors
|