Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 20 additions & 138 deletions bin/index.js
Original file line number Diff line number Diff line change
@@ -1,151 +1,33 @@
#!/usr/bin/env node
const database = require('../lib/database');
const Fuse = require('fuse.js');
const { Command } = require('commander');

const { Command } = require("commander");
const { spawn } = require("child_process");
const path = require("path");
const { findError } = require("../lib/matcher");
const { formatError } = require("../lib/formatter");
const { version } = require("../package.json");
const fuse = new Fuse(database, { keys: ['pattern'] });

const program = new Command();

program
.name("errlens")
.description("Professional JS Error Analytics")
.version(version)
.option("--json", "Output JSON instead of pretty UI"); // ✅ GLOBAL OPTION
.name('errlens')
.description('Translate errors to human-readable insights')
.version('1.0.9');

// ----------------- RUN COMMAND -----------------
program
.command("run <file>")
.description("Run a Javascript file and analyze crashes")
.action(async (file) => {
const { default: ora } = await import("ora");
const { default: chalk } = await import("chalk");

const isJson = Boolean(program.opts().json);
const filePath = path.resolve(process.cwd(), file);
const spinner = isJson
? null
: ora(`Running ${chalk.yellow(file)}...`).start();

const child = spawn(process.execPath, [filePath], {
stdio: ["inherit", "pipe", "pipe"],
});

let errorOutput = "";

// Stream stdout only in pretty mode
child.stdout.on("data", (data) => {
if (!isJson) {
spinner.stop();
process.stdout.write(data);
spinner.start();
}
});

// Capture stderr (DO NOT print in JSON mode)
child.stderr.on("data", (data) => {
errorOutput += data.toString();

if (!isJson) {
process.stderr.write(data);
}
});

child.on("close", (code, signal) => {
if (!isJson && spinner) {
spinner.stop();
}

const { count, matches } = findError(errorOutput);

// Process killed by signal
if (code === null) {
const result = { code: 1, count, matches };

if (isJson) {
console.log(JSON.stringify(result, null, 2));
} else {
console.log(
chalk.red.bold(`\n⚠️ Process killed by signal: ${signal}`)
);
}

process.exit(1);
}

// JSON MODE
if (isJson) {
console.log(JSON.stringify({ code, count, matches }, null, 2));
process.exit(code ?? 1);
}

// PRETTY MODE
if (code === 0) {
console.log(chalk.green.bold("\n✨ Process finished successfully."));
} else {
if (count > 0) {
console.log(
chalk.bold.cyan(`\n🚀 ErrLens Analysis (${count} Issue(s)):`)
);
matches.forEach((m) => console.log(formatError(m)));
} else {
console.log(
chalk.red.bold("\n❌ Crash detected (No known fix in database):")
);
console.log(chalk.gray(errorOutput));
}
}

process.exit(code ?? 1);
});

child.on("error", (err) => {
const result = { code: 1, count: 0, matches: [] };

if (isJson) {
console.log(JSON.stringify(result, null, 2));
.command('explain')
.argument('<message>', 'error message to explain')
.option('--json', 'output in json format')
.action((message, options) => {
Comment on lines +16 to +18
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "index.js" -path "*/bin/*" | head -5

Repository: BeyteFlow/errlens

Length of output: 75


🏁 Script executed:

cat -n ./bin/index.js | head -30

Repository: BeyteFlow/errlens

Length of output: 1201


🌐 Web query:

Commander.js variadic arguments syntax <message...>

💡 Result:

In Commander.js, appending ... to an argument placeholder makes it variadic (captures multiple values into an array).

  • <message...> = required variadic positional argument named message (1+ values), parsed as an array. Only the last positional argument may be variadic. [1]
  • [message...] = optional variadic positional argument (0+ values), also parsed as an array. [3]

Example:

program
  .argument('<message...>')
  .action((message) => {
    console.log(message); // array of words
  });

Sources: Commander documentation describing variadic command arguments and ... syntax. [1] [3]


🏁 Script executed:

cat -n ./bin/index.js

Repository: BeyteFlow/errlens

Length of output: 1258


Accept pasted error messages without forcing quotes.

Line 16 captures only one argv token. Most real errors contain spaces, so errlens explain ReferenceError: foo is not defined gets split into extra args unless the user remembers to quote the whole message.

🧩 Variadic message capture
-  .argument('<message>', 'error message to explain')
+  .argument('<message...>', 'error message to explain')
   .option('--json', 'output in json format')
-  .action((message, options) => {
+  .action((messageParts, options) => {
+    const message = messageParts.join(' ');
     const results = fuse.search(message);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/index.js` around lines 16 - 18, Change the single-token argument to a
variadic message and join the pieces in the action handler: replace the
.argument('<message>', 'error message to explain') with a variadic form (use the
CLI lib's variadic syntax, e.g. '<message...>') and update the .action handler
signature that currently expects (message, options) to accept the array of
message parts (or rest args) and join them into the full message before further
processing; refer to the .argument and .action declarations and the parameter
named message to locate the changes.

const results = fuse.search(message);
const result = results.length > 0 ? results[0].item : null;

if (options.json) {
console.log(JSON.stringify(result || { message: "No explanation found" }));
} else {
if (result) {
console.log(`Explanation: ${result.explanation}\nCause: ${result.cause}\nFix: ${result.fix}`);
} else {
if (spinner) spinner.stop();
console.log(chalk.red(`System Error: ${err.message}`));
console.log("No specific explanation found for this error.");
}

process.exit(1);
});
});

// ----------------- ANALYZE COMMAND -----------------
program
.command("analyze <errorString>")
.description("Analyze a specific error string")
.action(async (errorString) => {
const { default: chalk } = await import("chalk");
const isJson = Boolean(program.opts().json);
const { count, matches } = findError(errorString);
const exitCode = count > 0 ? 1 : 0;

if (isJson) {
console.log(
JSON.stringify({ code: exitCode, count, matches }, null, 2)
);
process.exit(exitCode);
}

if (count > 0) {
console.log(
chalk.bold.cyan(`\n🚀 ErrLens Analysis (${count} Issue(s)):`)
);
matches.forEach((m) => console.log(formatError(m)));
} else {
console.log(
chalk.red.bold("\n❌ Crash detected (No known fix in database):")
);
console.log(chalk.gray(errorString));
}

process.exit(exitCode);
});

// ----------------- PARSE -----------------
program.parse(process.argv);
32 changes: 32 additions & 0 deletions lib/database.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module.exports = [
{
pattern: "is not defined",
explanation: "You are trying to use a variable that hasn't been declared or is out of scope.",
cause: "Commonly caused by typos, missing variable declarations (var/let/const), or trying to access a variable before it is defined.",
fix: "Check for typos in the variable name. Ensure the variable is declared in the correct scope before accessing it."
},
{
pattern: "is not a function",
explanation: "You are trying to call a value as if it were a function, but the value is of a different type (like undefined, null, or a string).",
cause: "Often happens when an object property is missing, or an asynchronous function returns an unexpected result.",
fix: "Verify that the object or property you are calling is actually a function. Use 'typeof' to check if necessary."
},
{
pattern: "Cannot read property of undefined",
explanation: "You are trying to access a property on an object that is currently undefined.",
cause: "The object you are accessing hasn't been initialized or the API response you expected is missing.",
fix: "Use optional chaining (e.g., obj?.property) or add a null check before accessing the property."
Comment on lines +15 to +18
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's explore the repository structure
find . -type f -name "*.js" | head -20

Repository: BeyteFlow/errlens

Length of output: 161


🏁 Script executed:

# Check if lib/database.js and bin/index.js exist and examine them
ls -la lib/ bin/ 2>/dev/null || echo "Checking with find..."
git ls-files | grep -E "(lib|bin).*\.js"

Repository: BeyteFlow/errlens

Length of output: 830


🏁 Script executed:

# Read lib/database.js to see the current content at lines 15-18
cat -n lib/database.js

Repository: BeyteFlow/errlens

Length of output: 2210


🏁 Script executed:

# Read bin/index.js to understand how patterns are searched
cat -n bin/index.js

Repository: BeyteFlow/errlens

Length of output: 1258


🏁 Script executed:

# Check Node.js version requirements
cat package.json | grep -A 5 -B 5 "engines\|node"

Repository: BeyteFlow/errlens

Length of output: 634


🏁 Script executed:

# Also check for .nvmrc or other version files
ls -la | grep -E "\.nvmrc|\.node-version|\.tool-versions"
cat .nvmrc 2>/dev/null || echo "No .nvmrc file"

Repository: BeyteFlow/errlens

Length of output: 75


🌐 Web query:

Node.js 20 error message "Cannot read property of undefined" vs "Cannot read properties of undefined"

💡 Result:

The difference is just the JavaScript engine (V8) / Node.js version, not a different underlying error.

  • Older Node/V8 often threw messages like:
    TypeError: Cannot read property 'foo' of undefined
  • Newer Node/V8 (including Node.js 20) commonly throws:
    TypeError: Cannot read properties of undefined (reading 'foo')

This message wording change is why the same failing code (or the same test) can produce different text on different machines/CI images—pinning the Node version makes the message consistent. [1] Rollbar’s examples also show both variants in the wild. [2]

Practical takeaway: if you have tests/snapshots that assert the full error message, prefer asserting on error.name (TypeError), or match the message with a regex that allows both forms. [1][2]

Sources: [1] [2]


Update error pattern for Node.js 20 compatibility.

Line 15 uses Cannot read property of undefined, which is an incomplete legacy form that doesn't match Node.js 20's error messages. Since the project targets Node.js >=20.0.0, this pattern must use the modern pluralized form. Modern Node.js throws Cannot read properties of undefined (plural), not the singular form currently in the database. Because pattern is the only searchable field in the Fuse index, this mismatch prevents the error explanation from being matched at runtime.

Fix
-    pattern: "Cannot read property of undefined",
+    pattern: "Cannot read properties of undefined",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pattern: "Cannot read property of undefined",
explanation: "You are trying to access a property on an object that is currently undefined.",
cause: "The object you are accessing hasn't been initialized or the API response you expected is missing.",
fix: "Use optional chaining (e.g., obj?.property) or add a null check before accessing the property."
pattern: "Cannot read properties of undefined",
explanation: "You are trying to access a property on an object that is currently undefined.",
cause: "The object you are accessing hasn't been initialized or the API response you expected is missing.",
fix: "Use optional chaining (e.g., obj?.property) or add a null check before accessing the property."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/database.js` around lines 15 - 18, The error pattern string used in the
database error mapping, specifically the "pattern" value currently set to
"Cannot read property of undefined" (in lib/database.js), is the legacy singular
form and must be updated to the modern Node.js 20 message "Cannot read
properties of undefined" so that the Fuse index can match runtime errors; update
that "pattern" entry to the pluralized wording (or a small regex variant that
matches both forms) so the error explanation will be found at runtime.

},
{
pattern: "Maximum call stack size exceeded",
explanation: "Your code is hitting the recursion limit of the JavaScript engine.",
cause: "Typically caused by an infinite loop in a recursive function or missing a base case.",
fix: "Review your recursive function to ensure there is a clear exit condition and that it is reachable."
},
{
pattern: "Unexpected token",
explanation: "The JavaScript engine encountered a character it did not expect while parsing your code.",
cause: "Missing closing braces, parentheses, or commas in your syntax.",
fix: "Use an IDE with linting (like ESLint) to identify missing brackets or incorrect syntax patterns."
}
];