aether-hub 1.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.
@@ -0,0 +1,315 @@
1
+ /**
2
+ * aether-cli logs
3
+ *
4
+ * Tails and colourises validator log files.
5
+ * Supports both live process output and existing log files.
6
+ *
7
+ * Usage:
8
+ * aether-cli logs # Tail default validator log
9
+ * aether-cli logs --file <path> # Tail specific log file
10
+ * aether-cli logs --follow # Follow mode (like tail -f)
11
+ * aether-cli logs --lines <n> # Show last N lines
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { spawn } = require('child_process');
17
+ const readline = require('readline');
18
+
19
+ // ANSI colors
20
+ const colors = {
21
+ reset: '\x1b[0m',
22
+ bright: '\x1b[1m',
23
+ dim: '\x1b[2m',
24
+ black: '\x1b[30m',
25
+ red: '\x1b[31m',
26
+ green: '\x1b[32m',
27
+ yellow: '\x1b[33m',
28
+ blue: '\x1b[34m',
29
+ magenta: '\x1b[35m',
30
+ cyan: '\x1b[36m',
31
+ white: '\x1b[37m',
32
+ bgRed: '\x1b[41m',
33
+ bgYellow: '\x1b[43m',
34
+ bgGreen: '\x1b[42m',
35
+ };
36
+
37
+ // Log level color mapping
38
+ const levelColors = {
39
+ ERROR: colors.bgRed + colors.white + colors.bright,
40
+ WARN: colors.bgYellow + colors.black,
41
+ INFO: colors.green,
42
+ DEBUG: colors.blue,
43
+ TRACE: colors.dim,
44
+ };
45
+
46
+ // Regex patterns for log levels
47
+ const logPatterns = {
48
+ ERROR: /\bERROR\b|\bERR\b|\bFATAL\b|\[error\]|\[ERROR\]/i,
49
+ WARN: /\bWARN\b|\bWARNING\b|\bWARN\b|\[warn\]|\[WARNING\]/i,
50
+ INFO: /\bINFO\b|\[info\]|\[INFO\]/i,
51
+ DEBUG: /\bDEBUG\b|\[debug\]|\[DEBUG\]/i,
52
+ TRACE: /\bTRACE\b|\[trace\]|\[TRACE\]/i,
53
+ };
54
+
55
+ /**
56
+ * Detect log level in a line and return colorized version
57
+ */
58
+ function colorizeLogLine(line) {
59
+ // Check for log levels in order of severity
60
+ for (const [level, pattern] of Object.entries(logPatterns)) {
61
+ if (pattern.test(line)) {
62
+ const color = levelColors[level] || colors.reset;
63
+ return `${color}${line}${colors.reset}`;
64
+ }
65
+ }
66
+
67
+ // Check for common patterns
68
+ if (/failed|error|exception|crash|panic/i.test(line)) {
69
+ return `${colors.red}${line}${colors.reset}`;
70
+ }
71
+ if (/success|complete|ready|started|connected/i.test(line)) {
72
+ return `${colors.green}${line}${colors.reset}`;
73
+ }
74
+ if (/warning|timeout|retry|slow/i.test(line)) {
75
+ return `${colors.yellow}${line}${colors.reset}`;
76
+ }
77
+
78
+ return line;
79
+ }
80
+
81
+ /**
82
+ * Print the logs banner
83
+ */
84
+ function printBanner(options) {
85
+ const timestamp = new Date().toISOString().split('T')[0];
86
+
87
+ console.log(`
88
+ ${colors.cyan}╔═══════════════════════════════════════════════════════════════╗
89
+ ${colors.cyan}║ ║
90
+ ${colors.cyan}║ ${colors.bright}AETHER LOGS${colors.reset}${colors.cyan} ║
91
+ ${colors.cyan}║ ${colors.bright}Validator Log Viewer${colors.reset}${colors.cyan} ║
92
+ ${colors.cyan}║ ║
93
+ ${colors.cyan}╚═══════════════════════════════════════════════════════════════╝${colors.reset}
94
+ `);
95
+
96
+ console.log(` ${colors.bright}Log File:${colors.reset} ${options.filePath}`);
97
+ console.log(` ${colors.bright}Mode:${colors.reset} ${options.follow ? 'Follow (tail -f)' : 'Static'}`);
98
+ console.log(` ${colors.bright}Lines:${colors.reset} ${options.lines === -1 ? 'All' : `Last ${options.lines}`}`);
99
+ console.log();
100
+ console.log(` ${colors.dim}Legend: ${colors.bgRed + colors.white}ERROR${colors.reset} ${colors.bgYellow + colors.black}WARN${colors.reset} ${colors.green}INFO${colors.reset} ${colors.blue}DEBUG${colors.reset} ${colors.dim}TRACE${colors.reset}${colors.reset}`);
101
+ console.log(` ${colors.dim}Press Ctrl+C to exit${colors.reset}`);
102
+ console.log();
103
+ console.log(`${colors.cyan}${'─'.repeat(60)}${colors.reset}`);
104
+ }
105
+
106
+ /**
107
+ * Find default validator log file
108
+ */
109
+ function findDefaultLogFile() {
110
+ const workspaceRoot = path.join(__dirname, '..', '..');
111
+ const repoPath = path.join(workspaceRoot, 'Jelly-legs-unsteady-workshop');
112
+
113
+ // Common log file locations
114
+ const candidates = [
115
+ path.join(repoPath, 'validator.log'),
116
+ path.join(repoPath, 'testnet-debug.log'),
117
+ path.join(repoPath, 'testnet', 'node1.log'),
118
+ path.join(repoPath, 'aether-validator.log'),
119
+ path.join(process.cwd(), 'validator.log'),
120
+ ];
121
+
122
+ for (const candidate of candidates) {
123
+ if (fs.existsSync(candidate)) {
124
+ return candidate;
125
+ }
126
+ }
127
+
128
+ // Return the most likely location even if it doesn't exist yet
129
+ return path.join(repoPath, 'validator.log');
130
+ }
131
+
132
+ /**
133
+ * Parse command line args
134
+ */
135
+ function parseArgs() {
136
+ const args = process.argv.slice(3); // Skip 'aether-cli logs'
137
+
138
+ const options = {
139
+ filePath: null,
140
+ follow: false,
141
+ lines: 50,
142
+ };
143
+
144
+ for (let i = 0; i < args.length; i++) {
145
+ switch (args[i]) {
146
+ case '--file':
147
+ case '-f':
148
+ options.filePath = args[++i];
149
+ break;
150
+ case '--follow':
151
+ case '-F':
152
+ options.follow = true;
153
+ break;
154
+ case '--lines':
155
+ case '-n':
156
+ options.lines = parseInt(args[++i], 10) || 50;
157
+ break;
158
+ case '--help':
159
+ case '-h':
160
+ showHelp();
161
+ process.exit(0);
162
+ }
163
+ }
164
+
165
+ // Default file if not specified
166
+ if (!options.filePath) {
167
+ options.filePath = findDefaultLogFile();
168
+ }
169
+
170
+ return options;
171
+ }
172
+
173
+ /**
174
+ * Show help message
175
+ */
176
+ function showHelp() {
177
+ console.log(`
178
+ ${colors.bright}aether-cli logs${colors.reset} - Validator Log Viewer
179
+
180
+ ${colors.cyan}Usage:${colors.reset}
181
+ aether-cli logs [options]
182
+
183
+ ${colors.cyan}Options:${colors.reset}
184
+ --file, -f <path> Path to log file (default: auto-detect)
185
+ --follow, -F Follow mode (like tail -f)
186
+ --lines, -n <num> Number of lines to show (default: 50, use -1 for all)
187
+ --help, -h Show this help message
188
+
189
+ ${colors.cyan}Examples:${colors.reset}
190
+ aether-cli logs # Tail default validator log
191
+ aether-cli logs --follow # Follow live logs
192
+ aether-cli logs --file ./my.log -n 100
193
+ aether-cli logs -F -n -1 # Follow all lines
194
+
195
+ ${colors.cyan}Log Level Colors:${colors.reset}
196
+ ERROR ${colors.bgRed + colors.white}Red background${colors.reset}
197
+ WARN ${colors.bgYellow + colors.black}Yellow background${colors.reset}
198
+ INFO ${colors.green}Green${colors.reset}
199
+ DEBUG ${colors.blue}Blue${colors.reset}
200
+ TRACE ${colors.dim}Dim${colors.reset}
201
+ `);
202
+ }
203
+
204
+ /**
205
+ * Read and display log file (static mode)
206
+ */
207
+ function readLogFile(filePath, lines) {
208
+ if (!fs.existsSync(filePath)) {
209
+ console.log(` ${colors.yellow}⚠ Log file not found: ${filePath}${colors.reset}`);
210
+ console.log(` ${colors.dim}Start the validator first to generate logs.${colors.reset}`);
211
+ return;
212
+ }
213
+
214
+ const content = fs.readFileSync(filePath, 'utf-8');
215
+ const allLines = content.split('\n').filter(line => line.trim());
216
+
217
+ // Get last N lines
218
+ const displayLines = lines === -1
219
+ ? allLines
220
+ : allLines.slice(-lines);
221
+
222
+ displayLines.forEach(line => {
223
+ console.log(colorizeLogLine(line));
224
+ });
225
+ }
226
+
227
+ /**
228
+ * Follow log file (tail -f mode)
229
+ */
230
+ function followLogFile(filePath, lines) {
231
+ if (!fs.existsSync(filePath)) {
232
+ console.log(` ${colors.yellow}⚠ Log file not found: ${filePath}${colors.reset}`);
233
+ console.log(` ${colors.dim}Waiting for file to be created...${colors.reset}`);
234
+ console.log();
235
+ }
236
+
237
+ // Use platform-specific tail command
238
+ const platform = process.platform;
239
+ let tailCmd, tailArgs;
240
+
241
+ if (platform === 'win32') {
242
+ // PowerShell Get-Content -Wait is equivalent to tail -f
243
+ tailCmd = 'powershell';
244
+ tailArgs = [
245
+ '-Command',
246
+ `Get-Content "${filePath}" -Wait -Tail ${lines === -1 ? 1000 : lines} 2>$null`
247
+ ];
248
+ } else {
249
+ tailCmd = 'tail';
250
+ tailArgs = ['-F', '-n', lines === -1 ? '1000' : lines.toString(), filePath];
251
+ }
252
+
253
+ const tail = spawn(tailCmd, tailArgs, {
254
+ stdio: ['ignore', 'pipe', 'pipe'],
255
+ });
256
+
257
+ tail.stdout.on('data', (data) => {
258
+ const text = data.toString();
259
+ const textLines = text.split('\n');
260
+ textLines.forEach(line => {
261
+ if (line.trim()) {
262
+ console.log(colorizeLogLine(line));
263
+ }
264
+ });
265
+ });
266
+
267
+ tail.stderr.on('data', (data) => {
268
+ const text = data.toString();
269
+ if (text.includes('No such file') || text.includes('cannot open')) {
270
+ // File doesn't exist yet, wait
271
+ return;
272
+ }
273
+ console.log(`${colors.red}${text}${colors.reset}`);
274
+ });
275
+
276
+ tail.on('error', (err) => {
277
+ console.log(`${colors.red}Error: ${err.message}${colors.reset}`);
278
+ });
279
+
280
+ tail.on('close', (code) => {
281
+ if (code !== 0 && code !== null) {
282
+ console.log(`\n${colors.yellow}Log viewer exited with code ${code}${colors.reset}`);
283
+ }
284
+ });
285
+
286
+ // Handle Ctrl+C
287
+ process.on('SIGINT', () => {
288
+ console.log(`\n${colors.cyan}Logs viewer stopped.${colors.reset}`);
289
+ tail.kill();
290
+ process.exit(0);
291
+ });
292
+ }
293
+
294
+ /**
295
+ * Main logs command
296
+ */
297
+ function logsCommand() {
298
+ const options = parseArgs();
299
+
300
+ printBanner(options);
301
+
302
+ if (options.follow) {
303
+ followLogFile(options.filePath, options.lines);
304
+ } else {
305
+ readLogFile(options.filePath, options.lines);
306
+ }
307
+ }
308
+
309
+ // Export for use as module
310
+ module.exports = { logsCommand, colorizeLogLine, findDefaultLogFile };
311
+
312
+ // Run if called directly
313
+ if (require.main === module) {
314
+ logsCommand();
315
+ }