errlens 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 CHANGED
@@ -1,15 +1,38 @@
1
1
  # ErrLens 🔍
2
2
  > **Translate cryptic JavaScript errors into human-readable solutions instantly.**
3
3
 
4
- <p align="left">
5
- <img src="https://img.shields.io/npm/v/errlens?style=flat-square&color=007acc" alt="npm version">
6
- <img src="https://img.shields.io/github/license/BeyteFlow/errlens?style=flat-square&color=42b883" alt="license">
7
- <img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square" alt="PRs Welcome">
4
+ <p align="center">
5
+ <img src="assets/errlens.png" width="500">
8
6
  </p>
7
+ </br>
8
+ </br>
9
9
 
10
+ <p align="center">
11
+ <!-- GitHub Badges -->
12
+ <img src="https://img.shields.io/github/stars/BeyteFlow/errlens?style=for-the-badge" alt="GitHub stars">
13
+ <img src="https://img.shields.io/github/forks/BeyteFlow/errlens?style=for-the-badge" alt="GitHub forks">
14
+ <img src="https://img.shields.io/github/issues/BeyteFlow/errlens?style=for-the-badge" alt="GitHub issues">
15
+ <img src="https://img.shields.io/github/license/BeyteFlow/errlens?style=for-the-badge" alt="GitHub license">
16
+ <img src="https://img.shields.io/github/issues-pr/BeyteFlow/errlens?style=for-the-badge" alt="Open PRs">
17
+
18
+ <!-- npm Badges -->
19
+ <br>
20
+ <img src="https://img.shields.io/npm/v/errlens?style=for-the-badge" alt="npm version">
21
+ <img src="https://img.shields.io/npm/dm/errlens?style=for-the-badge" alt="npm downloads">
22
+ <img src="https://img.shields.io/github/v/tag/BeyteFlow/errlens?style=for-the-badge" alt="Git tag version">
23
+ <img src="https://img.shields.io/node/v/errlens?style=for-the-badge" alt="Node.js version">
24
+ </p>
10
25
  **ErrLens** is a professional-grade CLI utility designed to eliminate developer frustration. It intercepts Node.js crashes, analyzes stack traces, and delivers plain-English explanations with actionable fixes—directly in your terminal.
11
26
 
12
27
  ---
28
+ </br>
29
+ </br>
30
+ </br>
31
+ <p align="center">
32
+ <img src="assets/terminal.png" width="800">
33
+ </p>
34
+ </br>
35
+ </br>
13
36
 
14
37
  ## 🌟 Key Features
15
38
 
package/bin/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const { Command } = require("commander");
4
+ const { spawn } = require("child_process");
4
5
  const ora = require("ora");
5
6
  const chalk = require("chalk");
7
+ const path = require("path");
6
8
  const { findError } = require("../lib/matcher");
7
9
  const { formatError } = require("../lib/formatter");
8
10
 
@@ -10,39 +12,64 @@ const program = new Command();
10
12
 
11
13
  program
12
14
  .name("errlens")
13
- .description("Professional JS error analysis for the terminal")
14
- .version("1.1.0")
15
- .argument("[error]", "The error message to analyze")
16
- .option("-j, --json", "Output results in raw JSON format") // For automation
17
- .action((errorInput, options) => {
18
- // If no argument is provided, show help and exit
19
- if (!errorInput) {
20
- program.help();
21
- return;
22
- }
23
-
24
- // JSON Mode (No spinner, no colors, just data)
25
- if (options.json) {
26
- const result = findError(errorInput);
27
- console.log(JSON.stringify(result || { error: "No match found" }, null, 2));
28
- return;
29
- }
30
-
31
- // Standard Mode
32
- const spinner = ora({ text: "Analyzing error trace...", color: "cyan" }).start();
33
-
34
- setTimeout(() => {
35
- const result = findError(errorInput);
36
-
37
- if (!result) {
38
- spinner.fail(chalk.red(" Analysis Failed: Error not found in database."));
39
- console.log(chalk.dim("\nTry searching a shorter snippet of the error message."));
15
+ .description("Professional JS Error Analytics")
16
+ .version("1.3.1");
17
+
18
+ program
19
+ .command("run <file>")
20
+ .description("Run a Javascript file and analyze crashes")
21
+ .action((file) => {
22
+ const filePath = path.resolve(process.cwd(), file);
23
+ const spinner = ora(`Running ${chalk.yellow(file)}...`).start();
24
+
25
+ // stdio: ['inherit', 'pipe', 'pipe']
26
+ // This allows us to pipe stdout and stderr while keeping the process interactive
27
+ const child = spawn(process.execPath, [filePath], { stdio: ["inherit", "pipe", "pipe"] });
28
+
29
+ let errorOutput = "";
30
+
31
+ // Stream logs to terminal in REAL-TIME
32
+ child.stdout.on("data", (data) => {
33
+ // Clear spinner temporarily to print log, then restart (better UX)
34
+ spinner.stop();
35
+ process.stdout.write(data);
36
+ spinner.start();
37
+ });
38
+
39
+ // Capture stderr for analysis
40
+ child.stderr.on("data", (data) => {
41
+ errorOutput += data.toString();
42
+ });
43
+
44
+ child.on("close", (code, signal) => {
45
+ spinner.stop();
46
+
47
+ if (code === null) {
48
+ console.log(chalk.red.bold(`\n⚠️ Process killed by signal: ${signal}`));
49
+ process.exit(1);
40
50
  return;
41
51
  }
42
52
 
43
- spinner.succeed(chalk.bold("Analysis complete!"));
44
- console.log(formatError(result));
45
- }, 500);
53
+ if (code === 0) {
54
+ console.log(chalk.green.bold("\n✨ Process finished successfully."));
55
+ } else {
56
+ const { count, matches } = findError(errorOutput);
57
+
58
+ if (count > 0) {
59
+ console.log(chalk.bold.cyan(`\n🚀 ErrLens Analysis (${count} Issue(s)):`));
60
+ matches.forEach(m => console.log(formatError(m)));
61
+ } else {
62
+ console.log(chalk.red.bold("\n❌ Crash detected (No known fix in database):"));
63
+ console.log(chalk.gray(errorOutput));
64
+ }
65
+ }
66
+ process.exit(code ?? 1);
67
+ });
68
+
69
+ child.on("error", (err) => {
70
+ spinner.fail(chalk.red(`System Error: ${err.message}`));
71
+ process.exit(1);
72
+ });
46
73
  });
47
74
 
48
75
  program.parse(process.argv);
package/lib/auto.js CHANGED
@@ -1,15 +1,18 @@
1
- // lib/auto.js
2
1
  const { findError } = require("./matcher");
3
2
  const { formatError } = require("./formatter");
3
+ const chalk = require("chalk");
4
4
 
5
- // This listens for any crash that wasn't caught by a try/catch
6
5
  process.on("uncaughtException", (err) => {
7
- const result = findError(err.message);
6
+ // Use the message for the search, guard against non-Error throws
7
+ const msg = (err && err.message) ? err.message : String(err);
8
+ const { count, matches } = findError(msg);
8
9
 
9
- if (result) {
10
- console.log("\n--- ErrLens Auto-Detection ---");
11
- console.log(formatError(result));
10
+ if (count > 0) {
11
+ console.log(chalk.cyan(`\n💡 ErrLens detected ${count} issue(s):`));
12
+ matches.forEach(match => console.log(formatError(match)));
12
13
  } else {
14
+ // If no match, show the raw error so you know what happened
15
+ console.error(chalk.red.bold("\n--- Raw System Error ---"));
13
16
  console.error(err);
14
17
  }
15
18
  process.exit(1);
package/lib/database.json CHANGED
@@ -2,37 +2,430 @@
2
2
  {
3
3
  "name": "TypeError: Cannot read properties of undefined",
4
4
  "match": "Cannot read properties of undefined",
5
- "explanation": "You are trying to access a property (like .map, .length, or .id) on a variable that currently holds no value.",
6
- "why": "The variable wasn't initialized, an API call hasn't finished yet, or a function returned nothing.",
5
+ "explanation": "You are trying to access a property on a variable that is currently empty.",
6
+ "why": "The variable wasn't initialized, or an API call hasn't finished yet.",
7
7
  "fixes": [
8
- "Use optional chaining: user?.profile",
9
- "Set a default value: const list = data || []",
10
- "Check if the variable exists: if (data) { ... }"
8
+ "Use optional chaining: user?.name",
9
+ "Set a default value: data || []"
11
10
  ],
12
- "example": "const name = user?.info?.name || 'Guest';"
11
+ "example": "const name = user?.name || 'Guest';"
13
12
  },
14
13
  {
15
14
  "name": "ReferenceError: x is not defined",
16
15
  "match": "is not defined",
17
- "explanation": "You're trying to use a variable that hasn't been declared in the current scope.",
18
- "why": "Possible typo in the variable name, or the variable is scoped inside another function/block.",
19
- "fixes": [
20
- "Check for typos in the variable name",
21
- "Ensure the variable is declared with const, let, or var",
22
- "Check if the variable is defined outside the current function"
23
- ],
24
- "example": "const myVar = 10;\nconsole.log(myVar); // Ensure name matches exactly"
16
+ "explanation": "You're using a variable that hasn't been declared.",
17
+ "why": "Typo in the name or the variable is scoped inside another function.",
18
+ "fixes": ["Check for typos", "Declare with const/let"],
19
+ "example": "const myVar = 10;\nconsole.log(myVar);"
25
20
  },
26
21
  {
27
22
  "name": "TypeError: x is not a function",
28
23
  "match": "is not a function",
29
- "explanation": "You tried to call a variable as if it were a function, but it's a different data type (like a String or Object).",
30
- "why": "Often happens when an import fails or when a callback isn't passed correctly.",
24
+ "explanation": "You called a variable as a function, but it's another type (String/Object).",
25
+ "why": "Import failed or a callback wasn't passed correctly.",
26
+ "fixes": ["Check imports", "Verify type with typeof"],
27
+ "example": "if (typeof fn === 'function') fn();"
28
+ },
29
+ {
30
+ "name": "Error: Cannot find module",
31
+ "match": "Cannot find module",
32
+ "explanation": "Node.js cannot find the file or package you are importing.",
33
+ "why": "The package isn't installed or the relative path is wrong.",
34
+ "fixes": ["Run 'npm install'", "Check file path spelling"],
35
+ "example": "require('./src/utils.js');"
36
+ },
37
+ {
38
+ "name": "Error: EADDRINUSE (Port Taken)",
39
+ "match": "EADDRINUSE",
40
+ "explanation": "The port your server wants is already being used.",
41
+ "why": "Another instance of your app is already running.",
42
+ "fixes": ["Kill the other process", "Change the port number"],
43
+ "example": "const PORT = process.env.PORT || 4000;"
44
+ },
45
+ {
46
+ "name": "RangeError: Maximum call stack size exceeded",
47
+ "match": "Maximum call stack size exceeded",
48
+ "explanation": "The call stack limit was reached due to excessive recursion or a very deep call chain.",
49
+ "why": "A recursive function is calling itself too many times without hitting a base case, or a chain of function calls builds up too deeply.",
50
+ "fixes": ["Check recursion exit conditions", "Verify recursive base cases are reachable", "Check call-chain depth and stack-building loops"],
51
+ "example": "function run() { if (done) return; run(); }"
52
+ },
53
+ {
54
+ "name": "Error: ECONNREFUSED",
55
+ "match": "ECONNREFUSED",
56
+ "explanation": "Your app tried to connect to a database/API but was rejected.",
57
+ "why": "The target server is down or the URL is wrong.",
58
+ "fixes": ["Ensure DB is running", "Check API URL"],
59
+ "example": "mongoose.connect('mongodb://localhost:27017/db');"
60
+ },
61
+ {
62
+ "name": "TypeError: Assignment to constant variable",
63
+ "match": "Assignment to constant variable",
64
+ "explanation": "You tried to change a 'const' variable.",
65
+ "why": "Const variables cannot be reassigned.",
66
+ "fixes": ["Use 'let' instead of 'const'"],
67
+ "example": "let count = 0; count = 1;"
68
+ },
69
+ {
70
+ "name": "Error: ENOENT (No such file)",
71
+ "match": "ENOENT",
72
+ "explanation": "You're trying to read a file that doesn't exist.",
73
+ "why": "File moved or path string is incorrect.",
74
+ "fixes": ["Verify file exists", "Use path.join()"],
75
+ "example": "fs.readFileSync(path.join(__dirname, 'log.txt'));"
76
+ },
77
+ {
78
+ "name": "SyntaxError: await is only valid in async functions",
79
+ "match": "await is only valid in async functions",
80
+ "explanation": "You used 'await' inside a normal function.",
81
+ "why": "Await requires the 'async' keyword on the parent function.",
82
+ "fixes": ["Add 'async' to function definition"],
83
+ "example": "async function start() { await call(); }"
84
+ },
85
+ {
86
+ "name": "Error [ERR_HTTP_HEADERS_SENT]",
87
+ "match": "ERR_HTTP_HEADERS_SENT",
88
+ "explanation": "You sent a response after already sending one.",
89
+ "why": "Multiple res.send() calls in one route.",
90
+ "fixes": ["Use 'return res.send()' to stop execution"],
91
+ "example": "if (err) return res.send(err); res.send(ok);"
92
+ },
93
+ {
94
+ "name": "MongooseError: Operation `x.findOne()` buffering timed out",
95
+ "match": "buffering timed out",
96
+ "explanation": "Mongoose couldn't connect to MongoDB in time.",
97
+ "why": "Database is offline or connection string is wrong.",
98
+ "fixes": ["Check MongoDB status", "Check connection string"],
99
+ "example": "mongoose.connect(process.env.MONGO_URI);"
100
+ },
101
+ {
102
+ "name": "JsonWebTokenError: jwt malformed",
103
+ "match": "jwt malformed",
104
+ "explanation": "The token provided is not a valid JWT structure.",
105
+ "why": "Token is corrupted or only part of it was sent.",
106
+ "fixes": ["Check 'Authorization' header", "Verify token generation"],
107
+ "example": "const token = req.headers.authorization.split(' ')[1];"
108
+ },
109
+ {
110
+ "name": "Error: secretOrPrivateKey must have a value",
111
+ "match": "secretOrPrivateKey must have a value",
112
+ "explanation": "JWT secret key is missing.",
113
+ "why": "Environment variable (JWT_SECRET) is not loaded.",
114
+ "fixes": ["Check .env file", "Run dotenv.config()"],
115
+ "example": "jwt.sign(p, process.env.JWT_SECRET);"
116
+ },
117
+ {
118
+ "name": "MongoServerError: E11000 duplicate key error",
119
+ "match": "E11000",
120
+ "explanation": "You're saving data with a 'unique' field that already exists.",
121
+ "why": "Usually a duplicate Email or Username in the DB.",
122
+ "fixes": [
123
+ "Check if data exists before saving",
124
+ "Use unique error handling"
125
+ ],
126
+ "example": "UserSchema.path('email').validate(...) "
127
+ },
128
+ {
129
+ "name": "SyntaxError: Unexpected token < in JSON",
130
+ "match": "Unexpected token < in JSON",
131
+ "explanation": "You tried to parse JSON, but got HTML instead.",
132
+ "why": "Your API returned an error page (HTML) instead of data (JSON).",
133
+ "fixes": ["Check the API URL", "Check server logs for crashes"],
134
+ "example": "fetch(url).then(res => res.json())"
135
+ },
136
+ {
137
+ "name": "Error: Cross-Origin Request Blocked (CORS)",
138
+ "match": "CORS",
139
+ "explanation": "Browser blocked your request for security reasons.",
140
+ "why": "The server doesn't allow requests from your frontend domain.",
141
+ "fixes": ["Install 'cors' package on server", "app.use(cors())"],
142
+ "example": "const cors = require('cors'); app.use(cors());"
143
+ },
144
+ {
145
+ "name": "TypeError: Cannot read property 'map' of undefined",
146
+ "match": "'map' of undefined",
147
+ "explanation": "You're trying to loop through an array that doesn't exist.",
148
+ "why": "The variable is null/undefined instead of an Array.",
149
+ "fixes": ["Initialize as empty array: data || []", "Check API response"],
150
+ "example": "items?.map(item => ...)"
151
+ },
152
+ {
153
+ "name": "Error: module not found 'dotenv'",
154
+ "match": "module not found 'dotenv'",
155
+ "explanation": "The 'dotenv' package is missing.",
156
+ "why": "You used it in code but didn't install it.",
157
+ "fixes": ["npm install dotenv"],
158
+ "example": "require('dotenv').config();"
159
+ },
160
+ {
161
+ "name": "ValidationError: User validation failed",
162
+ "match": "validation failed",
163
+ "explanation": "Data doesn't match your Mongoose schema.",
164
+ "why": "Missing required fields or wrong data types.",
165
+ "fixes": ["Check required fields", "Validate data on frontend"],
166
+ "example": "new User({ name: 'x' }).save();"
167
+ },
168
+ {
169
+ "name": "Error: Not authorized",
170
+ "match": "not authorized",
171
+ "explanation": "Access denied because of missing or invalid credentials.",
172
+ "why": "Token expired or wrong API key.",
173
+ "fixes": ["Login again", "Check headers"],
174
+ "example": "res.status(401).send('Unauthorized');"
175
+ },
176
+ {
177
+ "name": "Error: Request failed with status code 404",
178
+ "match": "status code 404",
179
+ "explanation": "The API endpoint you are calling doesn't exist.",
180
+ "why": "Wrong URL or the server route isn't defined.",
181
+ "fixes": ["Check API route spelling", "Check Base URL"],
182
+ "example": "axios.get('/api/users');"
183
+ },
184
+ {
185
+ "name": "Error: Request failed with status code 500",
186
+ "match": "status code 500",
187
+ "explanation": "The server crashed while processing your request.",
188
+ "why": "A bug on the backend server code.",
189
+ "fixes": ["Check server console logs", "Add try/catch on server"],
190
+ "example": "try { ... } catch (e) { res.status(500) }"
191
+ },
192
+ {
193
+ "name": "TypeError: object is not iterable",
194
+ "match": "is not iterable",
195
+ "explanation": "You used a for...of loop or spread (...) on a non-array.",
196
+ "why": "You tried to iterate over an Object or Null.",
197
+ "fixes": ["Ensure variable is an Array", "Use Object.keys()"],
198
+ "example": "[...myArray]"
199
+ },
200
+ {
201
+ "name": "ReferenceError: require is not defined",
202
+ "match": "require is not defined",
203
+ "explanation": "You're using 'require' in an ES Module.",
204
+ "why": "Node.js is set to 'type': 'module' in package.json.",
205
+ "fixes": ["Use 'import' instead", "Remove 'type: module'"],
206
+ "example": "import fs from 'fs';"
207
+ },
208
+ {
209
+ "name": "Error: Permission denied (EACCES)",
210
+ "match": "EACCES",
211
+ "explanation": "You don't have permission to access this file/port.",
212
+ "why": "You need admin rights or the file is locked.",
213
+ "fixes": ["Use 'sudo' (Linux/Mac)", "Check file permissions"],
214
+ "example": "chmod +x script.js"
215
+ },
216
+ {
217
+ "name": "SyntaxError: Identifier 'x' has already been declared",
218
+ "match": "has already been declared",
219
+ "explanation": "You declared the same variable name twice.",
220
+ "why": "Using 'let' or 'const' on a name that exists in the same scope.",
221
+ "fixes": ["Rename one of the variables", "Remove the second declaration"],
222
+ "example": "let x = 1; x = 2; // Don't use let again"
223
+ },
224
+ {
225
+ "name": "Error: PayloadTooLargeError",
226
+ "match": "PayloadTooLargeError",
227
+ "explanation": "The data you sent is too big for the server.",
228
+ "why": "Usually an image or file that exceeds the body-parser limit.",
229
+ "fixes": ["Increase limit: app.use(express.json({limit: '50mb'}))"],
230
+ "example": "app.use(express.json({ limit: '10mb' }));"
231
+ },
232
+ {
233
+ "name": "TypeError: Cannot set property of null",
234
+ "match": "set property of null",
235
+ "explanation": "You're trying to give a value to a property of an empty variable.",
236
+ "why": "The object you're modifying hasn't been created yet.",
237
+ "fixes": ["Initialize the object first", "Check for null"],
238
+ "example": "const obj = {}; obj.prop = 1;"
239
+ },
240
+ {
241
+ "name": "Error: socket hang up",
242
+ "match": "socket hang up",
243
+ "explanation": "The server closed the connection unexpectedly.",
244
+ "why": "Server crashed or timeout occurred.",
245
+ "fixes": ["Check server health", "Increase timeout limit"],
246
+ "example": "axios.get(url, { timeout: 5000 });"
247
+ },
248
+ {
249
+ "name": "SyntaxError: Unexpected end of JSON input",
250
+ "match": "Unexpected end of JSON input",
251
+ "explanation": "The JSON data is incomplete or empty.",
252
+ "why": "The server sent an empty string instead of JSON.",
253
+ "fixes": ["Check API response body", "Handle empty responses"],
254
+ "example": "if (text) JSON.parse(text);"
255
+ },
256
+ {
257
+ "name": "Error: Script execution timed out",
258
+ "match": "Script execution timed out",
259
+ "explanation": "Your code took too long to run.",
260
+ "why": "Heavy computation or infinite loop.",
261
+ "fixes": ["Optimize logic", "Use worker threads"],
262
+ "example": "setTimeout(() => { ... }, 1000);"
263
+ },
264
+ {
265
+ "name": "TypeError: Cannot read property 'then' of undefined",
266
+ "match": "'then' of undefined",
267
+ "explanation": "You called .then() on something that isn't a Promise.",
268
+ "why": "The function didn't return a promise.",
269
+ "fixes": ["Make function 'async'", "Return a Promise"],
270
+ "example": "return new Promise(...) "
271
+ },
272
+ {
273
+ "name": "Error: Invalid connection string",
274
+ "match": "Invalid connection string",
275
+ "explanation": "The database URL format is wrong.",
276
+ "why": "Missing symbols like '@' or '/' in the URI.",
277
+ "fixes": ["Check DB URI format"],
278
+ "example": "mongodb+srv://user:pass@cluster..."
279
+ },
280
+ {
281
+ "name": "ReferenceError: window is not defined",
282
+ "match": "window is not defined",
283
+ "explanation": "You're trying to use browser code (window) in Node.js.",
284
+ "why": "Node.js doesn't have a 'window' object (that's for browsers).",
285
+ "fixes": ["Check if running in browser", "Use 'global' instead"],
286
+ "example": "if (typeof window !== 'undefined') { ... }"
287
+ },
288
+ {
289
+ "name": "Error: Too many listeners",
290
+ "match": "Too many listeners",
291
+ "explanation": "Memory leak detected in EventEmitters.",
292
+ "why": "Adding event listeners inside a loop or repeated function.",
293
+ "fixes": ["Use .once() instead of .on()", "Remove old listeners"],
294
+ "example": "emitter.setMaxListeners(20);"
295
+ },
296
+ {
297
+ "name": "TypeError: Cannot spread non-iterable object",
298
+ "match": "spread non-iterable",
299
+ "explanation": "You tried to use `...obj` where it's not allowed.",
300
+ "why": "Trying to spread an object into an array.",
301
+ "fixes": ["Use brackets [] for arrays and {} for objects"],
302
+ "example": "const newObj = { ...oldObj };"
303
+ },
304
+ {
305
+ "name": "Error: write EPIPE",
306
+ "match": "EPIPE",
307
+ "explanation": "Broken pipe: sending data to a closed connection.",
308
+ "why": "The other side (client/process) stopped listening.",
309
+ "fixes": ["Handle stream errors", "Check if process is alive"],
310
+ "example": "stream.on('error', (err) => { ... })"
311
+ },
312
+ {
313
+ "name": "SyntaxError: Missing initializer in const declaration",
314
+ "match": "Missing initializer",
315
+ "explanation": "You declared a const without giving it a value.",
316
+ "why": "Const must be set immediately when created.",
317
+ "fixes": ["Assign a value: const x = 1;"],
318
+ "example": "const x = 5;"
319
+ },
320
+ {
321
+ "name": "Error: npm ERR! peerinvalid",
322
+ "match": "peerinvalid",
323
+ "explanation": "Installed packages have conflicting versions.",
324
+ "why": "One library needs version A, another needs version B.",
325
+ "fixes": ["npm install --legacy-peer-deps"],
326
+ "example": "npm install package --force"
327
+ },
328
+ {
329
+ "name": "TypeError: data.filter is not a function",
330
+ "match": "filter is not a function",
331
+ "explanation": "You're trying to filter something that isn't an array.",
332
+ "why": "The variable is an Object or Null.",
333
+ "fixes": ["Ensure data is an Array: Array.isArray(data)"],
334
+ "example": "(data || []).filter(...)"
335
+ },
336
+ {
337
+ "name": "Error: Hostname not found (ENOTFOUND)",
338
+ "match": "ENOTFOUND",
339
+ "explanation": "DNS lookup failed for the URL.",
340
+ "why": "Typo in the URL or no internet connection.",
341
+ "fixes": ["Check URL spelling", "Check DNS/Internet"],
342
+ "example": "axios.get('https://api.example.com')"
343
+ },
344
+ {
345
+ "name": "SyntaxError: Unexpected reserved word",
346
+ "match": "reserved word",
347
+ "explanation": "You used a word saved for JS (like 'class' or 'await') as a variable name.",
348
+ "why": "You can't name a variable 'class' or 'function'.",
349
+ "fixes": ["Rename the variable"],
350
+ "example": "const myClass = 'math';"
351
+ },
352
+ {
353
+ "name": "Error: Cannot find name 'x'",
354
+ "match": "Cannot find name",
355
+ "explanation": "TypeScript cannot find this variable or type.",
356
+ "why": "Variable not declared or missing @types package.",
357
+ "fixes": ["Check imports", "Install types: npm install @types/node"],
358
+ "example": "import { x } from './file';"
359
+ },
360
+ {
361
+ "name": "TypeError: Cannot convert undefined or null to object",
362
+ "match": "convert undefined or null to object",
363
+ "explanation": "Calling Object methods on an empty variable.",
364
+ "why": "Using Object.keys(data) when data is null.",
365
+ "fixes": ["Check if data exists: data && Object.keys(data)"],
366
+ "example": "Object.entries(data || {})"
367
+ },
368
+ {
369
+ "name": "Error: No 'Access-Control-Allow-Origin' header",
370
+ "match": "Access-Control-Allow-Origin",
371
+ "explanation": "CORS error: Server isn't sharing data with your domain.",
372
+ "why": "Security policy on the backend.",
373
+ "fixes": ["Enable CORS on the server side"],
374
+ "example": "res.header('Access-Control-Allow-Origin', '*');"
375
+ },
376
+ {
377
+ "name": "SyntaxError: Invalid or unexpected token",
378
+ "match": "Invalid or unexpected token",
379
+ "explanation": "There is a character in your code that shouldn't be there.",
380
+ "why": "Copy-pasting hidden symbols or using smart quotes (” instead of \").",
381
+ "fixes": ["Retype the line manually", "Check for invisible symbols"],
382
+ "example": "const str = 'hello';"
383
+ },
384
+ {
385
+ "name": "Error: Timeout of 2000ms exceeded (Mocha/Jest)",
386
+ "match": "timeout of 2000ms exceeded",
387
+ "explanation": "Your test took too long to finish.",
388
+ "why": "Asynchronous code didn't call 'done()' or finish in time.",
389
+ "fixes": ["Increase test timeout", "Check for slow API calls"],
390
+ "example": "it('test', () => { ... }).timeout(5000);"
391
+ },
392
+ {
393
+ "name": "TypeError: Property 'x' does not exist on type 'y'",
394
+ "match": "does not exist on type",
395
+ "explanation": "TypeScript Error: Accessing a property that isn't in the Interface.",
396
+ "why": "The object structure doesn't match the TS Type definition.",
397
+ "fixes": ["Update the Interface", "Use type casting: (obj as any).prop"],
398
+ "example": "interface User { name: string }"
399
+ },
400
+ {
401
+ "name": "Error: npm ERR! 404 Not Found",
402
+ "match": "npm ERR! 404",
403
+ "explanation": "NPM can't find the package you want to install.",
404
+ "why": "Typo in the package name or it's a private package.",
405
+ "fixes": ["Check package spelling", "npm login"],
406
+ "example": "npm install express"
407
+ },
408
+ {
409
+ "name": "SyntaxError: Unexpected end of input",
410
+ "match": "Unexpected end of input",
411
+ "explanation": "Your code stopped abruptly, likely due to a missing closing bracket.",
412
+ "why": "You opened a ( [ or { but forgot to close it with ) ] or }.",
413
+ "fixes": [
414
+ "Check for missing closing parentheses )",
415
+ "Check for missing curly braces }",
416
+ "Ensure all quotes ' or \" are closed"
417
+ ],
418
+ "example": "console.log('Hello'); // Make sure the ) is there!"
419
+ },
420
+ {
421
+ "name": "SyntaxError: Missing Parenthesis",
422
+ "match": "missing ) after argument list",
423
+ "explanation": "You forgot to close a function call with a ')'.",
424
+ "why": "A parenthesis was opened but the line ended or another symbol appeared before it was closed.",
31
425
  "fixes": [
32
- "Verify the variable is actually a function with typeof",
33
- "Check your imports/requires",
34
- "Ensure you aren't overwriting the function name with a value"
426
+ "Add a ')' at the end of your function call",
427
+ "Check if you accidentally deleted a ')' while typing"
35
428
  ],
36
- "example": "if (typeof callback === 'function') {\n callback();\n}"
429
+ "example": "console.log('Hello');"
37
430
  }
38
- ]
431
+ ]
package/lib/formatter.js CHANGED
@@ -2,20 +2,15 @@ const chalk = require("chalk");
2
2
  const boxen = require("boxen");
3
3
 
4
4
  function formatError(error) {
5
- const fixList = error.fixes
6
- .map(fix => chalk.green(` ✔ ${fix}`))
7
- .join("\n");
5
+ const fixList = error.fixes.map(f => chalk.green(` ✔ ${f}`)).join("\n");
8
6
 
9
7
  const content = `
10
8
  ${chalk.cyan.bold("ID:")} ${error.name}
11
9
 
12
- ${chalk.yellow.bold("🔍 WHAT HAPPENED:")}
13
- ${error.explanation}
10
+ ${chalk.yellow.bold("🔍 WHAT:")} ${error.explanation}
11
+ ${chalk.magenta.bold("💥 WHY:")} ${error.why}
14
12
 
15
- ${chalk.magenta.bold("💥 WHY:")}
16
- ${error.why}
17
-
18
- ${chalk.green.bold("✅ COMMON FIXES:")}
13
+ ${chalk.green.bold(" FIXES:")}
19
14
  ${fixList}
20
15
 
21
16
  ${chalk.blue.bold("📘 EXAMPLE:")}
@@ -24,11 +19,12 @@ ${chalk.gray(error.example)}
24
19
 
25
20
  return boxen(content, {
26
21
  padding: 1,
27
- margin: 1,
22
+ margin: { top: 1, bottom: 1 },
28
23
  borderStyle: "round",
29
24
  borderColor: "cyan",
30
- title: "ErrLens Analysis",
31
- titleAlignment: "center"
25
+ title: chalk.bold("🔍 ErrLens Analysis"),
26
+ titleAlignment: "left",
27
+ width: 80 // Fixed width for consistent professional look
32
28
  });
33
29
  }
34
30
 
@@ -0,0 +1,43 @@
1
+ const { findError } = require("./matcher");
2
+ const { formatError } = require("./formatter");
3
+ const chalk = require("chalk");
4
+
5
+ /**
6
+ * ERR_LENS INJECTOR
7
+ * Attach this to any project to get automatic analysis on crash.
8
+ */
9
+ process.on("uncaughtException", (err) => {
10
+ // We search the stack trace because sometimes the match string
11
+ // is hidden deep in the error history.
12
+ const errStr = err !== null && typeof err === "object" ? (err.stack || err.message) : String(err ?? "unknown error");
13
+ const { count, matches } = findError(errStr);
14
+
15
+ if (count > 0) {
16
+ console.log(chalk.cyan.bold(`\n💡 ErrLens Runtime Analysis (${count} Issues Found):`));
17
+ matches.forEach(match => console.log(formatError(match)));
18
+ } else {
19
+ // If we don't know the error, show the original one so the dev isn't lost
20
+ console.error(chalk.red.bold("\n--- Raw System Error ---"));
21
+ console.error(err);
22
+ }
23
+
24
+ // Always exit with failure code 1 after a crash
25
+ process.exit(1);
26
+ });
27
+
28
+ // Also handle Unhandled Promise Rejections (Async/Await errors)
29
+ process.on("unhandledRejection", (reason) => {
30
+ const reasonStr = reason !== null && typeof reason === "object" ? (reason.stack || reason.message) : String(reason ?? "unknown error");
31
+ const { count, matches } = findError(reasonStr);
32
+
33
+ if (count > 0) {
34
+ console.log(chalk.magenta.bold(`\n📡 ErrLens Async Error Detection:`));
35
+ matches.forEach(match => console.log(formatError(match)));
36
+ } else {
37
+ console.error(chalk.red.bold("\n--- Unhandled Async Rejection ---"));
38
+ console.error(reason);
39
+ }
40
+ process.exit(1);
41
+ });
42
+
43
+ console.log(chalk.dim("🔧 ErrLens Protection Enabled."));
package/lib/matcher.js CHANGED
@@ -1,25 +1,42 @@
1
- const Fuse = require("fuse.js");
2
1
  const database = require("./database.json");
3
2
 
4
3
  function findError(input) {
5
- if (!input) return null;
6
-
7
- // Clean the input:
8
- // 1. Take only the first line (the error message).
9
- // 2. Remove common noise like 'Uncaught' or file paths.
10
- const coreError = input.split('\n')[0]
11
- .replace(/^Uncaught\s+/, '')
12
- .split(' at ')[0]
13
- .trim();
14
-
15
- const fuse = new Fuse(database, {
16
- keys: ["match", "name"],
17
- threshold: 0.4, // Balance between strict and loose
18
- distance: 100
4
+ if (!input) return { count: 0, matches: [] };
5
+
6
+ if (typeof input !== "string") input = String(input);
7
+
8
+ // 1. Normalize the input (Lowercase and remove extra whitespace)
9
+ const lowerInput = input.toLowerCase();
10
+
11
+ // 2. Filter the database
12
+ let foundMatches = database.filter(entry => {
13
+ const matchPhrase = entry.match.toLowerCase();
14
+
15
+ // STRICT CHECK: The phrase must exist in the error log
16
+ return lowerInput.includes(matchPhrase);
19
17
  });
20
18
 
21
- const result = fuse.search(coreError);
22
- return result.length > 0 ? result[0].item : null;
19
+ // 3. SPECIFICITY SORTING (The Secret Sauce)
20
+ // If we find "Cannot read properties of undefined" AND "TypeError",
21
+ // we want the longer, more specific one to show up first.
22
+ foundMatches.sort((a, b) => b.match.length - a.match.length);
23
+
24
+ // 4. DEDUPLICATION
25
+ // Ensure we don't show the same category twice
26
+ const uniqueMatches = [];
27
+ const seenNames = new Set();
28
+
29
+ for (const match of foundMatches) {
30
+ if (!seenNames.has(match.name)) {
31
+ uniqueMatches.push(match);
32
+ seenNames.add(match.name);
33
+ }
34
+ }
35
+
36
+ return {
37
+ count: uniqueMatches.length,
38
+ matches: uniqueMatches
39
+ };
23
40
  }
24
41
 
25
42
  module.exports = { findError };
package/package.json CHANGED
@@ -1,29 +1,58 @@
1
1
  {
2
2
  "name": "errlens",
3
- "version": "1.0.2",
4
- "description": "A CLI tool that explains JavaScript/Node.js errors in plain English.",
3
+ "version": "1.0.4",
4
+ "description": "Professional CLI tool that explains JavaScript and Node.js errors in plain English with actionable fixes directly in your terminal.",
5
+ "type": "module",
5
6
  "main": "./bin/index.js",
7
+ "bin": {
8
+ "errlens": "./bin/index.js"
9
+ },
10
+ "preferGlobal": true,
6
11
  "author": "BeyteFlow (https://github.com/BeyteFlow)",
7
12
  "license": "MIT",
8
- "bin": {
9
- "errlens": "bin/index.js"
13
+ "keywords": [
14
+ "javascript",
15
+ "nodejs",
16
+ "cli",
17
+ "error",
18
+ "debugging",
19
+ "stacktrace",
20
+ "developer-tools",
21
+ "terminal",
22
+ "diagnostics",
23
+ "productivity",
24
+ "devtools",
25
+ "error-handler"
26
+ ],
27
+ "engines": {
28
+ "node": ">=18.0.0"
10
29
  },
30
+ "files": [
31
+ "bin/",
32
+ "lib/",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
11
36
  "scripts": {
12
37
  "test": "node --test test/**/*.test.js"
13
38
  },
14
39
  "repository": {
15
40
  "type": "git",
16
- "url": "git+https://github.com/BeyteFlow/errlens.git"
41
+ "url": "https://github.com/BeyteFlow/errlens.git"
17
42
  },
18
43
  "bugs": {
19
44
  "url": "https://github.com/BeyteFlow/errlens/issues"
20
45
  },
21
46
  "homepage": "https://github.com/BeyteFlow/errlens#readme",
47
+ "funding": {
48
+ "type": "github",
49
+ "url": "https://github.com/sponsors/BeyteFlow"
50
+ },
22
51
  "dependencies": {
23
- "boxen": "^5.1.2",
24
- "chalk": "^4.1.2",
25
- "commander": "^11.1.0",
52
+ "boxen": "^8.0.1",
53
+ "chalk": "^5.6.2",
54
+ "commander": "^14.0.3",
26
55
  "fuse.js": "^7.0.0",
27
- "ora": "^5.4.1"
56
+ "ora": "^9.3.0"
28
57
  }
29
58
  }