@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/README.md +11 -11
- package/index.js +83 -83
- package/js/titan/builder.js +50 -50
- package/js/titan/bundle.js +164 -164
- package/js/titan/dev.js +53 -19
- package/js/titan/error-box.js +277 -277
- package/package.json +1 -1
- package/ts/titan/bundle.js +227 -227
- package/ts/titan/dev.js +53 -20
- package/ts/titan/error-box.js +277 -277
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
|
|
42
|
-
if (fs.existsSync(
|
|
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
|
|
50
|
-
const cliPkgPath =
|
|
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
|
-
|
|
56
|
-
const
|
|
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
|
|
63
|
-
const pkgPath =
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
139
|
+
setTimeout(onExit, 800); // Fallback
|
|
106
140
|
});
|
|
107
141
|
}
|
|
108
142
|
|
package/js/titan/error-box.js
CHANGED
|
@@ -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
|
+
}
|