@oussema_mili/test-pkg-123 1.1.22
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.
Potentially problematic release.
This version of @oussema_mili/test-pkg-123 might be problematic. Click here for more details.
- package/LICENSE +29 -0
- package/README.md +220 -0
- package/auth-callback.html +97 -0
- package/auth.js +276 -0
- package/cli-commands.js +1923 -0
- package/containerManager.js +304 -0
- package/daemon/agentRunner.js +429 -0
- package/daemon/daemonEntry.js +64 -0
- package/daemon/daemonManager.js +271 -0
- package/daemon/logManager.js +227 -0
- package/dist/styles.css +504 -0
- package/docker-actions/apps.js +3938 -0
- package/docker-actions/config-transformer.js +380 -0
- package/docker-actions/containers.js +355 -0
- package/docker-actions/general.js +171 -0
- package/docker-actions/images.js +1128 -0
- package/docker-actions/logs.js +224 -0
- package/docker-actions/metrics.js +270 -0
- package/docker-actions/registry.js +1100 -0
- package/docker-actions/setup-tasks.js +859 -0
- package/docker-actions/terminal.js +247 -0
- package/docker-actions/volumes.js +696 -0
- package/helper-functions.js +193 -0
- package/index.html +83 -0
- package/index.js +341 -0
- package/package.json +82 -0
- package/postcss.config.mjs +5 -0
- package/scripts/release.sh +212 -0
- package/setup/setupWizard.js +403 -0
- package/store/agentSessionStore.js +51 -0
- package/store/agentStore.js +113 -0
- package/store/configStore.js +171 -0
- package/store/daemonStore.js +217 -0
- package/store/deviceCredentialStore.js +107 -0
- package/store/npmTokenStore.js +65 -0
- package/store/registryStore.js +329 -0
- package/store/setupState.js +147 -0
- package/styles.css +1 -0
- package/utils/appLogger.js +223 -0
- package/utils/deviceInfo.js +98 -0
- package/utils/ecrAuth.js +225 -0
- package/utils/encryption.js +112 -0
- package/utils/envSetup.js +44 -0
- package/utils/errorHandler.js +327 -0
- package/utils/portUtils.js +59 -0
- package/utils/prerequisites.js +323 -0
- package/utils/prompts.js +318 -0
- package/utils/ssl-certificates.js +256 -0
- package/websocket-server.js +415 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
getDaemonPid,
|
|
10
|
+
getDaemonState,
|
|
11
|
+
clearDaemonPid,
|
|
12
|
+
clearDaemonState,
|
|
13
|
+
isDaemonRunning,
|
|
14
|
+
} from '../store/daemonStore.js';
|
|
15
|
+
import { getLogPath } from './logManager.js';
|
|
16
|
+
|
|
17
|
+
// ES module helpers
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
|
|
21
|
+
// Path to the daemon entry script
|
|
22
|
+
const DAEMON_SCRIPT = path.join(__dirname, 'daemonEntry.js');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Start the agent as a background daemon
|
|
26
|
+
* @param {Object} options - Daemon options
|
|
27
|
+
* @param {number} options.port - Preferred WebSocket port
|
|
28
|
+
* @returns {Promise<Object>} Daemon info
|
|
29
|
+
*/
|
|
30
|
+
export async function startDaemon(options = {}) {
|
|
31
|
+
// Check if daemon is already running
|
|
32
|
+
if (isDaemonRunning()) {
|
|
33
|
+
const state = getDaemonState();
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Agent is already running (PID: ${state?.pid}, Port: ${state?.port}). ` +
|
|
36
|
+
'Use "fenwave service stop" to stop it first.'
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Clean up any stale files
|
|
41
|
+
clearDaemonPid();
|
|
42
|
+
clearDaemonState();
|
|
43
|
+
|
|
44
|
+
// Prepare daemon arguments
|
|
45
|
+
const args = [];
|
|
46
|
+
if (options.port) {
|
|
47
|
+
args.push('--port', String(options.port));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Get log file paths for stdout/stderr redirection
|
|
51
|
+
const logPath = getLogPath();
|
|
52
|
+
const errorLogPath = getLogPath('agent.error');
|
|
53
|
+
|
|
54
|
+
// Ensure log directory exists
|
|
55
|
+
const logDir = path.dirname(logPath);
|
|
56
|
+
if (!fs.existsSync(logDir)) {
|
|
57
|
+
fs.mkdirSync(logDir, { recursive: true, mode: 0o700 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Open log files for writing
|
|
61
|
+
const outFd = fs.openSync(logPath, 'a');
|
|
62
|
+
const errFd = fs.openSync(errorLogPath, 'a');
|
|
63
|
+
|
|
64
|
+
// Spawn daemon process
|
|
65
|
+
const daemon = spawn('node', [DAEMON_SCRIPT, ...args], {
|
|
66
|
+
detached: true,
|
|
67
|
+
stdio: ['ignore', outFd, errFd],
|
|
68
|
+
env: {
|
|
69
|
+
...process.env,
|
|
70
|
+
FENWAVE_DAEMON: 'true',
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Unref to allow parent to exit
|
|
75
|
+
daemon.unref();
|
|
76
|
+
|
|
77
|
+
// Close file descriptors in parent
|
|
78
|
+
fs.closeSync(outFd);
|
|
79
|
+
fs.closeSync(errFd);
|
|
80
|
+
|
|
81
|
+
// Wait a moment for daemon to start and write state
|
|
82
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
83
|
+
|
|
84
|
+
// Check if daemon started successfully
|
|
85
|
+
const state = getDaemonState();
|
|
86
|
+
if (!state || state.status !== 'running') {
|
|
87
|
+
throw new Error(
|
|
88
|
+
'Daemon failed to start. Check logs with "fenwave service logs"'
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
pid: state.pid,
|
|
94
|
+
port: state.port,
|
|
95
|
+
startTime: state.startTime,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Stop the running daemon
|
|
101
|
+
* @param {Object} options - Stop options
|
|
102
|
+
* @param {boolean} options.force - Force kill if graceful fails
|
|
103
|
+
* @param {number} options.timeout - Timeout in ms to wait for graceful shutdown
|
|
104
|
+
* @returns {Promise<boolean>} True if stopped
|
|
105
|
+
*/
|
|
106
|
+
export async function stopDaemon(options = {}) {
|
|
107
|
+
const { force = false, timeout = 10000 } = options;
|
|
108
|
+
|
|
109
|
+
const pid = getDaemonPid();
|
|
110
|
+
if (!pid) {
|
|
111
|
+
console.log(chalk.yellow('No daemon PID file found'));
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check if process is running
|
|
116
|
+
if (!isDaemonRunning()) {
|
|
117
|
+
console.log(chalk.yellow('Daemon process not running, cleaning up files...'));
|
|
118
|
+
clearDaemonPid();
|
|
119
|
+
clearDaemonState();
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.log(chalk.blue(`Stopping daemon (PID: ${pid})...`));
|
|
124
|
+
|
|
125
|
+
// Send SIGTERM for graceful shutdown
|
|
126
|
+
try {
|
|
127
|
+
process.kill(pid, 'SIGTERM');
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (error.code === 'ESRCH') {
|
|
130
|
+
console.log(chalk.yellow('Process already terminated'));
|
|
131
|
+
clearDaemonPid();
|
|
132
|
+
clearDaemonState();
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Wait for process to terminate
|
|
139
|
+
const startTime = Date.now();
|
|
140
|
+
while (Date.now() - startTime < timeout) {
|
|
141
|
+
try {
|
|
142
|
+
process.kill(pid, 0);
|
|
143
|
+
// Process still running, wait
|
|
144
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
145
|
+
} catch (error) {
|
|
146
|
+
// Process terminated
|
|
147
|
+
console.log(chalk.green('Daemon stopped successfully'));
|
|
148
|
+
clearDaemonPid();
|
|
149
|
+
clearDaemonState();
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Timeout reached, try SIGKILL if force option
|
|
155
|
+
if (force) {
|
|
156
|
+
console.log(chalk.yellow('Graceful shutdown timed out, forcing...'));
|
|
157
|
+
try {
|
|
158
|
+
process.kill(pid, 'SIGKILL');
|
|
159
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
160
|
+
clearDaemonPid();
|
|
161
|
+
clearDaemonState();
|
|
162
|
+
console.log(chalk.green('Daemon force killed'));
|
|
163
|
+
return true;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (error.code === 'ESRCH') {
|
|
166
|
+
clearDaemonPid();
|
|
167
|
+
clearDaemonState();
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
throw new Error('Daemon did not stop within timeout. Use --force to kill it.');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Restart the daemon
|
|
179
|
+
* @param {Object} options - Restart options
|
|
180
|
+
* @returns {Promise<Object>} New daemon info
|
|
181
|
+
*/
|
|
182
|
+
export async function restartDaemon(options = {}) {
|
|
183
|
+
const state = getDaemonState();
|
|
184
|
+
const port = options.port || state?.port;
|
|
185
|
+
|
|
186
|
+
// Stop existing daemon if running
|
|
187
|
+
if (isDaemonRunning()) {
|
|
188
|
+
await stopDaemon({ force: false, timeout: 10000 });
|
|
189
|
+
// Wait a moment before restarting
|
|
190
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Start new daemon
|
|
194
|
+
return startDaemon({ port });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get daemon status
|
|
199
|
+
* @returns {Object} Daemon status
|
|
200
|
+
*/
|
|
201
|
+
export function getDaemonStatus() {
|
|
202
|
+
const state = getDaemonState();
|
|
203
|
+
const pid = getDaemonPid();
|
|
204
|
+
const running = isDaemonRunning();
|
|
205
|
+
|
|
206
|
+
if (!running && state) {
|
|
207
|
+
// Daemon died, clean up
|
|
208
|
+
clearDaemonPid();
|
|
209
|
+
clearDaemonState();
|
|
210
|
+
return {
|
|
211
|
+
running: false,
|
|
212
|
+
status: 'not_running',
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!state) {
|
|
217
|
+
return {
|
|
218
|
+
running: false,
|
|
219
|
+
status: 'not_running',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Calculate uptime
|
|
224
|
+
let uptime = null;
|
|
225
|
+
if (state.startTime) {
|
|
226
|
+
uptime = Date.now() - new Date(state.startTime).getTime();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
running: true,
|
|
231
|
+
status: state.status || 'running',
|
|
232
|
+
pid: state.pid,
|
|
233
|
+
port: state.port,
|
|
234
|
+
startTime: state.startTime,
|
|
235
|
+
uptime,
|
|
236
|
+
userEntityRef: state.userEntityRef,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Format uptime in human readable format
|
|
242
|
+
* @param {number} ms - Uptime in milliseconds
|
|
243
|
+
* @returns {string} Formatted uptime
|
|
244
|
+
*/
|
|
245
|
+
export function formatUptime(ms) {
|
|
246
|
+
if (!ms) return 'N/A';
|
|
247
|
+
|
|
248
|
+
const seconds = Math.floor(ms / 1000);
|
|
249
|
+
const minutes = Math.floor(seconds / 60);
|
|
250
|
+
const hours = Math.floor(minutes / 60);
|
|
251
|
+
const days = Math.floor(hours / 24);
|
|
252
|
+
|
|
253
|
+
if (days > 0) {
|
|
254
|
+
return `${days}d ${hours % 24}h ${minutes % 60}m`;
|
|
255
|
+
}
|
|
256
|
+
if (hours > 0) {
|
|
257
|
+
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
|
|
258
|
+
}
|
|
259
|
+
if (minutes > 0) {
|
|
260
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
261
|
+
}
|
|
262
|
+
return `${seconds}s`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export default {
|
|
266
|
+
startDaemon,
|
|
267
|
+
stopDaemon,
|
|
268
|
+
restartDaemon,
|
|
269
|
+
getDaemonStatus,
|
|
270
|
+
formatUptime,
|
|
271
|
+
};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const FENWAVE_DIR = path.join(os.homedir(), '.fenwave');
|
|
6
|
+
const LOGS_DIR = path.join(FENWAVE_DIR, 'logs');
|
|
7
|
+
|
|
8
|
+
// Log rotation settings
|
|
9
|
+
const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB
|
|
10
|
+
const MAX_LOG_FILES = 5;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Ensure logs directory exists
|
|
14
|
+
*/
|
|
15
|
+
function ensureLogsDir() {
|
|
16
|
+
if (!fs.existsSync(LOGS_DIR)) {
|
|
17
|
+
fs.mkdirSync(LOGS_DIR, { recursive: true, mode: 0o700 });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get path to log file
|
|
23
|
+
* @param {string} name - Log file name (without extension)
|
|
24
|
+
* @returns {string} Full path to log file
|
|
25
|
+
*/
|
|
26
|
+
export function getLogPath(name = 'agent') {
|
|
27
|
+
ensureLogsDir();
|
|
28
|
+
return path.join(LOGS_DIR, `${name}.log`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get path to error log file
|
|
33
|
+
* @returns {string} Full path to error log file
|
|
34
|
+
*/
|
|
35
|
+
export function getErrorLogPath() {
|
|
36
|
+
return getLogPath('agent.error');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Rotate log files if necessary
|
|
41
|
+
* @param {string} logPath - Path to log file
|
|
42
|
+
*/
|
|
43
|
+
export function rotateLogIfNeeded(logPath) {
|
|
44
|
+
try {
|
|
45
|
+
if (!fs.existsSync(logPath)) return;
|
|
46
|
+
|
|
47
|
+
const stats = fs.statSync(logPath);
|
|
48
|
+
if (stats.size < MAX_LOG_SIZE) return;
|
|
49
|
+
|
|
50
|
+
// Rotate existing files
|
|
51
|
+
for (let i = MAX_LOG_FILES - 1; i >= 1; i--) {
|
|
52
|
+
const oldPath = `${logPath}.${i}`;
|
|
53
|
+
const newPath = `${logPath}.${i + 1}`;
|
|
54
|
+
if (fs.existsSync(oldPath)) {
|
|
55
|
+
if (i === MAX_LOG_FILES - 1) {
|
|
56
|
+
// Delete oldest file
|
|
57
|
+
fs.unlinkSync(oldPath);
|
|
58
|
+
} else {
|
|
59
|
+
fs.renameSync(oldPath, newPath);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Rename current log to .1
|
|
65
|
+
fs.renameSync(logPath, `${logPath}.1`);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('Failed to rotate log:', error.message);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Write to log file with rotation
|
|
73
|
+
* @param {string} message - Message to log
|
|
74
|
+
* @param {string} level - Log level (info, warn, error)
|
|
75
|
+
*/
|
|
76
|
+
export function writeLog(message, level = 'info') {
|
|
77
|
+
const logPath = getLogPath();
|
|
78
|
+
rotateLogIfNeeded(logPath);
|
|
79
|
+
|
|
80
|
+
const timestamp = new Date().toISOString();
|
|
81
|
+
const logLine = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
fs.appendFileSync(logPath, logLine);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
// If main log fails, try to at least write to console
|
|
87
|
+
console.error('Failed to write to log file:', error.message);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Also write errors to error log
|
|
91
|
+
if (level === 'error') {
|
|
92
|
+
const errorLogPath = getErrorLogPath();
|
|
93
|
+
rotateLogIfNeeded(errorLogPath);
|
|
94
|
+
try {
|
|
95
|
+
fs.appendFileSync(errorLogPath, logLine);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
// Ignore
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Create a logger instance for daemon use
|
|
104
|
+
* @returns {Object} Logger with info, warn, error methods
|
|
105
|
+
*/
|
|
106
|
+
export function createLogger() {
|
|
107
|
+
return {
|
|
108
|
+
info: (message) => writeLog(message, 'info'),
|
|
109
|
+
warn: (message) => writeLog(message, 'warn'),
|
|
110
|
+
error: (message) => writeLog(message, 'error'),
|
|
111
|
+
debug: (message) => {
|
|
112
|
+
if (process.env.DEBUG === 'true') {
|
|
113
|
+
writeLog(message, 'debug');
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Read log file contents
|
|
121
|
+
* @param {number} lines - Number of lines to read from end (0 = all)
|
|
122
|
+
* @param {boolean} includeRotated - Include rotated log files
|
|
123
|
+
* @returns {string} Log content
|
|
124
|
+
*/
|
|
125
|
+
export function readLogs(lines = 100, includeRotated = false) {
|
|
126
|
+
const logPath = getLogPath();
|
|
127
|
+
let content = '';
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
// Read rotated files first if requested
|
|
131
|
+
if (includeRotated) {
|
|
132
|
+
for (let i = MAX_LOG_FILES; i >= 1; i--) {
|
|
133
|
+
const rotatedPath = `${logPath}.${i}`;
|
|
134
|
+
if (fs.existsSync(rotatedPath)) {
|
|
135
|
+
content += fs.readFileSync(rotatedPath, 'utf8');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Read current log file
|
|
141
|
+
if (fs.existsSync(logPath)) {
|
|
142
|
+
content += fs.readFileSync(logPath, 'utf8');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (lines > 0) {
|
|
146
|
+
const allLines = content.split('\n');
|
|
147
|
+
return allLines.slice(-lines).join('\n');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return content;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return `Error reading logs: ${error.message}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Clear all log files
|
|
158
|
+
*/
|
|
159
|
+
export function clearLogs() {
|
|
160
|
+
const logPath = getLogPath();
|
|
161
|
+
const errorLogPath = getErrorLogPath();
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
// Clear main log and rotated files
|
|
165
|
+
for (const basePath of [logPath, errorLogPath]) {
|
|
166
|
+
if (fs.existsSync(basePath)) {
|
|
167
|
+
fs.unlinkSync(basePath);
|
|
168
|
+
}
|
|
169
|
+
for (let i = 1; i <= MAX_LOG_FILES; i++) {
|
|
170
|
+
const rotatedPath = `${basePath}.${i}`;
|
|
171
|
+
if (fs.existsSync(rotatedPath)) {
|
|
172
|
+
fs.unlinkSync(rotatedPath);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error('Failed to clear logs:', error.message);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get logs directory path
|
|
183
|
+
* @returns {string} Path to logs directory
|
|
184
|
+
*/
|
|
185
|
+
export function getLogsDir() {
|
|
186
|
+
ensureLogsDir();
|
|
187
|
+
return LOGS_DIR;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* List all log files
|
|
192
|
+
* @returns {Array<Object>} Array of log file info
|
|
193
|
+
*/
|
|
194
|
+
export function listLogFiles() {
|
|
195
|
+
ensureLogsDir();
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const files = fs.readdirSync(LOGS_DIR);
|
|
199
|
+
return files
|
|
200
|
+
.filter((f) => f.endsWith('.log') || f.match(/\.log\.\d+$/))
|
|
201
|
+
.map((f) => {
|
|
202
|
+
const filePath = path.join(LOGS_DIR, f);
|
|
203
|
+
const stats = fs.statSync(filePath);
|
|
204
|
+
return {
|
|
205
|
+
name: f,
|
|
206
|
+
path: filePath,
|
|
207
|
+
size: stats.size,
|
|
208
|
+
modified: stats.mtime,
|
|
209
|
+
};
|
|
210
|
+
})
|
|
211
|
+
.sort((a, b) => b.modified - a.modified);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export default {
|
|
218
|
+
getLogPath,
|
|
219
|
+
getErrorLogPath,
|
|
220
|
+
rotateLogIfNeeded,
|
|
221
|
+
writeLog,
|
|
222
|
+
createLogger,
|
|
223
|
+
readLogs,
|
|
224
|
+
clearLogs,
|
|
225
|
+
getLogsDir,
|
|
226
|
+
listLogFiles,
|
|
227
|
+
};
|