@adithya-naik/cmd-tracker 1.0.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.
@@ -0,0 +1,286 @@
1
+ /*
2
+ * storage.js
3
+ *
4
+ * This file handles everything related to FILE OPERATIONS
5
+ * It saves commands, reads commands, and manages the .tracker folder
6
+ *
7
+ * Two files in Node.js help us work with files and folders:
8
+ * fs → File System → read, write, check if file exists
9
+ * path → helps build file paths that work on ALL operating systems
10
+ * Windows: C:\Users\project\.tracker\commands.json
11
+ * Mac/Linux: /home/user/project/.tracker/commands.json
12
+ * path module handles this difference automatically
13
+ */
14
+ const fs = require("fs");
15
+ const path = require("path");
16
+
17
+ /*
18
+ * Import our categorize function from categorizer.js
19
+ * We need it here to know WHICH category to save the command in
20
+ */
21
+ const { categorize } = require("./categorizer");
22
+
23
+ /*
24
+ * process.cwd() → Current Working Directory
25
+ * This gives us the path of the folder where user ran the command
26
+ *
27
+ * Example:
28
+ * User is in → C:\Adithya Naik\GitHub\my-linux-repo
29
+ * process.cwd() → "C:\Adithya Naik\GitHub\my-linux-repo"
30
+ *
31
+ * This is VERY important — we want to save commands in THEIR repo
32
+ * not in our package folder
33
+ */
34
+ const TRACKER_DIR = path.join(process.cwd(), ".tracker");
35
+
36
+ /*
37
+ * This is where all commands will be saved
38
+ * .tracker/commands.json inside the user's repo
39
+ */
40
+ const COMMANDS_FILE = path.join(TRACKER_DIR, "commands.json");
41
+
42
+ /*
43
+ * This is our default empty structure
44
+ * When we create commands.json for the first time
45
+ * it starts with all empty arrays
46
+ *
47
+ * We use a function so we always get a FRESH object
48
+ * not the same object reused (important in JavaScript)
49
+ */
50
+ function getDefaultStructure() {
51
+ return {
52
+ git: [],
53
+ npm: [],
54
+ docker: [],
55
+ linux: [],
56
+ node: [],
57
+ angular: [],
58
+ python: [],
59
+ others: [],
60
+ };
61
+ }
62
+
63
+ /*
64
+ * initStorage() — sets up the .tracker folder and commands.json
65
+ *
66
+ * This runs when user types: tracker init
67
+ * It creates the .tracker folder and commands.json if they don't exist
68
+ */
69
+ function initStorage() {
70
+
71
+ /*
72
+ * fs.existsSync() → checks if a folder/file EXISTS
73
+ * Returns true if exists, false if not
74
+ *
75
+ * If .tracker folder doesn't exist → create it
76
+ */
77
+ if (!fs.existsSync(TRACKER_DIR)) {
78
+
79
+ /*
80
+ * fs.mkdirSync() → creates a folder
81
+ * { recursive: true } → creates parent folders too if needed
82
+ * Like "mkdir -p" in linux
83
+ */
84
+ fs.mkdirSync(TRACKER_DIR, { recursive: true });
85
+ console.log("✅ Created .tracker folder");
86
+ }
87
+
88
+ /*
89
+ * If commands.json doesn't exist → create it with default structure
90
+ */
91
+ if (!fs.existsSync(COMMANDS_FILE)) {
92
+
93
+ /*
94
+ * fs.writeFileSync() → creates and writes to a file
95
+ * JSON.stringify() → converts JavaScript object to JSON string
96
+ * null, 2 → makes the JSON nicely formatted with 2 spaces indent
97
+ */
98
+ fs.writeFileSync(
99
+ COMMANDS_FILE,
100
+ JSON.stringify(getDefaultStructure(), null, 2)
101
+ );
102
+ console.log("✅ Created .tracker/commands.json");
103
+ }
104
+ }
105
+
106
+ /*
107
+ * readCommands() — reads all saved commands from commands.json
108
+ *
109
+ * @returns {object} — the entire commands object with all categories
110
+ */
111
+ function readCommands() {
112
+
113
+ /*
114
+ * If file doesn't exist yet — return empty structure
115
+ * This prevents crashes if someone runs tracker list before tracker init
116
+ */
117
+ if (!fs.existsSync(COMMANDS_FILE)) {
118
+ return getDefaultStructure();
119
+ }
120
+
121
+ /*
122
+ * fs.readFileSync() → reads the file content as text
123
+ * JSON.parse() → converts JSON text back to JavaScript object
124
+ */
125
+ const fileContent = fs.readFileSync(COMMANDS_FILE, "utf-8");
126
+ return JSON.parse(fileContent);
127
+ }
128
+
129
+ /*
130
+ * saveCommand() — saves a single command to commands.json
131
+ *
132
+ * @param {string} command — the terminal command to save
133
+ * @returns {object} — { saved: true/false, category: "git" etc, reason: "why not saved" }
134
+ */
135
+ function saveCommand(command) {
136
+
137
+ /*
138
+ * Safety check — don't save empty commands
139
+ */
140
+ if (!command || !command.trim()) {
141
+ return { saved: false, reason: "empty command" };
142
+ }
143
+
144
+ /*
145
+ * Clean the command — remove extra spaces
146
+ */
147
+ const cleanCommand = command.trim();
148
+
149
+ /*
150
+ * Use our categorizer to find which category this command belongs to
151
+ */
152
+ const category = categorize(cleanCommand);
153
+
154
+ /*
155
+ * Read existing commands from file
156
+ */
157
+ const data = readCommands();
158
+
159
+ /*
160
+ * DUPLICATE CHECK
161
+ *
162
+ * .some() → loops through array and returns true if ANY item matches
163
+ *
164
+ * We check if this exact command already exists in this category
165
+ * If it does — don't save it again
166
+ *
167
+ * item.command → because each saved command is an object like:
168
+ * { command: "git status", time: "2026-05-06T..." }
169
+ */
170
+ const isDuplicate = data[category].some(
171
+ (item) => item.command === cleanCommand
172
+ );
173
+
174
+ if (isDuplicate) {
175
+ return { saved: false, reason: "duplicate", category };
176
+ }
177
+
178
+ /*
179
+ * Create the command object with timestamp
180
+ * new Date().toISOString() → "2026-05-06T10:30:00.000Z"
181
+ * This gives us a full date+time in standard format
182
+ */
183
+ const commandObject = {
184
+ command: cleanCommand,
185
+ time: new Date().toISOString(),
186
+ };
187
+
188
+ /*
189
+ * Push the new command into the correct category array
190
+ */
191
+ data[category].push(commandObject);
192
+
193
+ /*
194
+ * Write the updated data back to commands.json
195
+ */
196
+ fs.writeFileSync(COMMANDS_FILE, JSON.stringify(data, null, 2));
197
+
198
+ return { saved: true, category };
199
+ }
200
+
201
+ /*
202
+ * toggleFavorite() — marks/unmarks a command as favorite
203
+ *
204
+ * @param {string} command — the command to favorite
205
+ * @returns {object} — { success, action: "added"/"removed", command }
206
+ */
207
+ function toggleFavorite(command) {
208
+
209
+ if (!command || !command.trim()) {
210
+ return { success: false, reason: "empty command" };
211
+ }
212
+
213
+ const cleanCommand = command.trim();
214
+ const data = readCommands();
215
+
216
+ /*
217
+ * Search for command across all categories
218
+ */
219
+ for (const [category, commands] of Object.entries(data)) {
220
+ const index = commands.findIndex(
221
+ (item) => item.command === cleanCommand
222
+ );
223
+
224
+ if (index !== -1) {
225
+ /*
226
+ * Toggle favorite flag
227
+ * If favorite exists and is true → set false
228
+ * If favorite doesn't exist or false → set true
229
+ */
230
+ const currentFav = data[category][index].favorite || false;
231
+ data[category][index].favorite = !currentFav;
232
+
233
+ fs.writeFileSync(COMMANDS_FILE, JSON.stringify(data, null, 2));
234
+
235
+ return {
236
+ success: true,
237
+ action: currentFav ? "removed" : "added",
238
+ command: cleanCommand,
239
+ category
240
+ };
241
+ }
242
+ }
243
+
244
+ return { success: false, reason: "command not found" };
245
+ }
246
+
247
+ /*
248
+ * getFavorites() — returns all favorited commands
249
+ *
250
+ * @returns {array} — array of { command, category, time }
251
+ */
252
+ function getFavorites() {
253
+
254
+ const data = readCommands();
255
+ const favorites = [];
256
+
257
+ for (const [category, commands] of Object.entries(data)) {
258
+ for (const item of commands) {
259
+ if (item.favorite === true) {
260
+ favorites.push({
261
+ command: item.command,
262
+ category,
263
+ time: item.time
264
+ });
265
+ }
266
+ }
267
+ }
268
+
269
+ return favorites;
270
+ }
271
+
272
+
273
+
274
+ /*
275
+ * module.exports → expose these functions to other files
276
+ * Only export what other files need to use
277
+ */
278
+ module.exports = {
279
+ initStorage,
280
+ readCommands,
281
+ saveCommand,
282
+ toggleFavorite,
283
+ getFavorites,
284
+ TRACKER_DIR,
285
+ COMMANDS_FILE,
286
+ };
@@ -0,0 +1,94 @@
1
+ /*
2
+ * validator.js
3
+ *
4
+ * Central validation utility for all commands
5
+ * Instead of repeating checks in every command file
6
+ * we put all validation logic here in one place
7
+ *
8
+ * This is called the DRY principle:
9
+ * DRY = Don't Repeat Yourself
10
+ * Write once, use everywhere ✅
11
+ */
12
+
13
+ const fs = require("fs");
14
+ const path = require("path");
15
+
16
+ /*
17
+ * Path to .tracker folder
18
+ * process.cwd() → user's current project folder
19
+ */
20
+ const TRACKER_DIR = path.join(process.cwd(), ".tracker");
21
+ const COMMANDS_FILE = path.join(TRACKER_DIR, "commands.json");
22
+
23
+ /*
24
+ * isInitialized() — checks if tracker init was run
25
+ *
26
+ * Before any command works — .tracker/commands.json must exist
27
+ * If user skips tracker init and runs tracker list
28
+ * we catch it here and guide them
29
+ *
30
+ * @returns {boolean} — true if initialized, false if not
31
+ */
32
+ function isInitialized() {
33
+ return fs.existsSync(TRACKER_DIR) && fs.existsSync(COMMANDS_FILE);
34
+ }
35
+
36
+ /*
37
+ * showInitError() — shows helpful error when not initialized
38
+ *
39
+ * Instead of a confusing crash — user sees a clear message
40
+ * telling them exactly what to do next
41
+ */
42
+ function showInitError() {
43
+ console.log("\n❌ cmd-tracker is not initialized in this project!");
44
+ console.log("💡 Run this first: tracker init\n");
45
+ }
46
+
47
+ /*
48
+ * isValidCategory() — checks if category name is valid
49
+ *
50
+ * @param {string} category — category name to validate
51
+ * @returns {boolean} — true if valid, false if not
52
+ */
53
+ function isValidCategory(category) {
54
+ const validCategories = [
55
+ "git", "npm", "docker", "linux",
56
+ "node", "angular", "python", "others"
57
+ ];
58
+ return validCategories.includes(category.toLowerCase());
59
+ }
60
+
61
+ /*
62
+ * showCategoryError() — shows helpful error for invalid category
63
+ *
64
+ * @param {string} category — the invalid category user typed
65
+ */
66
+ function showCategoryError(category) {
67
+ console.log(`\n❌ Unknown category: "${category}"`);
68
+ console.log("📋 Valid categories: git, npm, docker, linux, node, angular, python, others\n");
69
+ }
70
+
71
+ /*
72
+ * isValidQuery() — checks if search query is valid
73
+ *
74
+ * @param {string} query — search term to validate
75
+ * @returns {boolean} — true if valid, false if not
76
+ */
77
+ function isValidQuery(query) {
78
+ /*
79
+ * Query must exist and have at least 1 character after trimming
80
+ */
81
+ return query && query.trim().length > 0;
82
+ }
83
+
84
+ /*
85
+ * Export all validators
86
+ * Every command file will import what it needs from here
87
+ */
88
+ module.exports = {
89
+ isInitialized,
90
+ showInitError,
91
+ isValidCategory,
92
+ showCategoryError,
93
+ isValidQuery
94
+ };