nodewise 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.
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/USER_MANUAL.md +106 -0
- package/bin/nodewise.js +166 -0
- package/examples/express-server.js +70 -0
- package/examples/express-with-errors.js +63 -0
- package/examples/module-not-found.js +9 -0
- package/examples/reference-error.js +9 -0
- package/examples/syntax-error.js +11 -0
- package/examples/type-error.js +10 -0
- package/examples/working-app.js +35 -0
- package/package.json +49 -0
- package/src/config.js +167 -0
- package/src/errorPatterns.js +3253 -0
- package/src/errorWrapper.js +43 -0
- package/src/explainer/gemini.js +212 -0
- package/src/explainer/index.js +51 -0
- package/src/explainer/interactive.js +123 -0
- package/src/explainer/normal.js +27 -0
- package/src/expressErrorHandler.js +68 -0
- package/src/index.js +14 -0
- package/src/runner.js +243 -0
- package/src/setup.js +98 -0
package/src/runner.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runner.js
|
|
3
|
+
*
|
|
4
|
+
* Process runner and watcher
|
|
5
|
+
* - Spawns child process
|
|
6
|
+
* - Captures stderr for errors
|
|
7
|
+
* - Watches files for changes
|
|
8
|
+
* - Restarts on change
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { spawn } = require('child_process');
|
|
12
|
+
const chokidar = require('chokidar');
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const { explain } = require('./explainer');
|
|
15
|
+
const { showInteractiveExplainer } = require('./explainer/interactive');
|
|
16
|
+
|
|
17
|
+
class Runner {
|
|
18
|
+
constructor(scriptPath, args = [], config = {}) {
|
|
19
|
+
this.scriptPath = scriptPath;
|
|
20
|
+
this.args = args;
|
|
21
|
+
this.config = config;
|
|
22
|
+
this.child = null;
|
|
23
|
+
this.watcher = null;
|
|
24
|
+
this.isRestarting = false;
|
|
25
|
+
this.lastErrorExplanationTime = 0;
|
|
26
|
+
this.isProcessExiting = false;
|
|
27
|
+
this.ignorePatterns = config.ignorePatterns || ['node_modules', '.git', '.env'];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Start the process
|
|
32
|
+
*/
|
|
33
|
+
start() {
|
|
34
|
+
if (this.child) {
|
|
35
|
+
this.stop();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(chalk.blue(`\n▶ Starting: ${this.scriptPath}\n`));
|
|
39
|
+
|
|
40
|
+
// Set environment variable to indicate nodewise is active
|
|
41
|
+
const env = {
|
|
42
|
+
...process.env,
|
|
43
|
+
NODEWISE_ACTIVE: 'true'
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
this.child = spawn('node', [this.scriptPath, ...this.args], {
|
|
47
|
+
stdio: ['inherit', 'pipe', 'pipe'], // Capture both stdout and stderr
|
|
48
|
+
env: env
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Capture both stderr and stdout for errors
|
|
52
|
+
let errorBuffer = '';
|
|
53
|
+
let lastErrorTime = 0;
|
|
54
|
+
|
|
55
|
+
// Capture stderr
|
|
56
|
+
this.child.stderr.on('data', (data) => {
|
|
57
|
+
const text = data.toString();
|
|
58
|
+
process.stderr.write(text);
|
|
59
|
+
errorBuffer += text;
|
|
60
|
+
|
|
61
|
+
// Detect errors in stderr and explain them immediately
|
|
62
|
+
if (this.isErrorOutput(text)) {
|
|
63
|
+
this.explainErrorWithDebounce(text);
|
|
64
|
+
errorBuffer = ''; // Reset buffer after explanation
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Also capture stdout for runtime errors that get logged
|
|
69
|
+
this.child.stdout.on('data', (data) => {
|
|
70
|
+
const text = data.toString();
|
|
71
|
+
process.stdout.write(text);
|
|
72
|
+
// Check if this looks like an error logged to stdout
|
|
73
|
+
if (this.isErrorOutput(text)) {
|
|
74
|
+
errorBuffer += text;
|
|
75
|
+
// Detect errors in stdout and explain them immediately
|
|
76
|
+
this.explainErrorWithDebounce(text);
|
|
77
|
+
errorBuffer = ''; // Reset buffer after explanation
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.child.on('error', (err) => {
|
|
82
|
+
console.error(chalk.red(`\n✗ Error spawning process: ${err.message}\n`));
|
|
83
|
+
this.handleError(errorBuffer);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
this.child.on('exit', (code, signal) => {
|
|
87
|
+
this.isProcessExiting = true;
|
|
88
|
+
|
|
89
|
+
if (code !== 0 && code !== null) {
|
|
90
|
+
// Process exited with error
|
|
91
|
+
if (!this.isRestarting && errorBuffer) {
|
|
92
|
+
this.handleError(errorBuffer);
|
|
93
|
+
}
|
|
94
|
+
} else if (signal) {
|
|
95
|
+
console.log(chalk.yellow(`\nProcess terminated by signal ${signal}\n`));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.child = null;
|
|
99
|
+
errorBuffer = '';
|
|
100
|
+
this.isProcessExiting = false;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Watch for file changes
|
|
104
|
+
this.setupWatcher();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Handle error with interactive explanation
|
|
109
|
+
*/
|
|
110
|
+
async handleError(errorText) {
|
|
111
|
+
if (!errorText) return;
|
|
112
|
+
await showInteractiveExplainer(errorText, this.config);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Setup file watcher
|
|
117
|
+
*/
|
|
118
|
+
setupWatcher() {
|
|
119
|
+
if (this.watcher) {
|
|
120
|
+
this.watcher.close();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const watchPatterns = [
|
|
124
|
+
'**/*.js',
|
|
125
|
+
'**/*.json',
|
|
126
|
+
'!node_modules/**',
|
|
127
|
+
'!.git/**'
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
this.watcher = chokidar.watch(watchPatterns, {
|
|
131
|
+
ignored: this.ignorePatterns,
|
|
132
|
+
awaitWriteFinish: {
|
|
133
|
+
stabilityThreshold: 300,
|
|
134
|
+
pollInterval: 100
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
this.watcher.on('change', (path) => {
|
|
139
|
+
this.restart(`File changed: ${path}`);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Restart the process
|
|
145
|
+
*/
|
|
146
|
+
restart(reason = '') {
|
|
147
|
+
if (this.isRestarting) return;
|
|
148
|
+
|
|
149
|
+
this.isRestarting = true;
|
|
150
|
+
|
|
151
|
+
if (this.child) {
|
|
152
|
+
this.child.kill('SIGTERM');
|
|
153
|
+
|
|
154
|
+
// Wait a bit for graceful shutdown
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
if (this.child && !this.child.killed) {
|
|
157
|
+
this.child.kill('SIGKILL');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
this.isRestarting = false;
|
|
162
|
+
if (reason) {
|
|
163
|
+
console.log(chalk.yellow(`\n↻ Restarting... (${reason})\n`));
|
|
164
|
+
}
|
|
165
|
+
this.start();
|
|
166
|
+
}, 100);
|
|
167
|
+
}, 500);
|
|
168
|
+
} else {
|
|
169
|
+
this.isRestarting = false;
|
|
170
|
+
this.start();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Stop the process and watcher
|
|
176
|
+
*/
|
|
177
|
+
stop() {
|
|
178
|
+
if (this.watcher) {
|
|
179
|
+
this.watcher.close();
|
|
180
|
+
this.watcher = null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (this.child) {
|
|
184
|
+
this.child.kill('SIGTERM');
|
|
185
|
+
this.child = null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Handle graceful shutdown
|
|
191
|
+
*/
|
|
192
|
+
handleShutdown() {
|
|
193
|
+
console.log(chalk.yellow('\nShutting down nodewise...\n'));
|
|
194
|
+
this.stop();
|
|
195
|
+
process.exit(0);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Check if output contains error indicators
|
|
200
|
+
*/
|
|
201
|
+
isErrorOutput(text) {
|
|
202
|
+
const errorPatterns = [
|
|
203
|
+
/error:/i,
|
|
204
|
+
/Error\:/i,
|
|
205
|
+
/TypeError/i,
|
|
206
|
+
/ReferenceError/i,
|
|
207
|
+
/SyntaxError/i,
|
|
208
|
+
/warning:/i,
|
|
209
|
+
/failed/i,
|
|
210
|
+
/throw/i,
|
|
211
|
+
/stack trace/i,
|
|
212
|
+
/traceback/i,
|
|
213
|
+
/exception/i,
|
|
214
|
+
/crash/i,
|
|
215
|
+
/fatal/i
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
return errorPatterns.some(pattern => pattern.test(text));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Explain error with debouncing to avoid duplicate explanations
|
|
223
|
+
*/
|
|
224
|
+
async explainErrorWithDebounce(errorText) {
|
|
225
|
+
const now = Date.now();
|
|
226
|
+
// Debounce: only explain if at least 500ms has passed since last error
|
|
227
|
+
if (this.lastErrorExplanationTime && (now - this.lastErrorExplanationTime) < 500) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
this.lastErrorExplanationTime = now;
|
|
231
|
+
|
|
232
|
+
// Don't explain startup errors that already caused process exit
|
|
233
|
+
if (this.isProcessExiting) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
await showInteractiveExplainer(errorText, this.config);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module.exports = {
|
|
242
|
+
Runner
|
|
243
|
+
};
|
package/src/setup.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* setup.js
|
|
3
|
+
*
|
|
4
|
+
* Interactive setup wizard for first-time users
|
|
5
|
+
* Similar to Vite's setup experience
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const inquirer = require('inquirer').default;
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const config = require('./config');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Run interactive setup
|
|
14
|
+
*/
|
|
15
|
+
async function runSetup() {
|
|
16
|
+
console.log('\n' + chalk.cyan('┌─ Welcome to Nodewise Setup ─┐'));
|
|
17
|
+
console.log(chalk.cyan('│') +
|
|
18
|
+
chalk.blue(' Node.js error explainer with AI-powered clarity') +
|
|
19
|
+
chalk.cyan(' │'));
|
|
20
|
+
console.log(chalk.cyan('└────────────────────────────────┘\n'));
|
|
21
|
+
|
|
22
|
+
// Step 1: Choose explanation mode
|
|
23
|
+
const modeAnswer = await inquirer.prompt([
|
|
24
|
+
{
|
|
25
|
+
type: 'list',
|
|
26
|
+
name: 'mode',
|
|
27
|
+
message: 'Select explanation mode:',
|
|
28
|
+
choices: [
|
|
29
|
+
{
|
|
30
|
+
name: 'Gemini Explainer (AI-powered)',
|
|
31
|
+
value: 'gemini',
|
|
32
|
+
description: 'Uses Google Gemini AI to explain errors intelligently'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'Normal Detection (Pattern-based)',
|
|
36
|
+
value: 'normal',
|
|
37
|
+
description: 'Uses built-in pattern matching to explain common errors'
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
default: 'normal'
|
|
41
|
+
}
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
let finalConfig;
|
|
45
|
+
|
|
46
|
+
if (modeAnswer.mode === 'gemini') {
|
|
47
|
+
console.log('\n' + chalk.cyan('┌─ Google Gemini Setup ─┐'));
|
|
48
|
+
console.log(chalk.cyan('│'));
|
|
49
|
+
console.log(chalk.cyan('│') + ' 1. Visit: ' + chalk.blue('https://makersuite.google.com/app/apikey'));
|
|
50
|
+
console.log(chalk.cyan('│') + ' 2. Click "Create API Key"');
|
|
51
|
+
console.log(chalk.cyan('│') + ' 3. Copy the key and paste below');
|
|
52
|
+
console.log(chalk.cyan('│'));
|
|
53
|
+
console.log(chalk.cyan('└──────────────────────────────┘\n'));
|
|
54
|
+
|
|
55
|
+
const geminiAnswers = await inquirer.prompt([
|
|
56
|
+
{
|
|
57
|
+
type: 'password',
|
|
58
|
+
name: 'apiKey',
|
|
59
|
+
message: 'Paste your Gemini API Key:',
|
|
60
|
+
mask: '*',
|
|
61
|
+
validate: (input) => {
|
|
62
|
+
const trimmed = input.trim();
|
|
63
|
+
if (!trimmed || trimmed.length < 10) {
|
|
64
|
+
return 'API key seems invalid (too short). Copy the full key from makersuite.google.com';
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
finalConfig = config.createConfig('gemini', {
|
|
72
|
+
apiKey: geminiAnswers.apiKey.trim()
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
console.log('\n' + chalk.green('✓ Gemini configuration saved!'));
|
|
76
|
+
console.log(chalk.gray(' Endpoint: https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent'));
|
|
77
|
+
} else {
|
|
78
|
+
finalConfig = config.createConfig('normal');
|
|
79
|
+
console.log('\n' + chalk.green('✓ Normal mode configured'));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Display next steps
|
|
83
|
+
console.log(chalk.cyan('\n┌─ Next Steps ─┐'));
|
|
84
|
+
console.log(chalk.cyan('│'));
|
|
85
|
+
console.log(chalk.cyan('│') + ' Run your app with nodewise:');
|
|
86
|
+
console.log(chalk.cyan('│') + ' ' + chalk.yellow('npx nodewise my-app.js'));
|
|
87
|
+
console.log(chalk.cyan('│'));
|
|
88
|
+
console.log(chalk.cyan('│') + ' Or with arguments:');
|
|
89
|
+
console.log(chalk.cyan('│') + ' ' + chalk.yellow('npx nodewise server.js --port 3000'));
|
|
90
|
+
console.log(chalk.cyan('│'));
|
|
91
|
+
console.log(chalk.cyan('└──────────────────────────────┘\n'));
|
|
92
|
+
|
|
93
|
+
return finalConfig;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
runSetup
|
|
98
|
+
};
|