taskninja 1.0.2 → 1.0.4
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 +206 -190
- package/app.js +138 -74
- package/package.json +1 -1
- package/taskService.js +25 -0
- package/todos.json +1 -0
- package/validators.js +26 -0
package/README.md
CHANGED
|
@@ -1,30 +1,23 @@
|
|
|
1
|
-
# TaskNinja
|
|
2
|
-
|
|
3
|
-
 <!-- Optional: Replace with actual logo if available -->
|
|
4
|
-
|
|
5
|
-
A simple Command-Line Interface (CLI) application built with JavaScript for managing your to-do tasks. It allows users to add, list, update, and delete tasks interactively, with features like status tracking, priority levels, due dates, and descriptions. Tasks are stored in a local JSON file for persistence.
|
|
6
1
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Features
|
|
2
|
+
# TaskNinja
|
|
10
3
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- **Update Tasks**: Change the status of existing tasks via interactive selection.
|
|
14
|
-
- **Delete Tasks**: Remove tasks with confirmation to prevent accidental deletion.
|
|
15
|
-
- **Persistent Storage**: Tasks are saved to a local `todos.json` file.
|
|
16
|
-
- **Interactive Prompts**: Uses Inquirer for user-friendly input.
|
|
17
|
-
- **Validation**: Ensures valid statuses, priorities, and date formats.
|
|
4
|
+
**Version:** 1.0.4
|
|
5
|
+
**Description:** A simple CLI To-Do Application to manage your tasks directly from the terminal.
|
|
18
6
|
|
|
19
|
-
|
|
7
|
+
---
|
|
20
8
|
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
9
|
+
## Table of Contents
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Running the CLI](#running-the-cli)
|
|
12
|
+
- [Commands](#commands)
|
|
13
|
+
- [Add Task](#add-task)
|
|
14
|
+
- [List Tasks](#list-tasks)
|
|
15
|
+
- [Update Task](#update-task)
|
|
16
|
+
- [Delete Task](#delete-task)
|
|
17
|
+
- [Task Fields & Allowed Values](#task-fields--allowed-values)
|
|
18
|
+
- [Examples](#examples)
|
|
26
19
|
|
|
27
|
-
|
|
20
|
+
---
|
|
28
21
|
|
|
29
22
|
## Installation
|
|
30
23
|
|
|
@@ -36,198 +29,221 @@ TaskNinja is published on npm as `taskninja`. Install it globally to use the CLI
|
|
|
36
29
|
npm install -g taskninja
|
|
37
30
|
```
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
3. Updating a task:
|
|
130
|
-
```
|
|
131
|
-
dotask update
|
|
132
|
-
```
|
|
133
|
-
- Select task ID from the list and choose new status (e.g., "done").
|
|
134
|
-
|
|
135
|
-
4. Deleting a task:
|
|
136
|
-
```
|
|
137
|
-
dotask delete
|
|
138
|
-
```
|
|
139
|
-
- Select task ID and confirm.
|
|
140
|
-
|
|
141
|
-
Tasks are stored in `./todos.json` relative to where the command is run. Ensure write permissions in the directory.
|
|
142
|
-
|
|
143
|
-
## Configuration
|
|
144
|
-
|
|
145
|
-
- **Task File**: Defaults to `./todos.json`. You can modify `TASKS_FILE` in `app.js` if needed.
|
|
146
|
-
- **Statuses**: Limited to "todo", "in-progress", "done".
|
|
147
|
-
- **Priorities**: Limited to "low", "medium", "high".
|
|
148
|
-
- **Due Date Format**: YYYY-MM-DD (validated during input).
|
|
149
|
-
|
|
150
|
-
No external configuration files are required.
|
|
151
|
-
|
|
152
|
-
## Development
|
|
153
|
-
|
|
154
|
-
To run in development mode:
|
|
32
|
+
or use it instantly with:
|
|
33
|
+
```bash
|
|
34
|
+
npx taskninja
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
and then use it:
|
|
38
|
+
```bash
|
|
39
|
+
npx taskninja <command>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
After installation, you can run the CLI using the `dotask` or `taskninja` or `tn` command (as defined in `package.json`).
|
|
43
|
+
|
|
44
|
+
Clone the repository and install dependencies:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone <your-repo-url>
|
|
48
|
+
cd taskninja
|
|
49
|
+
npm install
|
|
50
|
+
````
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Running the CLI
|
|
55
|
+
|
|
56
|
+
You can run the CLI using either `node` or the bin aliases:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Using node
|
|
60
|
+
node app.js <command>
|
|
61
|
+
|
|
62
|
+
# Using bin aliases
|
|
63
|
+
dotask <command>
|
|
64
|
+
taskninja <command>
|
|
65
|
+
tn <command>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Replace `<command>` with any of the available commands: `add`, `list`, `update`, `delete`.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Commands
|
|
73
|
+
|
|
74
|
+
### 1. Add Task
|
|
75
|
+
|
|
76
|
+
**Alias:** `a`
|
|
77
|
+
**Description:** Add a new task interactively.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
node app.js add
|
|
81
|
+
# or
|
|
82
|
+
tn a
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Prompts:**
|
|
86
|
+
|
|
87
|
+
1. **Task Title:** Enter any text (cannot be empty).
|
|
88
|
+
2. **Task Status:** Select from allowed values using arrows:
|
|
89
|
+
|
|
90
|
+
* todo
|
|
91
|
+
* in-progress
|
|
92
|
+
* done
|
|
93
|
+
3. **Task Priority:** Select from allowed values using arrows:
|
|
94
|
+
|
|
95
|
+
* low
|
|
96
|
+
* medium
|
|
97
|
+
* high
|
|
98
|
+
4. **Due Date:** Enter in `YYYY-MM-DD` format. Invalid dates will show a validation error.
|
|
99
|
+
5. **Description:** Optional text.
|
|
100
|
+
|
|
101
|
+
**Expected Behavior:**
|
|
102
|
+
|
|
103
|
+
* Task is saved in `todos.json`.
|
|
104
|
+
* Confirmation message: `Task added successfully!`
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### 2. List Tasks
|
|
109
|
+
|
|
110
|
+
**Alias:** `ls`
|
|
111
|
+
**Description:** List all tasks, optionally filtered by status.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
node app.js list
|
|
115
|
+
# or
|
|
116
|
+
tn ls
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Optional Status Filter:**
|
|
120
|
+
|
|
155
121
|
```bash
|
|
156
|
-
|
|
122
|
+
node app.js list --status todo
|
|
157
123
|
```
|
|
158
124
|
|
|
159
|
-
|
|
125
|
+
**Behavior:**
|
|
160
126
|
|
|
161
|
-
|
|
127
|
+
* Displays tasks in a table with columns:
|
|
162
128
|
|
|
163
|
-
|
|
164
|
-
-
|
|
129
|
+
| # | ID | Title | Status | Priority | DueDate | Description |
|
|
130
|
+
| - | -- | ----- | ------ | -------- | ------- | ----------- |
|
|
165
131
|
|
|
166
|
-
|
|
132
|
+
* Numbered index (`#`) starts from 1.
|
|
167
133
|
|
|
168
|
-
|
|
134
|
+
---
|
|
169
135
|
|
|
170
|
-
###
|
|
136
|
+
### 3. Update Task
|
|
171
137
|
|
|
172
|
-
|
|
173
|
-
|
|
138
|
+
**Alias:** `up`
|
|
139
|
+
**Description:** Update an existing task by selecting its ID.
|
|
174
140
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
141
|
+
```bash
|
|
142
|
+
node app.js update
|
|
143
|
+
# or
|
|
144
|
+
tn up
|
|
145
|
+
```
|
|
180
146
|
|
|
181
|
-
|
|
182
|
-
- Use a descriptive name: `git checkout -b feature/new-feature` or `git checkout -b fix/bug-fix`.
|
|
147
|
+
**Steps:**
|
|
183
148
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- Ensure your code handles errors gracefully and maintains validation logic.
|
|
149
|
+
1. Select task by ID from a list (use arrows).
|
|
150
|
+
2. Confirm which fields to change (title, status, priority, due date, description).
|
|
151
|
+
3. Enter new values where applicable (status/priority selections use arrows).
|
|
188
152
|
|
|
189
|
-
|
|
190
|
-
- Use clear commit messages: e.g., "feat: add search functionality" or "fix: resolve date validation issue".
|
|
191
|
-
- Reference issues if relevant: e.g., "Closes #123".
|
|
153
|
+
**Behavior:**
|
|
192
154
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
```
|
|
155
|
+
* Only fields selected for change are updated.
|
|
156
|
+
* Confirmation message: `Task updated successfully!`
|
|
157
|
+
* Shows updated task table.
|
|
197
158
|
|
|
198
|
-
|
|
199
|
-
- Go to the original repo and click "New Pull Request".
|
|
200
|
-
- Provide a detailed description of your changes, including why they're needed and any relevant screenshots.
|
|
201
|
-
- Link to any related issues.
|
|
159
|
+
---
|
|
202
160
|
|
|
203
|
-
###
|
|
161
|
+
### 4. Delete Task
|
|
204
162
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
- **Dependencies**: Keep them minimal. Justify any new additions.
|
|
208
|
-
- **Features**: New commands or options should align with the simple CLI philosophy. Discuss major changes in an issue first.
|
|
209
|
-
- **Documentation**: Update README.md with any new features or changes.
|
|
210
|
-
- **Issues**: Check for existing issues before creating a new one. Use labels like "bug", "enhancement", or "question".
|
|
163
|
+
**Alias:** `del`
|
|
164
|
+
**Description:** Delete a task by selecting its ID.
|
|
211
165
|
|
|
212
|
-
|
|
166
|
+
```bash
|
|
167
|
+
node app.js delete
|
|
168
|
+
# or
|
|
169
|
+
tn del
|
|
170
|
+
```
|
|
213
171
|
|
|
214
|
-
|
|
215
|
-
- No harassment or discrimination.
|
|
216
|
-
- Report issues to the maintainer (Mohamed Bakr).
|
|
172
|
+
**Steps:**
|
|
217
173
|
|
|
218
|
-
|
|
174
|
+
1. Select task by ID from a list (use arrows).
|
|
175
|
+
2. Confirm deletion.
|
|
219
176
|
|
|
220
|
-
|
|
177
|
+
**Behavior:**
|
|
221
178
|
|
|
222
|
-
|
|
179
|
+
* Task is removed from `todos.json`.
|
|
180
|
+
* Confirmation message: `Task deleted successfully!`
|
|
181
|
+
* Shows updated task table.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Task Fields & Allowed Values
|
|
186
|
+
|
|
187
|
+
| Field | Allowed Values / Description |
|
|
188
|
+
| ----------- | ---------------------------- |
|
|
189
|
+
| title | Any non-empty string |
|
|
190
|
+
| status | todo, in-progress, done |
|
|
191
|
+
| priority | low, medium, high |
|
|
192
|
+
| dueDate | YYYY-MM-DD |
|
|
193
|
+
| description | Optional text |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Examples
|
|
198
|
+
|
|
199
|
+
**Adding a Task:**
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
tn a
|
|
203
|
+
```
|
|
223
204
|
|
|
224
|
-
|
|
205
|
+
```
|
|
206
|
+
Task Title: Buy groceries
|
|
207
|
+
Task Status: → todo
|
|
208
|
+
Task Priority: → medium
|
|
209
|
+
Due Date (YYYY-MM-DD): 2026-02-01
|
|
210
|
+
Task Description (optional): Buy fruits and vegetables
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Listing Tasks:**
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
tn ls
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
┌─┬────┬────────────────┬─────────────┬─────────┬────────────┬─────────────────────────────┐
|
|
221
|
+
│#│ ID │ Title │ Status │ Priority│ DueDate │ Description │
|
|
222
|
+
├─┼────┼────────────────┼─────────────┼─────────┼────────────┼─────────────────────────────┤
|
|
223
|
+
│1│ 1 │ Buy groceries │ todo │ medium │ 2026-02-01 │ Buy fruits and vegetables │
|
|
224
|
+
└─┴────┴────────────────┴─────────────┴─────────┴────────────┴─────────────────────────────┘
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Updating a Task:**
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
tn up
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
* Select task ID → confirm fields → update values
|
|
234
|
+
* Shows updated table.
|
|
235
|
+
|
|
236
|
+
**Deleting a Task:**
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
tn del
|
|
240
|
+
```
|
|
225
241
|
|
|
226
|
-
|
|
242
|
+
* Select task ID → confirm deletion → updated table shown
|
|
227
243
|
|
|
228
|
-
|
|
244
|
+
---
|
|
229
245
|
|
|
230
|
-
|
|
231
|
-
- Thanks to the open-source community for libraries like Commander and Inquirer.
|
|
246
|
+
## Notes
|
|
232
247
|
|
|
233
|
-
|
|
248
|
+
* All selection prompts (`status`, `priority`, task selection) use arrow keys in terminal.
|
|
249
|
+
* Task table always shows numbered index (`#`) starting from 1 for easy reference.
|
package/app.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
|
-
|
|
3
2
|
/**
|
|
4
3
|
* Task Manager CLI Application
|
|
5
4
|
* This application allows users to manage their tasks via command line interface.
|
|
@@ -17,70 +16,28 @@
|
|
|
17
16
|
import { Command } from "commander";
|
|
18
17
|
// for interactive command line prompts
|
|
19
18
|
import inquirer from "inquirer";
|
|
20
|
-
// for file system operations
|
|
21
|
-
import fs from "fs/promises";
|
|
22
19
|
|
|
23
20
|
// assigning Commander to a variable
|
|
24
21
|
const program = new Command();
|
|
25
22
|
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
// allowed task priorities
|
|
31
|
-
const ALLOWED_PRIORITIES = ["low", "medium", "high"];
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// function to load tasks from file
|
|
35
|
-
const loadTasks = async () => {
|
|
36
|
-
try{
|
|
37
|
-
const data = await fs.readFile(TASKS_FILE, 'utf8');
|
|
38
|
-
return JSON.parse(data);
|
|
39
|
-
} catch (error) {
|
|
40
|
-
if (error.code === 'ENOENT') return [];
|
|
41
|
-
throw error;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
// function to save tasks to file
|
|
45
|
-
const saveTasks = async (tasks) => {
|
|
46
|
-
await fs.writeFile(TASKS_FILE, JSON.stringify(tasks, null, 2));
|
|
47
|
-
};
|
|
48
|
-
// get next task ID
|
|
49
|
-
const getNextId = (tasks) => {
|
|
50
|
-
return tasks.length > 0 ? Math.max(...tasks.map(task => task.id)) + 1 : 1;
|
|
51
|
-
};
|
|
52
|
-
// validate task status
|
|
53
|
-
const validateStatus = (status) => {
|
|
54
|
-
if (!ALLOWED_STATUSES.includes(status)) {
|
|
55
|
-
throw new Error(`Invalid status. Allowed statuses are: ${ALLOWED_STATUSES.join(", ")}`);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
// validate task priority
|
|
59
|
-
const validatePriority = (priority) => {
|
|
60
|
-
if (!ALLOWED_PRIORITIES.includes(priority)) {
|
|
61
|
-
throw new Error(`Invalid priority. Allowed priorities are: ${ALLOWED_PRIORITIES.join(", ")}`);
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
// verify due date format
|
|
65
|
-
const validateDueDate = (dueDate) => {
|
|
66
|
-
if (isNaN(Date.parse(dueDate))) {
|
|
67
|
-
throw new Error("Invalid due date. Please use a valid date format (YYYY-MM-DD).");
|
|
68
|
-
}
|
|
69
|
-
};
|
|
23
|
+
// importing validators and allowed values
|
|
24
|
+
import { validateDueDate, ALLOWED_PRIORITIES, ALLOWED_STATUSES } from "./validators.js";
|
|
25
|
+
// importing task service functions
|
|
26
|
+
import { loadTasks, saveTasks, getNextId } from "./taskService.js";
|
|
70
27
|
|
|
71
28
|
|
|
72
29
|
// setting up
|
|
73
30
|
program
|
|
74
31
|
.name("todo-cli")
|
|
75
32
|
.description("A simple CLI application to manage your tasks")
|
|
76
|
-
.version("1.0.
|
|
33
|
+
.version("1.0.4");
|
|
77
34
|
|
|
78
35
|
// use command 'add' with title + status + priority + dueDate + description and action
|
|
79
36
|
program
|
|
80
37
|
.command('add')
|
|
81
38
|
.alias('a')
|
|
82
39
|
.description('Add a new task')
|
|
83
|
-
.action(async (
|
|
40
|
+
.action(async () => {
|
|
84
41
|
const answers = await inquirer.prompt([
|
|
85
42
|
{
|
|
86
43
|
type: 'input',
|
|
@@ -89,14 +46,14 @@ program
|
|
|
89
46
|
validate : input => input ? true : 'Title cannot be empty!'
|
|
90
47
|
},
|
|
91
48
|
{
|
|
92
|
-
type: '
|
|
49
|
+
type: 'rawlist',
|
|
93
50
|
name: 'status',
|
|
94
51
|
message : 'Task Status:',
|
|
95
52
|
choices : ALLOWED_STATUSES,
|
|
96
53
|
default : 'todo'
|
|
97
54
|
},
|
|
98
55
|
{
|
|
99
|
-
type: '
|
|
56
|
+
type: 'rawlist',
|
|
100
57
|
name: 'priority',
|
|
101
58
|
message : 'Task Priority:',
|
|
102
59
|
choices : ALLOWED_PRIORITIES,
|
|
@@ -146,7 +103,7 @@ program
|
|
|
146
103
|
.command('list')
|
|
147
104
|
.alias('ls')
|
|
148
105
|
.description('List all tasks')
|
|
149
|
-
.option('-s, --status <status>', 'Filter tasks by status')
|
|
106
|
+
.option('-s, --status <status>', 'Filter tasks by status (todo, in-progress, done)')
|
|
150
107
|
.action(async (options) => {
|
|
151
108
|
let tasks = await loadTasks();
|
|
152
109
|
|
|
@@ -162,7 +119,8 @@ program
|
|
|
162
119
|
return;
|
|
163
120
|
}
|
|
164
121
|
// map all tasks to displayable objects
|
|
165
|
-
const tableData = tasks.map(task => ({
|
|
122
|
+
const tableData = tasks.map((task, index) => ({
|
|
123
|
+
'#': index + 1,
|
|
166
124
|
ID: task.id,
|
|
167
125
|
Title: task.title,
|
|
168
126
|
Status: task.status,
|
|
@@ -179,7 +137,7 @@ program
|
|
|
179
137
|
program
|
|
180
138
|
.command('update')
|
|
181
139
|
.alias('up')
|
|
182
|
-
.description('Update a task by ID')
|
|
140
|
+
.description('Update a task by ==> ID <==')
|
|
183
141
|
.action(async () =>{
|
|
184
142
|
const tasks = await loadTasks();
|
|
185
143
|
if (tasks.length === 0) {
|
|
@@ -189,46 +147,148 @@ program
|
|
|
189
147
|
|
|
190
148
|
const { id } = await inquirer.prompt([
|
|
191
149
|
{
|
|
192
|
-
type: '
|
|
150
|
+
type: 'rawlist',
|
|
193
151
|
name: 'id',
|
|
194
|
-
message: 'Select the task to update:',
|
|
152
|
+
message: 'Select the task to update (By ID):',
|
|
195
153
|
choices: tasks.map(task => ({ name: `${task.id}: ${task.title}`, value: task.id }))
|
|
196
154
|
}
|
|
197
155
|
]);
|
|
198
|
-
const { status } = await inquirer.prompt([
|
|
199
|
-
{
|
|
200
|
-
type: 'list',
|
|
201
|
-
name: 'status',
|
|
202
|
-
message: 'Select the new status:',
|
|
203
|
-
choices: ALLOWED_STATUSES
|
|
204
|
-
}
|
|
205
|
-
]);
|
|
206
156
|
|
|
157
|
+
// find the task to update
|
|
207
158
|
const task = tasks.find(t => t.id === Number(id));
|
|
208
159
|
if (!task) {
|
|
209
160
|
console.error('Task not found!');
|
|
210
161
|
return;
|
|
211
162
|
}
|
|
212
|
-
task.status = status;
|
|
213
163
|
|
|
214
|
-
await
|
|
215
|
-
|
|
164
|
+
const answers = await inquirer.prompt([
|
|
165
|
+
// for title
|
|
166
|
+
{
|
|
167
|
+
type: 'confirm',
|
|
168
|
+
name: 'changeTitle',
|
|
169
|
+
message: 'Do you want to change the title?',
|
|
170
|
+
default: false
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
type: 'input',
|
|
174
|
+
name: 'title',
|
|
175
|
+
message: 'Enter the new title:',
|
|
176
|
+
when: answers => answers.changeTitle,
|
|
177
|
+
validate: input => input ? true : 'Title cannot be empty!'
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
// for status
|
|
181
|
+
{
|
|
182
|
+
type: 'confirm',
|
|
183
|
+
name: 'changeStatus',
|
|
184
|
+
message: 'Do you want to change the status?',
|
|
185
|
+
default: false
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
type: 'rawlist',
|
|
189
|
+
name: 'status',
|
|
190
|
+
message: 'Select the new status:',
|
|
191
|
+
choices: ALLOWED_STATUSES,
|
|
192
|
+
when: answers => answers.changeStatus
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
// for priority
|
|
196
|
+
{
|
|
197
|
+
type: 'confirm',
|
|
198
|
+
name: 'changePriority',
|
|
199
|
+
message: 'Do you want to change the priority?',
|
|
200
|
+
default: false
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
type: 'rawlist',
|
|
204
|
+
name: 'priority',
|
|
205
|
+
message: 'Select the new priority:',
|
|
206
|
+
choices: ALLOWED_PRIORITIES,
|
|
207
|
+
when: answers => answers.changePriority
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
// for due date
|
|
211
|
+
{
|
|
212
|
+
type: 'confirm',
|
|
213
|
+
name: 'changeDueDate',
|
|
214
|
+
message: 'Do you want to change the due date?',
|
|
215
|
+
default: false
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
type: 'input',
|
|
219
|
+
name: 'dueDate',
|
|
220
|
+
message: 'Enter the new due date (YYYY-MM-DD):',
|
|
221
|
+
when: answers => answers.changeDueDate,
|
|
222
|
+
validate: input => {
|
|
223
|
+
try {
|
|
224
|
+
validateDueDate(input);
|
|
225
|
+
return true;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
return error.message;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
// for description
|
|
233
|
+
{
|
|
234
|
+
type: 'confirm',
|
|
235
|
+
name: 'changeDescription',
|
|
236
|
+
message: 'Do you want to change the description?',
|
|
237
|
+
default: false
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
type: "rawlist",
|
|
241
|
+
name: 'description',
|
|
242
|
+
message: 'Enter the new description:',
|
|
243
|
+
when: answers => answers.changeDescription
|
|
244
|
+
}
|
|
245
|
+
]);
|
|
246
|
+
|
|
247
|
+
// apply updates only if user chose to change them
|
|
248
|
+
if (answers.changeTitle) task.title = answers.title;
|
|
249
|
+
if (answers.changeStatus) task.status = answers.status;
|
|
250
|
+
if (answers.changePriority) task.priority = answers.priority;
|
|
251
|
+
if (answers.changeDueDate) task.dueDate = answers.dueDate;
|
|
252
|
+
if (answers.changeDescription)
|
|
253
|
+
task.description = answers.description || '';
|
|
254
|
+
|
|
255
|
+
// is there any change?
|
|
256
|
+
const hasChanges = [
|
|
257
|
+
answers.changeTitle,
|
|
258
|
+
answers.changeStatus,
|
|
259
|
+
answers.changePriority,
|
|
260
|
+
answers.changeDueDate,
|
|
261
|
+
answers.changeDescription
|
|
262
|
+
].some(Boolean);
|
|
216
263
|
|
|
217
|
-
|
|
264
|
+
if (!hasChanges) {
|
|
265
|
+
console.log('No changes were made.');
|
|
266
|
+
}else {
|
|
267
|
+
// save updated tasks to file
|
|
268
|
+
await saveTasks(tasks);
|
|
269
|
+
console.log('Task updated successfully!');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const tableData = tasks.map((task, index) => ({
|
|
273
|
+
'#': index + 1,
|
|
218
274
|
ID: task.id,
|
|
219
275
|
Title: task.title,
|
|
220
276
|
Status: task.status,
|
|
221
277
|
Priority: task.priority,
|
|
222
278
|
DueDate: task.dueDate,
|
|
223
279
|
Description: task.description || ''
|
|
224
|
-
}
|
|
280
|
+
}));
|
|
281
|
+
|
|
282
|
+
// display all tasks in table format
|
|
283
|
+
console.table(tableData);
|
|
225
284
|
});
|
|
226
285
|
|
|
286
|
+
|
|
227
287
|
// use command 'delete' with task ID and action
|
|
228
288
|
program
|
|
229
289
|
.command('delete')
|
|
230
290
|
.alias('del')
|
|
231
|
-
.description('delete a task by ID')
|
|
291
|
+
.description('delete a task by ==> ID <==')
|
|
232
292
|
.action(async () => {
|
|
233
293
|
const tasks = await loadTasks();
|
|
234
294
|
if (tasks.length === 0) {
|
|
@@ -238,7 +298,7 @@ program
|
|
|
238
298
|
|
|
239
299
|
const { id } = await inquirer.prompt([
|
|
240
300
|
{
|
|
241
|
-
type: '
|
|
301
|
+
type: 'rawlist',
|
|
242
302
|
name: 'id',
|
|
243
303
|
message: 'Select the task to delete:',
|
|
244
304
|
choices: tasks.map(task => ({ name: `${task.id}: ${task.title}`, value: task.id }))
|
|
@@ -258,18 +318,22 @@ program
|
|
|
258
318
|
return;
|
|
259
319
|
}
|
|
260
320
|
|
|
261
|
-
const newTasks = tasks.filter(t => t.id !== id);
|
|
321
|
+
const newTasks = tasks.filter(t => t.id !== Number(id));
|
|
262
322
|
await saveTasks(newTasks);
|
|
263
323
|
console.log('Task deleted successfully!');
|
|
264
324
|
|
|
265
|
-
|
|
325
|
+
const tableData = newTasks.map((task, index) => ({
|
|
326
|
+
'#': index + 1,
|
|
266
327
|
ID: task.id,
|
|
267
328
|
Title: task.title,
|
|
268
329
|
Status: task.status,
|
|
269
330
|
Priority: task.priority,
|
|
270
331
|
DueDate: task.dueDate,
|
|
271
332
|
Description: task.description || ''
|
|
272
|
-
}
|
|
333
|
+
}));
|
|
334
|
+
|
|
335
|
+
// display all tasks in table format
|
|
336
|
+
console.table(tableData);
|
|
273
337
|
});
|
|
274
338
|
|
|
275
339
|
// parse command line arguments
|
package/package.json
CHANGED
package/taskService.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// for file system operations
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// file to store tasks
|
|
6
|
+
const TASKS_FILE = "./todos.json";
|
|
7
|
+
|
|
8
|
+
// function to load tasks from file
|
|
9
|
+
export const loadTasks = async () => {
|
|
10
|
+
try{
|
|
11
|
+
const data = await fs.readFile(TASKS_FILE, 'utf8');
|
|
12
|
+
return JSON.parse(data);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
if (error.code === 'ENOENT') return [];
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
// function to save tasks to file
|
|
19
|
+
export const saveTasks = async (tasks) => {
|
|
20
|
+
await fs.writeFile(TASKS_FILE, JSON.stringify(tasks, null, 2));
|
|
21
|
+
};
|
|
22
|
+
// get next task ID
|
|
23
|
+
export const getNextId = (tasks) => {
|
|
24
|
+
return tasks.length > 0 ? Math.max(...tasks.map(task => task.id)) + 1 : 1;
|
|
25
|
+
};
|
package/todos.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
package/validators.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
// allowed task statuses
|
|
3
|
+
const ALLOWED_STATUSES = ["todo", "in-progress", "done"];
|
|
4
|
+
// allowed task priorities
|
|
5
|
+
const ALLOWED_PRIORITIES = ["low", "medium", "high"];
|
|
6
|
+
|
|
7
|
+
// validate task status
|
|
8
|
+
export const validateStatus = (status) => {
|
|
9
|
+
if (!ALLOWED_STATUSES.includes(status)) {
|
|
10
|
+
throw new Error(`Invalid status. Allowed statuses are: ${ALLOWED_STATUSES.join(", ")}`);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
// validate task priority
|
|
14
|
+
export const validatePriority = (priority) => {
|
|
15
|
+
if (!ALLOWED_PRIORITIES.includes(priority)) {
|
|
16
|
+
throw new Error(`Invalid priority. Allowed priorities are: ${ALLOWED_PRIORITIES.join(", ")}`);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
// verify due date format
|
|
20
|
+
export const validateDueDate = (dueDate) => {
|
|
21
|
+
if (isNaN(Date.parse(dueDate))) {
|
|
22
|
+
throw new Error("Invalid due date. Please use a valid date format (YYYY-MM-DD).");
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { ALLOWED_PRIORITIES, ALLOWED_STATUSES };
|