@titanpl/packet 2.0.1 → 2.0.3

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/js/titan/dev.js CHANGED
@@ -38,33 +38,53 @@ function getEngineBinaryPath(root) {
38
38
  // 1. Monorepo search (dev environment)
39
39
  let current = root;
40
40
  for (let i = 0; i < 5; i++) {
41
- const potential = path.join(current, 'engine', 'target', 'release', binName);
42
- if (fs.existsSync(potential)) return potential;
41
+ const potentialRelease = path.join(current, 'engine', 'target', 'release', binName);
42
+ if (fs.existsSync(potentialRelease)) return potentialRelease;
43
+ const potentialDebug = path.join(current, 'engine', 'target', 'debug', binName);
44
+ if (fs.existsSync(potentialDebug)) return potentialDebug;
45
+
46
+ // Check sibling monorepo folder for test-apps
47
+ const siblingRelease = path.join(current, 'titanpl', 'engine', 'target', 'release', binName);
48
+ if (fs.existsSync(siblingRelease)) return siblingRelease;
49
+ const siblingDebug = path.join(current, 'titanpl', 'engine', 'target', 'debug', binName);
50
+ if (fs.existsSync(siblingDebug)) return siblingDebug;
51
+ const siblingPkg = path.join(current, 'titanpl', 'packages', pkgName.replace('@titanpl/', ''), 'bin', binName);
52
+ if (fs.existsSync(siblingPkg)) return siblingPkg;
53
+
43
54
  current = path.dirname(current);
44
55
  }
45
56
 
46
57
  // 2. Search relative to @titanpl/cli (where optionalDependencies are installed)
47
- // This is the primary path for globally-installed CLI users.
48
58
  try {
49
- const require = createRequire(import.meta.url);
50
- const cliPkgPath = require.resolve('@titanpl/cli/package.json');
59
+ const req = createRequire(import.meta.url);
60
+ const cliPkgPath = req.resolve('@titanpl/cli/package.json');
51
61
  const cliDir = path.dirname(cliPkgPath);
52
- // Check cli's own node_modules (global install sibling)
53
62
  const cliNodeModulesBin = path.join(cliDir, 'node_modules', pkgName, 'bin', binName);
54
63
  if (fs.existsSync(cliNodeModulesBin)) return cliNodeModulesBin;
55
- // Check parent node_modules (hoisted global install)
56
- const parentNodeModulesBin = path.join(path.dirname(cliDir), pkgName, 'bin', binName);
64
+
65
+ const nodeModulesDir = path.dirname(path.dirname(cliDir));
66
+ const parentNodeModulesBin = path.join(nodeModulesDir, pkgName, 'bin', binName);
57
67
  if (fs.existsSync(parentNodeModulesBin)) return parentNodeModulesBin;
58
68
  } catch (e) { }
59
69
 
60
- // 3. Search in the project's own node_modules
70
+ // 3. Search in the project's own node_modules directly
61
71
  try {
62
- const require = createRequire(import.meta.url);
63
- const pkgPath = require.resolve(`${pkgName}/package.json`);
72
+ const req = createRequire(import.meta.url);
73
+ const pkgPath = req.resolve(`${pkgName}/package.json`);
64
74
  const binPath = path.join(path.dirname(pkgPath), 'bin', binName);
65
75
  if (fs.existsSync(binPath)) return binPath;
66
76
  } catch (e) { }
67
77
 
78
+ // Walk upwards from current dir searching for node_modules/@titanpl/engine-...
79
+ let searchDir = process.cwd();
80
+ for (let i = 0; i < 5; i++) {
81
+ const nmBin = path.join(searchDir, 'node_modules', pkgName, 'bin', binName);
82
+ if (fs.existsSync(nmBin)) return nmBin;
83
+ const parent = path.dirname(searchDir);
84
+ if (parent === searchDir) break;
85
+ searchDir = parent;
86
+ }
87
+
68
88
  // 4. Fallback: check common global npm paths
69
89
  const globalSearchRoots = [
70
90
  process.env.npm_config_prefix,
@@ -76,8 +96,16 @@ function getEngineBinaryPath(root) {
76
96
  for (const gRoot of globalSearchRoots) {
77
97
  const gBin = path.join(gRoot, 'node_modules', pkgName, 'bin', binName);
78
98
  if (fs.existsSync(gBin)) return gBin;
99
+ const libNodeModulesBin = path.join(gRoot, 'lib', 'node_modules', pkgName, 'bin', binName);
100
+ if (fs.existsSync(libNodeModulesBin)) return libNodeModulesBin;
79
101
  }
80
102
 
103
+ try {
104
+ const globalModules = execSync('npm root -g').toString().trim();
105
+ const globalBin = path.join(globalModules, pkgName, 'bin', binName);
106
+ if (fs.existsSync(globalBin)) return globalBin;
107
+ } catch (e) { }
108
+
81
109
  return null;
82
110
  }
83
111
 
@@ -87,22 +115,28 @@ async function killServer() {
87
115
  if (!serverProcess) return;
88
116
 
89
117
  return new Promise((resolve) => {
90
- const onExit = () => {
118
+ if (serverProcess.killed || serverProcess.exitCode !== null) {
91
119
  serverProcess = null;
92
120
  resolve();
121
+ return;
122
+ }
123
+
124
+ let isDone = false;
125
+ const onExit = () => {
126
+ if (isDone) return;
127
+ isDone = true;
128
+ serverProcess = null;
129
+ setTimeout(resolve, 300); // Grace period for OS socket release
93
130
  };
94
131
 
95
132
  serverProcess.on('exit', onExit);
133
+ serverProcess.on('error', onExit);
96
134
 
97
- if (os.platform() === 'win32') {
98
- try {
99
- execSync(`taskkill /pid ${serverProcess.pid} /f /t`, { stdio: 'ignore' });
100
- } catch (e) { }
101
- } else {
135
+ try {
102
136
  serverProcess.kill('SIGKILL');
103
- }
137
+ } catch (e) { }
104
138
 
105
- setTimeout(onExit, 500);
139
+ setTimeout(onExit, 800); // Fallback
106
140
  });
107
141
  }
108
142
 
@@ -1,277 +1,277 @@
1
- /**
2
- * Error Box Renderer
3
- * Renders errors in a Next.js-style red terminal box
4
- */
5
-
6
- import fs from 'fs';
7
- import path from 'path';
8
- import { createRequire } from 'module';
9
- import { execSync } from 'child_process';
10
- import { fileURLToPath } from 'url';
11
-
12
- const __filename = fileURLToPath(import.meta.url);
13
- const __dirname = path.dirname(__filename);
14
-
15
- // Simple color function using ANSI escape codes
16
- const red = (text) => `\x1b[31m${text}\x1b[0m`;
17
-
18
- /**
19
- * Wraps text to fit within a specified width
20
- * @param {string} text - Text to wrap
21
- * @param {number} maxWidth - Maximum width per line
22
- * @returns {string[]} Array of wrapped lines
23
- */
24
-
25
- function getTitanVersion() {
26
- try {
27
- const require = createRequire(import.meta.url);
28
- const pkgPath = require.resolve("titanpl/package.json");
29
- return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
30
- } catch (e) {
31
- try {
32
- let cur = __dirname;
33
- for (let i = 0; i < 5; i++) {
34
- const pkgPath = path.join(cur, "package.json");
35
- if (fs.existsSync(pkgPath)) {
36
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
37
- if (pkg.name === "titanpl") return pkg.version;
38
- }
39
- cur = path.join(cur, "..");
40
- }
41
- } catch (e2) { }
42
-
43
- try {
44
- const output = execSync("tit --version", { encoding: "utf-8" }).trim();
45
- const match = output.match(/v(\d+\.\d+\.\d+)/);
46
- if (match) return match[1];
47
- } catch (e3) { }
48
- }
49
- return "0.1.0";
50
- }
51
-
52
- function wrapText(text, maxWidth) {
53
- if (!text) return [''];
54
-
55
- const lines = text.split('\n');
56
- const wrapped = [];
57
-
58
- for (const line of lines) {
59
- if (line.length <= maxWidth) {
60
- wrapped.push(line);
61
- continue;
62
- }
63
-
64
- // Word wrap logic
65
- const words = line.split(' ');
66
- let currentLine = '';
67
-
68
- for (const word of words) {
69
- const testLine = currentLine ? `${currentLine} ${word}` : word;
70
-
71
- if (testLine.length <= maxWidth) {
72
- currentLine = testLine;
73
- } else {
74
- if (currentLine) {
75
- wrapped.push(currentLine);
76
- }
77
- // If single word is too long, force split it
78
- if (word.length > maxWidth) {
79
- let remaining = word;
80
- while (remaining.length > maxWidth) {
81
- wrapped.push(remaining.substring(0, maxWidth));
82
- remaining = remaining.substring(maxWidth);
83
- }
84
- currentLine = remaining;
85
- } else {
86
- currentLine = word;
87
- }
88
- }
89
- }
90
-
91
- if (currentLine) {
92
- wrapped.push(currentLine);
93
- }
94
- }
95
-
96
- return wrapped.length > 0 ? wrapped : [''];
97
- }
98
-
99
- /**
100
- * Pads text to fit within box width
101
- * @param {string} text - Text to pad
102
- * @param {number} width - Target width
103
- * @returns {string} Padded text
104
- */
105
- function padLine(text, width) {
106
- const padding = width - text.length;
107
- return text + ' '.repeat(Math.max(0, padding));
108
- }
109
-
110
- /**
111
- * Renders an error in a Next.js-style red box
112
- * @param {Object} errorInfo - Error information
113
- * @param {string} errorInfo.title - Error title (e.g., "Build Error")
114
- * @param {string} errorInfo.file - File path where error occurred
115
- * @param {string} errorInfo.message - Error message
116
- * @param {string} [errorInfo.location] - Error location (e.g., "at hello.js:3:1")
117
- * @param {number} [errorInfo.line] - Line number
118
- * @param {number} [errorInfo.column] - Column number
119
- * @param {string} [errorInfo.codeFrame] - Code frame showing error context
120
- * @param {string} [errorInfo.suggestion] - Recommended fix
121
- */
122
- /**
123
- * Renders an error in a Next.js-style red box
124
- * @param {Object} errorInfo - Error information
125
- * @param {string} errorInfo.title - Error title (e.g., "Build Error")
126
- * @param {string} errorInfo.file - File path where error occurred
127
- * @param {string} errorInfo.message - Error message
128
- * @param {string} [errorInfo.location] - Error location (e.g., "at hello.js:3:1")
129
- * @param {number} [errorInfo.line] - Line number
130
- * @param {number} [errorInfo.column] - Column number
131
- * @param {string} [errorInfo.codeFrame] - Code frame showing error context
132
- * @param {string} [errorInfo.suggestion] - Recommended fix
133
- */
134
- export function renderErrorBox(errorInfo) {
135
- const boxWidth = 72;
136
- const contentWidth = boxWidth - 4; // Account for "│ " and " │"
137
-
138
- const lines = [];
139
-
140
- // Add title
141
- if (errorInfo.title) {
142
- lines.push(bold(errorInfo.title));
143
- }
144
-
145
- // Add file path
146
- if (errorInfo.file) {
147
- lines.push(errorInfo.file);
148
- }
149
-
150
- // Add message
151
- if (errorInfo.message) {
152
- lines.push('');
153
- lines.push(...wrapText(errorInfo.message, contentWidth));
154
- }
155
-
156
- // Add location
157
- if (errorInfo.location) {
158
- lines.push(gray(errorInfo.location));
159
- } else if (errorInfo.file && errorInfo.line !== undefined) {
160
- const loc = `at ${errorInfo.file}:${errorInfo.line}${errorInfo.column !== undefined ? `:${errorInfo.column}` : ''}`;
161
- lines.push(gray(loc));
162
- }
163
-
164
- // Add code frame if available
165
- if (errorInfo.codeFrame) {
166
- lines.push(''); // Empty line for separation
167
- const frameLines = errorInfo.codeFrame.split('\n');
168
- for (const frameLine of frameLines) {
169
- lines.push(frameLine);
170
- }
171
- }
172
-
173
- // Add suggestion if available
174
- if (errorInfo.suggestion) {
175
- lines.push(''); // Empty line for separation
176
- lines.push(...wrapText('Recommended fix: ' + errorInfo.suggestion, contentWidth));
177
- }
178
-
179
- // Add Footer with Branding
180
- lines.push('');
181
- const version = getTitanVersion()
182
- lines.push(gray(`⏣ Titan Planet ${version}`));
183
-
184
- // Build the box
185
- const topBorder = '┌' + '─'.repeat(boxWidth - 2) + '┐';
186
- const bottomBorder = '└' + '─'.repeat(boxWidth - 2) + '┘';
187
-
188
-
189
- const boxLines = [
190
- red(topBorder),
191
- ...lines.map(line => {
192
- // Strip ANSI codes for padding calculation if any were added
193
- const plainLine = line.replace(/\x1b\[\d+m/g, '');
194
- const padding = ' '.repeat(Math.max(0, contentWidth - plainLine.length));
195
- return red('│ ') + line + padding + red(' │');
196
- }),
197
- red(bottomBorder)
198
- ];
199
-
200
- return boxLines.join('\n');
201
- }
202
-
203
- // Internal formatting helpers
204
- function gray(t) { return `\x1b[90m${t}\x1b[0m`; }
205
- function bold(t) { return `\x1b[1m${t}\x1b[0m`; }
206
-
207
- /**
208
- * Parses esbuild error and extracts relevant information
209
- * @param {Object} error - esbuild error object
210
- * @returns {Object} Parsed error information
211
- */
212
- export function parseEsbuildError(error) {
213
- const errorInfo = {
214
- title: 'Build Error',
215
- file: error.location?.file || 'unknown',
216
- message: error.text || error.message || 'Unknown error',
217
- line: error.location?.line,
218
- column: error.location?.column,
219
- location: null,
220
- codeFrame: null,
221
- suggestion: error.notes?.[0]?.text || null
222
- };
223
-
224
- // Format location
225
- if (error.location) {
226
- const { file, line, column } = error.location;
227
- errorInfo.location = `at ${file}:${line}:${column}`;
228
-
229
- // Format code frame if lineText is available
230
- if (error.location.lineText) {
231
- const lineText = error.location.lineText;
232
- // Ensure column is at least 1 to prevent negative values
233
- const col = Math.max(0, (column || 1) - 1);
234
- const pointer = ' '.repeat(col) + '^';
235
- errorInfo.codeFrame = `${line} | ${lineText}\n${' '.repeat(String(line).length)} | ${pointer}`;
236
- }
237
- }
238
-
239
- return errorInfo;
240
- }
241
-
242
- /**
243
- * Parses Node.js syntax error and extracts relevant information
244
- * @param {Error} error - Node.js error object
245
- * @param {string} [file] - File path (if known)
246
- * @returns {Object} Parsed error information
247
- */
248
- export function parseNodeError(error, file = null) {
249
- const errorInfo = {
250
- title: 'Syntax Error',
251
- file: file || 'unknown',
252
- message: error.message || 'Unknown error',
253
- location: null,
254
- suggestion: null
255
- };
256
-
257
- // Try to extract line and column from error message
258
- const locationMatch = error.message.match(/\((\d+):(\d+)\)/) ||
259
- error.stack?.match(/:(\d+):(\d+)/);
260
-
261
- if (locationMatch) {
262
- const line = parseInt(locationMatch[1]);
263
- const column = parseInt(locationMatch[2]);
264
- errorInfo.line = line;
265
- errorInfo.column = column;
266
- errorInfo.location = `at ${errorInfo.file}:${line}:${column}`;
267
- }
268
-
269
- // Extract suggestion from error message if available
270
- if (error.message.includes('expected')) {
271
- errorInfo.suggestion = 'Check for missing or misplaced syntax elements';
272
- } else if (error.message.includes('Unexpected token')) {
273
- errorInfo.suggestion = 'Remove or fix the unexpected token';
274
- }
275
-
276
- return errorInfo;
277
- }
1
+ /**
2
+ * Error Box Renderer
3
+ * Renders errors in a Next.js-style red terminal box
4
+ */
5
+
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { createRequire } from 'module';
9
+ import { execSync } from 'child_process';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ // Simple color function using ANSI escape codes
16
+ const red = (text) => `\x1b[31m${text}\x1b[0m`;
17
+
18
+ /**
19
+ * Wraps text to fit within a specified width
20
+ * @param {string} text - Text to wrap
21
+ * @param {number} maxWidth - Maximum width per line
22
+ * @returns {string[]} Array of wrapped lines
23
+ */
24
+
25
+ function getTitanVersion() {
26
+ try {
27
+ const require = createRequire(import.meta.url);
28
+ const pkgPath = require.resolve("titanpl/package.json");
29
+ return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
30
+ } catch (e) {
31
+ try {
32
+ let cur = __dirname;
33
+ for (let i = 0; i < 5; i++) {
34
+ const pkgPath = path.join(cur, "package.json");
35
+ if (fs.existsSync(pkgPath)) {
36
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
37
+ if (pkg.name === "titanpl") return pkg.version;
38
+ }
39
+ cur = path.join(cur, "..");
40
+ }
41
+ } catch (e2) { }
42
+
43
+ try {
44
+ const output = execSync("tit --version", { encoding: "utf-8" }).trim();
45
+ const match = output.match(/v(\d+\.\d+\.\d+)/);
46
+ if (match) return match[1];
47
+ } catch (e3) { }
48
+ }
49
+ return "0.1.0";
50
+ }
51
+
52
+ function wrapText(text, maxWidth) {
53
+ if (!text) return [''];
54
+
55
+ const lines = text.split('\n');
56
+ const wrapped = [];
57
+
58
+ for (const line of lines) {
59
+ if (line.length <= maxWidth) {
60
+ wrapped.push(line);
61
+ continue;
62
+ }
63
+
64
+ // Word wrap logic
65
+ const words = line.split(' ');
66
+ let currentLine = '';
67
+
68
+ for (const word of words) {
69
+ const testLine = currentLine ? `${currentLine} ${word}` : word;
70
+
71
+ if (testLine.length <= maxWidth) {
72
+ currentLine = testLine;
73
+ } else {
74
+ if (currentLine) {
75
+ wrapped.push(currentLine);
76
+ }
77
+ // If single word is too long, force split it
78
+ if (word.length > maxWidth) {
79
+ let remaining = word;
80
+ while (remaining.length > maxWidth) {
81
+ wrapped.push(remaining.substring(0, maxWidth));
82
+ remaining = remaining.substring(maxWidth);
83
+ }
84
+ currentLine = remaining;
85
+ } else {
86
+ currentLine = word;
87
+ }
88
+ }
89
+ }
90
+
91
+ if (currentLine) {
92
+ wrapped.push(currentLine);
93
+ }
94
+ }
95
+
96
+ return wrapped.length > 0 ? wrapped : [''];
97
+ }
98
+
99
+ /**
100
+ * Pads text to fit within box width
101
+ * @param {string} text - Text to pad
102
+ * @param {number} width - Target width
103
+ * @returns {string} Padded text
104
+ */
105
+ function padLine(text, width) {
106
+ const padding = width - text.length;
107
+ return text + ' '.repeat(Math.max(0, padding));
108
+ }
109
+
110
+ /**
111
+ * Renders an error in a Next.js-style red box
112
+ * @param {Object} errorInfo - Error information
113
+ * @param {string} errorInfo.title - Error title (e.g., "Build Error")
114
+ * @param {string} errorInfo.file - File path where error occurred
115
+ * @param {string} errorInfo.message - Error message
116
+ * @param {string} [errorInfo.location] - Error location (e.g., "at hello.js:3:1")
117
+ * @param {number} [errorInfo.line] - Line number
118
+ * @param {number} [errorInfo.column] - Column number
119
+ * @param {string} [errorInfo.codeFrame] - Code frame showing error context
120
+ * @param {string} [errorInfo.suggestion] - Recommended fix
121
+ */
122
+ /**
123
+ * Renders an error in a Next.js-style red box
124
+ * @param {Object} errorInfo - Error information
125
+ * @param {string} errorInfo.title - Error title (e.g., "Build Error")
126
+ * @param {string} errorInfo.file - File path where error occurred
127
+ * @param {string} errorInfo.message - Error message
128
+ * @param {string} [errorInfo.location] - Error location (e.g., "at hello.js:3:1")
129
+ * @param {number} [errorInfo.line] - Line number
130
+ * @param {number} [errorInfo.column] - Column number
131
+ * @param {string} [errorInfo.codeFrame] - Code frame showing error context
132
+ * @param {string} [errorInfo.suggestion] - Recommended fix
133
+ */
134
+ export function renderErrorBox(errorInfo) {
135
+ const boxWidth = 72;
136
+ const contentWidth = boxWidth - 4; // Account for "│ " and " │"
137
+
138
+ const lines = [];
139
+
140
+ // Add title
141
+ if (errorInfo.title) {
142
+ lines.push(bold(errorInfo.title));
143
+ }
144
+
145
+ // Add file path
146
+ if (errorInfo.file) {
147
+ lines.push(errorInfo.file);
148
+ }
149
+
150
+ // Add message
151
+ if (errorInfo.message) {
152
+ lines.push('');
153
+ lines.push(...wrapText(errorInfo.message, contentWidth));
154
+ }
155
+
156
+ // Add location
157
+ if (errorInfo.location) {
158
+ lines.push(gray(errorInfo.location));
159
+ } else if (errorInfo.file && errorInfo.line !== undefined) {
160
+ const loc = `at ${errorInfo.file}:${errorInfo.line}${errorInfo.column !== undefined ? `:${errorInfo.column}` : ''}`;
161
+ lines.push(gray(loc));
162
+ }
163
+
164
+ // Add code frame if available
165
+ if (errorInfo.codeFrame) {
166
+ lines.push(''); // Empty line for separation
167
+ const frameLines = errorInfo.codeFrame.split('\n');
168
+ for (const frameLine of frameLines) {
169
+ lines.push(frameLine);
170
+ }
171
+ }
172
+
173
+ // Add suggestion if available
174
+ if (errorInfo.suggestion) {
175
+ lines.push(''); // Empty line for separation
176
+ lines.push(...wrapText('Recommended fix: ' + errorInfo.suggestion, contentWidth));
177
+ }
178
+
179
+ // Add Footer with Branding
180
+ lines.push('');
181
+ const version = getTitanVersion()
182
+ lines.push(gray(`⏣ Titan Planet ${version}`));
183
+
184
+ // Build the box
185
+ const topBorder = '┌' + '─'.repeat(boxWidth - 2) + '┐';
186
+ const bottomBorder = '└' + '─'.repeat(boxWidth - 2) + '┘';
187
+
188
+
189
+ const boxLines = [
190
+ red(topBorder),
191
+ ...lines.map(line => {
192
+ // Strip ANSI codes for padding calculation if any were added
193
+ const plainLine = line.replace(/\x1b\[\d+m/g, '');
194
+ const padding = ' '.repeat(Math.max(0, contentWidth - plainLine.length));
195
+ return red('│ ') + line + padding + red(' │');
196
+ }),
197
+ red(bottomBorder)
198
+ ];
199
+
200
+ return boxLines.join('\n');
201
+ }
202
+
203
+ // Internal formatting helpers
204
+ function gray(t) { return `\x1b[90m${t}\x1b[0m`; }
205
+ function bold(t) { return `\x1b[1m${t}\x1b[0m`; }
206
+
207
+ /**
208
+ * Parses esbuild error and extracts relevant information
209
+ * @param {Object} error - esbuild error object
210
+ * @returns {Object} Parsed error information
211
+ */
212
+ export function parseEsbuildError(error) {
213
+ const errorInfo = {
214
+ title: 'Build Error',
215
+ file: error.location?.file || 'unknown',
216
+ message: error.text || error.message || 'Unknown error',
217
+ line: error.location?.line,
218
+ column: error.location?.column,
219
+ location: null,
220
+ codeFrame: null,
221
+ suggestion: error.notes?.[0]?.text || null
222
+ };
223
+
224
+ // Format location
225
+ if (error.location) {
226
+ const { file, line, column } = error.location;
227
+ errorInfo.location = `at ${file}:${line}:${column}`;
228
+
229
+ // Format code frame if lineText is available
230
+ if (error.location.lineText) {
231
+ const lineText = error.location.lineText;
232
+ // Ensure column is at least 1 to prevent negative values
233
+ const col = Math.max(0, (column || 1) - 1);
234
+ const pointer = ' '.repeat(col) + '^';
235
+ errorInfo.codeFrame = `${line} | ${lineText}\n${' '.repeat(String(line).length)} | ${pointer}`;
236
+ }
237
+ }
238
+
239
+ return errorInfo;
240
+ }
241
+
242
+ /**
243
+ * Parses Node.js syntax error and extracts relevant information
244
+ * @param {Error} error - Node.js error object
245
+ * @param {string} [file] - File path (if known)
246
+ * @returns {Object} Parsed error information
247
+ */
248
+ export function parseNodeError(error, file = null) {
249
+ const errorInfo = {
250
+ title: 'Syntax Error',
251
+ file: file || 'unknown',
252
+ message: error.message || 'Unknown error',
253
+ location: null,
254
+ suggestion: null
255
+ };
256
+
257
+ // Try to extract line and column from error message
258
+ const locationMatch = error.message.match(/\((\d+):(\d+)\)/) ||
259
+ error.stack?.match(/:(\d+):(\d+)/);
260
+
261
+ if (locationMatch) {
262
+ const line = parseInt(locationMatch[1]);
263
+ const column = parseInt(locationMatch[2]);
264
+ errorInfo.line = line;
265
+ errorInfo.column = column;
266
+ errorInfo.location = `at ${errorInfo.file}:${line}:${column}`;
267
+ }
268
+
269
+ // Extract suggestion from error message if available
270
+ if (error.message.includes('expected')) {
271
+ errorInfo.suggestion = 'Check for missing or misplaced syntax elements';
272
+ } else if (error.message.includes('Unexpected token')) {
273
+ errorInfo.suggestion = 'Remove or fix the unexpected token';
274
+ }
275
+
276
+ return errorInfo;
277
+ }