taskninja 1.1.6 → 1.1.8
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/README.md +83 -254
- package/index.js +108 -0
- package/package.json +5 -5
- package/src/commands/addCommand.js +79 -0
- package/src/commands/deleteCommand.js +51 -0
- package/src/commands/doneCommand.js +36 -0
- package/src/commands/listCommand.js +19 -0
- package/src/commands/searchCommand.js +48 -0
- package/src/commands/sortCommand.js +47 -0
- package/src/commands/undoCommand.js +32 -0
- package/src/commands/updateCommand.js +62 -0
- package/src/helpers/helpers.js +1 -18
- package/src/utils/taskService.js +32 -7
- package/src/index.js +0 -523
package/src/index.js
DELETED
|
@@ -1,523 +0,0 @@
|
|
|
1
|
-
#! /usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* TASKNINJA -
|
|
5
|
-
* Task Manager CLI Application
|
|
6
|
-
* This application allows users to manage their tasks via command line interface.
|
|
7
|
-
* It supports adding, listing, and removing tasks.
|
|
8
|
-
* Dependencies:
|
|
9
|
-
* - commander: For command line argument parsing
|
|
10
|
-
* - inquirer: For interactive prompts
|
|
11
|
-
* - fs: For file system operations
|
|
12
|
-
* Author: Mohamed Bakr
|
|
13
|
-
* Date: January 2026
|
|
14
|
-
* Version: 1.1.5
|
|
15
|
-
* @license MIT
|
|
16
|
-
* Copyright (c) 2026 Mohamed Bakr
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
// for using commands in terminal
|
|
20
|
-
import { Command } from "commander";
|
|
21
|
-
// for interactive command line prompts
|
|
22
|
-
import inquirer from "inquirer";
|
|
23
|
-
// for colored text
|
|
24
|
-
import chalk from 'chalk';
|
|
25
|
-
|
|
26
|
-
// helpers
|
|
27
|
-
import {displayTasks, enableEscExit, cleanupAndExit} from './helpers/helpers.js';
|
|
28
|
-
|
|
29
|
-
// assigning Commander to a variable
|
|
30
|
-
const program = new Command();
|
|
31
|
-
|
|
32
|
-
// importing validators and allowed values
|
|
33
|
-
import { validateDueDate, ALLOWED_PRIORITIES, ALLOWED_STATUSES } from "./utils/validators.js";
|
|
34
|
-
// importing task service functions
|
|
35
|
-
import { loadTasks, saveTasks, getNextId, saveDeletedTask, loadDeletedTask, clearDeletedTask, cleanupExpiredDeletedTasks } from "./utils/taskService.js";
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// setting up
|
|
39
|
-
program
|
|
40
|
-
.name("taskninja")
|
|
41
|
-
.description("A simple CLI application to manage your tasks")
|
|
42
|
-
.version("1.1.6");
|
|
43
|
-
|
|
44
|
-
// use command 'add' with title + status + priority + dueDate + description and action
|
|
45
|
-
program
|
|
46
|
-
.command('add')
|
|
47
|
-
.alias('a')
|
|
48
|
-
.description('Add a new task')
|
|
49
|
-
.action(async () => {
|
|
50
|
-
enableEscExit();
|
|
51
|
-
const answers = await inquirer.prompt([
|
|
52
|
-
{
|
|
53
|
-
type: 'input',
|
|
54
|
-
name: 'title',
|
|
55
|
-
message : 'Task Title:',
|
|
56
|
-
validate : input => input ? true : 'Title cannot be empty!'
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
type: 'rawlist',
|
|
60
|
-
name: 'status',
|
|
61
|
-
message : 'Task Status:',
|
|
62
|
-
choices : ALLOWED_STATUSES,
|
|
63
|
-
default : 'todo'
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
type: 'rawlist',
|
|
67
|
-
name: 'priority',
|
|
68
|
-
message : 'Task Priority:',
|
|
69
|
-
choices : ALLOWED_PRIORITIES,
|
|
70
|
-
default : 'medium'
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
type: 'input',
|
|
74
|
-
name: 'dueDate',
|
|
75
|
-
message : 'Due Date (YYYY-MM-DD):',
|
|
76
|
-
default : () => {
|
|
77
|
-
const today = new Date();
|
|
78
|
-
return today.toISOString().split('T')[0];
|
|
79
|
-
},
|
|
80
|
-
filter: (input) => {
|
|
81
|
-
const trimmed = input.trim();
|
|
82
|
-
if (!trimmed) {
|
|
83
|
-
return new Date().toISOString().split('T')[0];
|
|
84
|
-
}
|
|
85
|
-
return trimmed;
|
|
86
|
-
},
|
|
87
|
-
validate : input => {
|
|
88
|
-
try {
|
|
89
|
-
validateDueDate(input);
|
|
90
|
-
return true;
|
|
91
|
-
} catch (error) {
|
|
92
|
-
return error.message;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
type: 'input',
|
|
98
|
-
name: 'description',
|
|
99
|
-
message : 'Task Description (optional):'
|
|
100
|
-
},
|
|
101
|
-
]);
|
|
102
|
-
// load existing tasks
|
|
103
|
-
const tasks = await loadTasks();
|
|
104
|
-
|
|
105
|
-
// create new task object
|
|
106
|
-
const newTask = {
|
|
107
|
-
id: getNextId(tasks),
|
|
108
|
-
title: answers.title,
|
|
109
|
-
status: answers.status,
|
|
110
|
-
priority: answers.priority,
|
|
111
|
-
dueDate: answers.dueDate,
|
|
112
|
-
description: answers.description || ''
|
|
113
|
-
};
|
|
114
|
-
// add new task to tasks array
|
|
115
|
-
tasks.push(newTask);
|
|
116
|
-
// save updated tasks to file
|
|
117
|
-
await saveTasks(tasks);
|
|
118
|
-
console.log(chalk.green('Task added successfully!'));
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// use command 'list' with optional status filter and action
|
|
123
|
-
program
|
|
124
|
-
.command('list')
|
|
125
|
-
.alias('ls')
|
|
126
|
-
.description('List all tasks')
|
|
127
|
-
.option('-s, --status <status>', 'Filter tasks by status (todo, in-progress, done)')
|
|
128
|
-
.action(async (options) => {
|
|
129
|
-
let tasks = await loadTasks();
|
|
130
|
-
|
|
131
|
-
if (options.status) {
|
|
132
|
-
if (!ALLOWED_STATUSES.includes(options.status)){
|
|
133
|
-
console.log(chalk.red(`Invalid status filter. Allowed statuses are: ${ALLOWED_STATUSES.join(", ")}`));
|
|
134
|
-
cleanupAndExit(0);
|
|
135
|
-
}
|
|
136
|
-
tasks = tasks.filter(task => task.status === options.status);
|
|
137
|
-
}
|
|
138
|
-
// display all tasks in table format
|
|
139
|
-
displayTasks(tasks);
|
|
140
|
-
cleanupAndExit(0);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// use command 'search' to find tasks by keyword in title or description
|
|
146
|
-
program
|
|
147
|
-
.command('search [keyword]')
|
|
148
|
-
.alias('sr')
|
|
149
|
-
.option('-f, --find <keyword>', 'Keyword to search in title or description')
|
|
150
|
-
.description('Search tasks by keyword in title or description')
|
|
151
|
-
.action(async (keyword, options) => {
|
|
152
|
-
const tasks = await loadTasks();
|
|
153
|
-
let searchTerm = keyword || options.find;
|
|
154
|
-
|
|
155
|
-
if (!searchTerm) {
|
|
156
|
-
enableEscExit();
|
|
157
|
-
const answers = await inquirer.prompt([
|
|
158
|
-
{
|
|
159
|
-
type: 'input',
|
|
160
|
-
name: 'term',
|
|
161
|
-
message: 'Enter the keyword you want to search for:',
|
|
162
|
-
validate: input => input ? true : 'Keyword cannot be empty!'
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
type: 'rawlist',
|
|
166
|
-
name: 'field',
|
|
167
|
-
message: 'Where do you want to search?',
|
|
168
|
-
choices: ['title', 'description', 'both'],
|
|
169
|
-
default: 'both'
|
|
170
|
-
}
|
|
171
|
-
]);
|
|
172
|
-
searchTerm = answers.term;
|
|
173
|
-
const field = answers.field;
|
|
174
|
-
|
|
175
|
-
const founded = tasks.filter(task => {
|
|
176
|
-
if (field === 'title') return task.title.toLowerCase().includes(searchTerm.toLowerCase());
|
|
177
|
-
if (field === 'description') return task.description.toLowerCase().includes(searchTerm.toLowerCase());
|
|
178
|
-
return task.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
179
|
-
task.description.toLowerCase().includes(searchTerm.toLowerCase());
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
if (!founded.length) {
|
|
183
|
-
console.log(chalk.red('No task matched your search!'));
|
|
184
|
-
cleanupAndExit(0);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
displayTasks(founded);
|
|
188
|
-
cleanupAndExit(0);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const founded = tasks.filter(task =>
|
|
192
|
-
task.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
193
|
-
task.description.toLowerCase().includes(searchTerm.toLowerCase())
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
if (!founded.length) {
|
|
197
|
-
console.log(chalk.red('No task matched your search!'));
|
|
198
|
-
cleanupAndExit(0);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
displayTasks(founded);
|
|
202
|
-
cleanupAndExit(0);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// use command 'sort' to sort tasks by due date, priority, or status
|
|
209
|
-
program
|
|
210
|
-
.command('sort')
|
|
211
|
-
.alias('so')
|
|
212
|
-
.option('--by <criteria>', 'Sort tasks by criteria (dueDate, priority, status)')
|
|
213
|
-
.description('Sort tasks by due date, priority, or status')
|
|
214
|
-
.action( async (options) => {
|
|
215
|
-
|
|
216
|
-
const tasks = await loadTasks();
|
|
217
|
-
let criteria = options.by;
|
|
218
|
-
|
|
219
|
-
if (!criteria) {
|
|
220
|
-
enableEscExit();
|
|
221
|
-
const answer = await inquirer.prompt([
|
|
222
|
-
{
|
|
223
|
-
type: 'rawlist',
|
|
224
|
-
name: 'criteria',
|
|
225
|
-
message: 'Sort tasks by:',
|
|
226
|
-
choices: ['dueDate', 'priority', 'status']
|
|
227
|
-
}
|
|
228
|
-
]);
|
|
229
|
-
criteria = answer.criteria;
|
|
230
|
-
}
|
|
231
|
-
// normailze input `case-insensitive + separators`
|
|
232
|
-
criteria = criteria.toLowerCase().replace(/[-_]/g, '');
|
|
233
|
-
|
|
234
|
-
if (!['duedate', 'priority', 'status'].includes(criteria)) {
|
|
235
|
-
console.log(chalk.red('Invalid sort criteria. Use --by with dueDate, priority, or status.'));
|
|
236
|
-
cleanupAndExit(0);
|
|
237
|
-
}
|
|
238
|
-
// sorting logic
|
|
239
|
-
const sortedTasks = [...tasks];
|
|
240
|
-
|
|
241
|
-
switch (criteria) {
|
|
242
|
-
case 'duedate':
|
|
243
|
-
sortedTasks.sort((a, b) => new Date(a.dueDate) - new Date(b.dueDate));
|
|
244
|
-
break;
|
|
245
|
-
case 'priority':
|
|
246
|
-
const priorityOrder = { 'high': 1, 'medium': 2, 'low': 3 };
|
|
247
|
-
sortedTasks.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
248
|
-
break;
|
|
249
|
-
case 'status':
|
|
250
|
-
const statusOrder = { 'todo': 1, 'in-progress': 2, 'done': 3 };
|
|
251
|
-
sortedTasks.sort((a, b) => statusOrder[a.status] - statusOrder[b.status]);
|
|
252
|
-
break;
|
|
253
|
-
default:
|
|
254
|
-
console.log(chalk.red('Invalid sort criteria. Use dueDate, priority, or status.'));
|
|
255
|
-
cleanupAndExit(0);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
displayTasks(sortedTasks);
|
|
259
|
-
cleanupAndExit(0);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
// use command 'update' with task ID and action
|
|
264
|
-
program
|
|
265
|
-
.command('update')
|
|
266
|
-
.alias('up')
|
|
267
|
-
.description('Update a task by ==> ID <==')
|
|
268
|
-
.action(async () =>{
|
|
269
|
-
const tasks = await loadTasks();
|
|
270
|
-
if (!tasks.length) {
|
|
271
|
-
console.log(chalk.red('No tasks found to update.'));
|
|
272
|
-
cleanupAndExit(0);
|
|
273
|
-
}
|
|
274
|
-
enableEscExit();
|
|
275
|
-
const { id } = await inquirer.prompt([
|
|
276
|
-
{
|
|
277
|
-
type: 'rawlist',
|
|
278
|
-
name: 'id',
|
|
279
|
-
message: 'Select the task to update (By ID):',
|
|
280
|
-
choices: tasks.map(task => ({ name: `${task.id}: ${task.title}`, value: task.id }))
|
|
281
|
-
}
|
|
282
|
-
]);
|
|
283
|
-
|
|
284
|
-
// find the task to update
|
|
285
|
-
const task = tasks.find(t => t.id === Number(id));
|
|
286
|
-
if (!task) {
|
|
287
|
-
console.log(chalk.red('Task not found!'));
|
|
288
|
-
cleanupAndExit(0);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
enableEscExit();
|
|
292
|
-
const answers = await inquirer.prompt([
|
|
293
|
-
// for title
|
|
294
|
-
{
|
|
295
|
-
type: 'confirm',
|
|
296
|
-
name: 'changeTitle',
|
|
297
|
-
message: 'Do you want to change the title?',
|
|
298
|
-
default: false
|
|
299
|
-
},
|
|
300
|
-
{
|
|
301
|
-
type: 'input',
|
|
302
|
-
name: 'title',
|
|
303
|
-
message: 'Enter the new title:',
|
|
304
|
-
when: answers => answers.changeTitle,
|
|
305
|
-
validate: input => input ? true : 'Title cannot be empty!'
|
|
306
|
-
},
|
|
307
|
-
|
|
308
|
-
// for status
|
|
309
|
-
{
|
|
310
|
-
type: 'confirm',
|
|
311
|
-
name: 'changeStatus',
|
|
312
|
-
message: 'Do you want to change the status?',
|
|
313
|
-
default: false
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
type: 'rawlist',
|
|
317
|
-
name: 'status',
|
|
318
|
-
message: 'Select the new status:',
|
|
319
|
-
choices: ALLOWED_STATUSES,
|
|
320
|
-
when: answers => answers.changeStatus
|
|
321
|
-
},
|
|
322
|
-
|
|
323
|
-
// for priority
|
|
324
|
-
{
|
|
325
|
-
type: 'confirm',
|
|
326
|
-
name: 'changePriority',
|
|
327
|
-
message: 'Do you want to change the priority?',
|
|
328
|
-
default: false
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
type: 'rawlist',
|
|
332
|
-
name: 'priority',
|
|
333
|
-
message: 'Select the new priority:',
|
|
334
|
-
choices: ALLOWED_PRIORITIES,
|
|
335
|
-
when: answers => answers.changePriority
|
|
336
|
-
},
|
|
337
|
-
|
|
338
|
-
// for due date
|
|
339
|
-
{
|
|
340
|
-
type: 'confirm',
|
|
341
|
-
name: 'changeDueDate',
|
|
342
|
-
message: 'Do you want to change the due date?',
|
|
343
|
-
default: false
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
type: 'input',
|
|
347
|
-
name: 'dueDate',
|
|
348
|
-
message: 'Enter the new due date (YYYY-MM-DD):',
|
|
349
|
-
when: answers => answers.changeDueDate,
|
|
350
|
-
validate: input => {
|
|
351
|
-
try {
|
|
352
|
-
validateDueDate(input);
|
|
353
|
-
return true;
|
|
354
|
-
} catch (error) {
|
|
355
|
-
return error.message;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
},
|
|
359
|
-
|
|
360
|
-
// for description
|
|
361
|
-
{
|
|
362
|
-
type: 'confirm',
|
|
363
|
-
name: 'changeDescription',
|
|
364
|
-
message: 'Do you want to change the description?',
|
|
365
|
-
default: false
|
|
366
|
-
},
|
|
367
|
-
{
|
|
368
|
-
type: "input",
|
|
369
|
-
name: 'description',
|
|
370
|
-
message: 'Enter the new description:',
|
|
371
|
-
when: answers => answers.changeDescription,
|
|
372
|
-
validate: input => input.trim() ? true : 'Description cannot be empty!'
|
|
373
|
-
}
|
|
374
|
-
]);
|
|
375
|
-
|
|
376
|
-
// apply updates only if user chose to change them
|
|
377
|
-
if (answers.changeTitle) task.title = answers.title;
|
|
378
|
-
if (answers.changeStatus) task.status = answers.status;
|
|
379
|
-
if (answers.changePriority) task.priority = answers.priority;
|
|
380
|
-
if (answers.changeDueDate) task.dueDate = answers.dueDate;
|
|
381
|
-
if (answers.changeDescription)
|
|
382
|
-
task.description = answers.description || '';
|
|
383
|
-
|
|
384
|
-
// is there any change?
|
|
385
|
-
const hasChanges = [
|
|
386
|
-
answers.changeTitle,
|
|
387
|
-
answers.changeStatus,
|
|
388
|
-
answers.changePriority,
|
|
389
|
-
answers.changeDueDate,
|
|
390
|
-
answers.changeDescription
|
|
391
|
-
].some(Boolean);
|
|
392
|
-
|
|
393
|
-
if (!hasChanges) {
|
|
394
|
-
console.log(chalk.yellow('No changes were made.'));
|
|
395
|
-
}else {
|
|
396
|
-
// save updated tasks to file
|
|
397
|
-
await saveTasks(tasks);
|
|
398
|
-
console.log(chalk.green('Task updated successfully!'));
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
displayTasks(tasks);
|
|
402
|
-
cleanupAndExit(0);
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
// use command 'done' with task ID instead of 'update' + confirm to mark task as done
|
|
407
|
-
program
|
|
408
|
-
.command('done')
|
|
409
|
-
.description('Mark a task as done by ==> ID <==')
|
|
410
|
-
.action(async () => {
|
|
411
|
-
const tasks = await loadTasks();
|
|
412
|
-
if (!tasks.length) {
|
|
413
|
-
console.log(chalk.magenta('Congratulations! All tasks are already done.'));
|
|
414
|
-
cleanupAndExit(0);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const activeTasks = tasks.filter(t => t.status !== 'done');
|
|
418
|
-
if (!activeTasks.length) {
|
|
419
|
-
console.log(chalk.magenta('Congratulations! All tasks are already done.'));
|
|
420
|
-
cleanupAndExit(0);
|
|
421
|
-
}
|
|
422
|
-
enableEscExit();
|
|
423
|
-
const { id } = await inquirer.prompt([
|
|
424
|
-
{
|
|
425
|
-
type: 'rawlist',
|
|
426
|
-
name: 'id',
|
|
427
|
-
message: 'Select the task to mark as done:',
|
|
428
|
-
choices: activeTasks.map(t => (
|
|
429
|
-
{
|
|
430
|
-
name: `${t.id}: ${t.title}[Current Status: ${t.status}]`,
|
|
431
|
-
value: t.id
|
|
432
|
-
}
|
|
433
|
-
))
|
|
434
|
-
}
|
|
435
|
-
]);
|
|
436
|
-
|
|
437
|
-
const task = tasks.find(t => t.id === Number(id));
|
|
438
|
-
task.status = 'done';
|
|
439
|
-
await saveTasks(tasks);
|
|
440
|
-
console.log(chalk.green('Task marked as done successfully!'));
|
|
441
|
-
|
|
442
|
-
displayTasks(tasks);
|
|
443
|
-
cleanupAndExit(0);
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
// use command 'delete' with task ID and action
|
|
449
|
-
program
|
|
450
|
-
.command('delete')
|
|
451
|
-
.alias('del')
|
|
452
|
-
.description('delete a task by ==> ID <==')
|
|
453
|
-
.action(async () => {
|
|
454
|
-
const tasks = await loadTasks();
|
|
455
|
-
if (!tasks.length) {
|
|
456
|
-
console.log(chalk.yellow('No tasks found to delete.'));
|
|
457
|
-
cleanupAndExit(0);
|
|
458
|
-
}
|
|
459
|
-
enableEscExit();
|
|
460
|
-
const { id } = await inquirer.prompt([
|
|
461
|
-
{
|
|
462
|
-
type: 'rawlist',
|
|
463
|
-
name: 'id',
|
|
464
|
-
message: 'Select the task to delete:',
|
|
465
|
-
choices: tasks.map(task => ({ name: `${task.id}: ${task.title}`, value: task.id }))
|
|
466
|
-
}
|
|
467
|
-
]);
|
|
468
|
-
enableEscExit();
|
|
469
|
-
const { confirm } = await inquirer.prompt([
|
|
470
|
-
{
|
|
471
|
-
type: 'confirm',
|
|
472
|
-
name: 'confirm',
|
|
473
|
-
message: 'Are you sure you want to delete this task?',
|
|
474
|
-
default: false
|
|
475
|
-
}
|
|
476
|
-
]);
|
|
477
|
-
if (!confirm) {
|
|
478
|
-
console.log(chalk.yellow('Task deletion cancelled.'));
|
|
479
|
-
cleanupAndExit(0);
|
|
480
|
-
}
|
|
481
|
-
const taskToDelete = tasks.find(t => t.id === Number(id));
|
|
482
|
-
// save deleted task for undo functionality
|
|
483
|
-
await saveDeletedTask(taskToDelete);
|
|
484
|
-
// filter out the deleted task
|
|
485
|
-
const newTasks = tasks.filter(t => t.id !== Number(id));
|
|
486
|
-
|
|
487
|
-
await saveTasks(newTasks);
|
|
488
|
-
console.log(chalk.green('Task deleted successfully!'));
|
|
489
|
-
console.log(chalk.cyan('You can undo this action by using the `undo` command.'));
|
|
490
|
-
|
|
491
|
-
displayTasks(newTasks);
|
|
492
|
-
cleanupAndExit(0);
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
// use command 'undo' to restore last deleted task
|
|
496
|
-
program
|
|
497
|
-
.command('undo')
|
|
498
|
-
.alias('un')
|
|
499
|
-
.description('Undo the last deleted task')
|
|
500
|
-
.action( async () => {
|
|
501
|
-
await cleanupExpiredDeletedTasks();
|
|
502
|
-
|
|
503
|
-
const deletedTasks = await loadDeletedTask();
|
|
504
|
-
if (!deletedTasks || !deletedTasks.length) {
|
|
505
|
-
console.log(chalk.yellow('No deleted task to restore.'));
|
|
506
|
-
cleanupAndExit(0);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
const lastDeletedTask = deletedTasks.pop();
|
|
510
|
-
|
|
511
|
-
const tasks = await loadTasks();
|
|
512
|
-
tasks.push(lastDeletedTask);
|
|
513
|
-
tasks.sort((a, b) => a.id - b.id); // keep tasks sorted by ID
|
|
514
|
-
await saveTasks(tasks);
|
|
515
|
-
// await clearDeletedTask();
|
|
516
|
-
|
|
517
|
-
console.log(chalk.green(`Last deleted task restored successfully!, (Task name: ${lastDeletedTask.title})`));
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
// parse command line arguments
|
|
523
|
-
program.parse(process.argv);
|