neex 0.1.7 → 0.2.5
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 +39 -226
- package/bun.lock +621 -0
- package/dist/src/cli.js +12 -583
- package/dist/src/index.js +1 -3
- package/dist/src/logger.js +0 -36
- package/dist/src/runner.js +78 -112
- package/package.json +2 -4
- package/dist/src/dev-runner.js +0 -209
- package/dist/src/process-manager.js +0 -426
- package/dist/src/utils.js +0 -10
- package/dist/src/watcher.js +0 -245
- package/feet.txt +0 -16
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.ProcessManager = void 0;
|
|
30
|
-
// src/process-manager.ts - PM2 alternative process manager
|
|
31
|
-
const child_process_1 = require("child_process");
|
|
32
|
-
const fsFull = __importStar(require("fs")); // Renamed to fsFull to avoid conflicts
|
|
33
|
-
const fs_1 = require("fs");
|
|
34
|
-
const path = __importStar(require("path"));
|
|
35
|
-
const events_1 = require("events");
|
|
36
|
-
const logger_1 = __importDefault(require("./logger"));
|
|
37
|
-
class ProcessManager extends events_1.EventEmitter {
|
|
38
|
-
constructor(configPath) {
|
|
39
|
-
super();
|
|
40
|
-
this.logStreams = new Map();
|
|
41
|
-
this.processes = new Map();
|
|
42
|
-
this.isMonitoring = false;
|
|
43
|
-
this.configPath = configPath || path.join(process.cwd(), '.neex', 'processes.json');
|
|
44
|
-
this.logDir = path.join(path.dirname(this.configPath), 'logs');
|
|
45
|
-
this.ensureConfigDirAndLogDir(); // Renamed and updated
|
|
46
|
-
// loadConfig will be called by CLI commands as needed before operations like list, stop, etc.
|
|
47
|
-
this.startMonitoring();
|
|
48
|
-
}
|
|
49
|
-
async ensureConfigDirAndLogDir() {
|
|
50
|
-
const dir = path.dirname(this.configPath);
|
|
51
|
-
try {
|
|
52
|
-
await fs_1.promises.mkdir(dir, { recursive: true });
|
|
53
|
-
await fs_1.promises.mkdir(this.logDir, { recursive: true });
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
logger_1.default.printLine(`Failed to create config/log directories: ${error.message}`, 'error');
|
|
57
|
-
// Directory might already exist or other error
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
async saveConfig() {
|
|
61
|
-
try {
|
|
62
|
-
const config = Array.from(this.processes.values()).map(proc => ({
|
|
63
|
-
...proc.config,
|
|
64
|
-
status: proc.status,
|
|
65
|
-
created_at: proc.created_at,
|
|
66
|
-
restarts: proc.restarts
|
|
67
|
-
}));
|
|
68
|
-
await fs_1.promises.writeFile(this.configPath, JSON.stringify(config, null, 2));
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
logger_1.default.printLine(`Failed to save config: ${error.message}`, 'error');
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
async loadConfig() {
|
|
75
|
-
try {
|
|
76
|
-
const data = await fs_1.promises.readFile(this.configPath, 'utf-8');
|
|
77
|
-
const configs = JSON.parse(data);
|
|
78
|
-
for (const config of configs) {
|
|
79
|
-
if (config.id && !this.processes.has(config.id)) {
|
|
80
|
-
const processInfo = {
|
|
81
|
-
id: config.id,
|
|
82
|
-
name: config.name,
|
|
83
|
-
status: 'stopped',
|
|
84
|
-
uptime: 0,
|
|
85
|
-
restarts: 0,
|
|
86
|
-
memory: 0,
|
|
87
|
-
cpu: 0,
|
|
88
|
-
created_at: new Date(),
|
|
89
|
-
config
|
|
90
|
-
};
|
|
91
|
-
this.processes.set(config.id, processInfo);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
// Config file might not exist yet
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
generateId(name) {
|
|
100
|
-
let id = name.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
101
|
-
let counter = 0;
|
|
102
|
-
while (this.processes.has(id)) {
|
|
103
|
-
counter++;
|
|
104
|
-
id = `${name.toLowerCase().replace(/[^a-z0-9]/g, '-')}-${counter}`;
|
|
105
|
-
}
|
|
106
|
-
return id;
|
|
107
|
-
}
|
|
108
|
-
closeLogStreams(id) {
|
|
109
|
-
var _a, _b;
|
|
110
|
-
const streams = this.logStreams.get(id);
|
|
111
|
-
if (streams) {
|
|
112
|
-
(_a = streams.out) === null || _a === void 0 ? void 0 : _a.end();
|
|
113
|
-
(_b = streams.err) === null || _b === void 0 ? void 0 : _b.end();
|
|
114
|
-
this.logStreams.delete(id);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
async startProcess(processInfo) {
|
|
118
|
-
const { config } = processInfo;
|
|
119
|
-
// Setup log files
|
|
120
|
-
const defaultLogPath = (type) => path.join(this.logDir, `${processInfo.id}.${type}.log`);
|
|
121
|
-
config.out_file = config.out_file || defaultLogPath('out');
|
|
122
|
-
config.error_file = config.error_file || defaultLogPath('err');
|
|
123
|
-
this.closeLogStreams(processInfo.id); // Ensure any previous streams for this ID are closed (e.g., on restart)
|
|
124
|
-
let outStream;
|
|
125
|
-
let errStream;
|
|
126
|
-
try {
|
|
127
|
-
if (config.out_file) {
|
|
128
|
-
await fs_1.promises.mkdir(path.dirname(config.out_file), { recursive: true }); // Ensure directory for custom log file exists
|
|
129
|
-
outStream = fsFull.createWriteStream(config.out_file, { flags: 'a' });
|
|
130
|
-
this.logStreams.set(processInfo.id, { ...this.logStreams.get(processInfo.id), out: outStream });
|
|
131
|
-
}
|
|
132
|
-
if (config.error_file) {
|
|
133
|
-
await fs_1.promises.mkdir(path.dirname(config.error_file), { recursive: true }); // Ensure directory for custom log file exists
|
|
134
|
-
errStream = fsFull.createWriteStream(config.error_file, { flags: 'a' });
|
|
135
|
-
this.logStreams.set(processInfo.id, { ...this.logStreams.get(processInfo.id), err: errStream });
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
catch (streamError) {
|
|
139
|
-
logger_1.default.printLine(`Failed to open log streams for ${processInfo.id}: ${streamError.message}`, 'error');
|
|
140
|
-
}
|
|
141
|
-
try {
|
|
142
|
-
processInfo.status = 'launching';
|
|
143
|
-
this.emit('process:launching', processInfo);
|
|
144
|
-
const args = config.args || [];
|
|
145
|
-
const env = {
|
|
146
|
-
...process.env,
|
|
147
|
-
...config.env,
|
|
148
|
-
NEEX_PROCESS_ID: processInfo.id,
|
|
149
|
-
NEEX_PROCESS_NAME: processInfo.name
|
|
150
|
-
};
|
|
151
|
-
const childProcess = (0, child_process_1.spawn)(config.script, args, {
|
|
152
|
-
cwd: config.cwd || process.cwd(),
|
|
153
|
-
env,
|
|
154
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
155
|
-
detached: true
|
|
156
|
-
});
|
|
157
|
-
processInfo.process = childProcess;
|
|
158
|
-
processInfo.pid = childProcess.pid;
|
|
159
|
-
processInfo.status = 'online';
|
|
160
|
-
processInfo.created_at = new Date();
|
|
161
|
-
// Allow parent to exit independently of the child
|
|
162
|
-
if (childProcess.pid) { // unref only if process spawned successfully
|
|
163
|
-
childProcess.unref();
|
|
164
|
-
}
|
|
165
|
-
this.emit('process:start', processInfo);
|
|
166
|
-
// Pipe to log files if streams are available
|
|
167
|
-
if (outStream && childProcess.stdout) {
|
|
168
|
-
childProcess.stdout.pipe(outStream);
|
|
169
|
-
}
|
|
170
|
-
if (errStream && childProcess.stderr) {
|
|
171
|
-
childProcess.stderr.pipe(errStream);
|
|
172
|
-
}
|
|
173
|
-
// Handle stdout
|
|
174
|
-
if (childProcess.stdout) {
|
|
175
|
-
childProcess.stdout.on('data', (data) => {
|
|
176
|
-
this.emit('process:log', {
|
|
177
|
-
id: processInfo.id,
|
|
178
|
-
type: 'stdout',
|
|
179
|
-
data: data.toString()
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
// Handle stderr
|
|
184
|
-
if (childProcess.stderr) {
|
|
185
|
-
childProcess.stderr.on('data', (data) => {
|
|
186
|
-
this.emit('process:log', {
|
|
187
|
-
id: processInfo.id,
|
|
188
|
-
type: 'stderr',
|
|
189
|
-
data: data.toString()
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
// Handle process exit
|
|
194
|
-
childProcess.on('exit', (code, signal) => {
|
|
195
|
-
processInfo.status = code === 0 ? 'stopped' : 'errored';
|
|
196
|
-
processInfo.process = undefined;
|
|
197
|
-
processInfo.pid = undefined;
|
|
198
|
-
// Note: Streams are not closed here on 'exit' to allow logs to be read after a crash.
|
|
199
|
-
// They are closed on explicit stop/delete or neex shutdown.
|
|
200
|
-
this.emit('process:exit', { processInfo, code, signal });
|
|
201
|
-
// Auto-restart if enabled
|
|
202
|
-
if (config.autorestart && processInfo.restarts < (config.max_restarts || 10)) {
|
|
203
|
-
const delay = config.restart_delay || 1000;
|
|
204
|
-
setTimeout(() => {
|
|
205
|
-
if (processInfo.status !== 'stopped') {
|
|
206
|
-
this.restart(processInfo.id);
|
|
207
|
-
}
|
|
208
|
-
}, delay);
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
// Handle process error
|
|
212
|
-
childProcess.on('error', (error) => {
|
|
213
|
-
processInfo.status = 'errored';
|
|
214
|
-
this.emit('process:error', { processInfo, error });
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
catch (error) {
|
|
218
|
-
processInfo.status = 'errored';
|
|
219
|
-
this.emit('process:error', { processInfo, error });
|
|
220
|
-
throw error;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
startMonitoring() {
|
|
224
|
-
if (this.isMonitoring)
|
|
225
|
-
return;
|
|
226
|
-
this.isMonitoring = true;
|
|
227
|
-
this.monitoringInterval = setInterval(() => {
|
|
228
|
-
this.updateProcessStats();
|
|
229
|
-
}, 5000); // Update every 5 seconds
|
|
230
|
-
}
|
|
231
|
-
stopMonitoring() {
|
|
232
|
-
if (this.monitoringInterval) {
|
|
233
|
-
clearInterval(this.monitoringInterval);
|
|
234
|
-
this.monitoringInterval = undefined;
|
|
235
|
-
}
|
|
236
|
-
this.isMonitoring = false;
|
|
237
|
-
}
|
|
238
|
-
async updateProcessStats() {
|
|
239
|
-
for (const processInfo of this.processes.values()) {
|
|
240
|
-
if (processInfo.status === 'online' && processInfo.pid) {
|
|
241
|
-
try {
|
|
242
|
-
// Update uptime
|
|
243
|
-
const now = Date.now();
|
|
244
|
-
processInfo.uptime = Math.floor((now - processInfo.created_at.getTime()) / 1000);
|
|
245
|
-
// TODO: Implement memory and CPU monitoring
|
|
246
|
-
// This would require platform-specific code or external libraries
|
|
247
|
-
// For now, we'll set placeholder values
|
|
248
|
-
processInfo.memory = 0;
|
|
249
|
-
processInfo.cpu = 0;
|
|
250
|
-
}
|
|
251
|
-
catch (error) {
|
|
252
|
-
// Process might have died
|
|
253
|
-
if (processInfo.status === 'online') {
|
|
254
|
-
processInfo.status = 'stopped';
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
async start(config) {
|
|
261
|
-
const id = this.generateId(config.name);
|
|
262
|
-
const processInfo = {
|
|
263
|
-
id,
|
|
264
|
-
name: config.name,
|
|
265
|
-
status: 'stopped',
|
|
266
|
-
uptime: 0,
|
|
267
|
-
restarts: 0,
|
|
268
|
-
memory: 0,
|
|
269
|
-
cpu: 0,
|
|
270
|
-
created_at: new Date(),
|
|
271
|
-
config: { ...config, id }
|
|
272
|
-
};
|
|
273
|
-
this.processes.set(id, processInfo);
|
|
274
|
-
await this.startProcess(processInfo);
|
|
275
|
-
await this.saveConfig();
|
|
276
|
-
return id;
|
|
277
|
-
}
|
|
278
|
-
async stop(id) {
|
|
279
|
-
const processInfo = this.processes.get(id);
|
|
280
|
-
if (!processInfo) {
|
|
281
|
-
throw new Error(`Process ${id} not found`);
|
|
282
|
-
}
|
|
283
|
-
if (processInfo.status === 'stopped') {
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
processInfo.status = 'stopping';
|
|
287
|
-
if (processInfo.process && processInfo.pid) {
|
|
288
|
-
try {
|
|
289
|
-
// Try graceful shutdown first
|
|
290
|
-
processInfo.process.kill('SIGTERM');
|
|
291
|
-
// Force kill after timeout
|
|
292
|
-
setTimeout(() => {
|
|
293
|
-
if (processInfo.process && !processInfo.process.killed) {
|
|
294
|
-
processInfo.process.kill('SIGKILL');
|
|
295
|
-
}
|
|
296
|
-
}, 5000);
|
|
297
|
-
}
|
|
298
|
-
catch (error) {
|
|
299
|
-
// Process might already be dead
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
processInfo.status = 'stopped';
|
|
303
|
-
processInfo.process = undefined;
|
|
304
|
-
processInfo.pid = undefined;
|
|
305
|
-
this.closeLogStreams(id);
|
|
306
|
-
await this.saveConfig();
|
|
307
|
-
this.emit('process:stop', processInfo);
|
|
308
|
-
}
|
|
309
|
-
async restart(id) {
|
|
310
|
-
const processInfo = this.processes.get(id);
|
|
311
|
-
if (!processInfo) {
|
|
312
|
-
throw new Error(`Process ${id} not found`);
|
|
313
|
-
}
|
|
314
|
-
if (processInfo.status === 'online') {
|
|
315
|
-
await this.stop(id);
|
|
316
|
-
// Wait a bit before restarting
|
|
317
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
318
|
-
}
|
|
319
|
-
processInfo.restarts++;
|
|
320
|
-
processInfo.last_restart = new Date();
|
|
321
|
-
await this.startProcess(processInfo);
|
|
322
|
-
await this.saveConfig();
|
|
323
|
-
}
|
|
324
|
-
async delete(id) {
|
|
325
|
-
const processInfo = this.processes.get(id);
|
|
326
|
-
if (!processInfo) {
|
|
327
|
-
throw new Error(`Process ${id} not found`);
|
|
328
|
-
}
|
|
329
|
-
if (processInfo.status === 'online') {
|
|
330
|
-
await this.stop(id);
|
|
331
|
-
}
|
|
332
|
-
const currentProcessInfo = this.processes.get(id);
|
|
333
|
-
if (currentProcessInfo) {
|
|
334
|
-
if (currentProcessInfo.status === 'online' || currentProcessInfo.status === 'launching' || currentProcessInfo.status === 'stopping') {
|
|
335
|
-
// stop() will call closeLogStreams
|
|
336
|
-
}
|
|
337
|
-
else {
|
|
338
|
-
this.closeLogStreams(id); // Ensure closed for other states before deleting
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
// Optional: Delete actual log files if `delete` implies full data removal
|
|
342
|
-
// if (config.out_file) await fsPromises.unlink(config.out_file).catch(() => {});
|
|
343
|
-
// if (config.error_file) await fsPromises.unlink(config.error_file).catch(() => {});
|
|
344
|
-
this.processes.delete(id);
|
|
345
|
-
await this.saveConfig();
|
|
346
|
-
if (currentProcessInfo)
|
|
347
|
-
this.emit('process:delete', currentProcessInfo);
|
|
348
|
-
}
|
|
349
|
-
async list() {
|
|
350
|
-
return Array.from(this.processes.values());
|
|
351
|
-
}
|
|
352
|
-
async status(id) {
|
|
353
|
-
if (id) {
|
|
354
|
-
const processInfo = this.processes.get(id);
|
|
355
|
-
if (!processInfo) {
|
|
356
|
-
throw new Error(`Process ${id} not found`);
|
|
357
|
-
}
|
|
358
|
-
return processInfo;
|
|
359
|
-
}
|
|
360
|
-
return this.list();
|
|
361
|
-
}
|
|
362
|
-
async logs(id, lines = 15) {
|
|
363
|
-
let processInfo = this.processes.get(id);
|
|
364
|
-
if (!processInfo) {
|
|
365
|
-
await this.loadConfig(); // Attempt to load if not in memory
|
|
366
|
-
processInfo = this.processes.get(id);
|
|
367
|
-
if (!processInfo) {
|
|
368
|
-
throw new Error(`Process ${id} not found`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
return this.readLogFiles(processInfo.config, lines);
|
|
372
|
-
}
|
|
373
|
-
async readLogFiles(config, linesToRead) {
|
|
374
|
-
const finalMessages = [];
|
|
375
|
-
const { out_file, error_file } = config;
|
|
376
|
-
const readFileLastLines = async (filePath) => {
|
|
377
|
-
if (!filePath)
|
|
378
|
-
return [];
|
|
379
|
-
try {
|
|
380
|
-
await fs_1.promises.access(filePath);
|
|
381
|
-
const content = await fs_1.promises.readFile(filePath, 'utf-8');
|
|
382
|
-
const fileLines = content.split('\n').filter((line) => line.trim() !== '');
|
|
383
|
-
return fileLines.slice(-linesToRead);
|
|
384
|
-
}
|
|
385
|
-
catch (error) {
|
|
386
|
-
// Log file might not exist or not be readable, return empty
|
|
387
|
-
return [];
|
|
388
|
-
}
|
|
389
|
-
};
|
|
390
|
-
const outLogs = await readFileLastLines(out_file);
|
|
391
|
-
const errLogs = await readFileLastLines(error_file);
|
|
392
|
-
outLogs.forEach(log => finalMessages.push(log));
|
|
393
|
-
if (errLogs.length > 0 && outLogs.length > 0) {
|
|
394
|
-
finalMessages.push('--- STDERR ---'); // Separator
|
|
395
|
-
}
|
|
396
|
-
errLogs.forEach(log => finalMessages.push(log));
|
|
397
|
-
return finalMessages;
|
|
398
|
-
}
|
|
399
|
-
async save() {
|
|
400
|
-
await this.saveConfig();
|
|
401
|
-
}
|
|
402
|
-
async load() {
|
|
403
|
-
await this.loadConfig();
|
|
404
|
-
}
|
|
405
|
-
async stopAll() {
|
|
406
|
-
const promises = Array.from(this.processes.keys()).map(id => this.stop(id));
|
|
407
|
-
await Promise.all(promises);
|
|
408
|
-
}
|
|
409
|
-
async restartAll() {
|
|
410
|
-
const promises = Array.from(this.processes.keys()).map(id => this.restart(id));
|
|
411
|
-
await Promise.all(promises);
|
|
412
|
-
}
|
|
413
|
-
async deleteAll() {
|
|
414
|
-
await this.stopAll();
|
|
415
|
-
this.processes.clear();
|
|
416
|
-
await this.saveConfig();
|
|
417
|
-
}
|
|
418
|
-
getProcess(id) {
|
|
419
|
-
return this.processes.get(id);
|
|
420
|
-
}
|
|
421
|
-
async dispose() {
|
|
422
|
-
this.stopMonitoring();
|
|
423
|
-
this.stopAll();
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
exports.ProcessManager = ProcessManager;
|
package/dist/src/utils.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.formatDuration = void 0;
|
|
4
|
-
function formatDuration(ms) {
|
|
5
|
-
if (ms < 1000) {
|
|
6
|
-
return `${ms}ms`;
|
|
7
|
-
}
|
|
8
|
-
return `${(ms / 1000).toFixed(2)}s`;
|
|
9
|
-
}
|
|
10
|
-
exports.formatDuration = formatDuration;
|
package/dist/src/watcher.js
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.FileWatcher = void 0;
|
|
30
|
-
// src/watcher.ts - File watcher for development (nodemon functionality)
|
|
31
|
-
const fs = __importStar(require("fs"));
|
|
32
|
-
const path = __importStar(require("path"));
|
|
33
|
-
const events_1 = require("events");
|
|
34
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
35
|
-
const logger_1 = __importDefault(require("./logger"));
|
|
36
|
-
class FileWatcher extends events_1.EventEmitter {
|
|
37
|
-
constructor(options) {
|
|
38
|
-
super();
|
|
39
|
-
this.watchers = [];
|
|
40
|
-
this.watchedFiles = new Set();
|
|
41
|
-
this.debounceTimer = null;
|
|
42
|
-
this.isWatching = false;
|
|
43
|
-
this.ignorePatterns = [];
|
|
44
|
-
// Initialize with a copy of user-provided options.
|
|
45
|
-
const processedOptions = { ...options };
|
|
46
|
-
// Apply defaults if properties were not set in the provided 'options'.
|
|
47
|
-
if (processedOptions.watch === undefined) {
|
|
48
|
-
processedOptions.watch = ['./'];
|
|
49
|
-
}
|
|
50
|
-
if (processedOptions.ignore === undefined) {
|
|
51
|
-
processedOptions.ignore = [
|
|
52
|
-
'node_modules/**',
|
|
53
|
-
'.git/**',
|
|
54
|
-
'*.log',
|
|
55
|
-
'dist/**',
|
|
56
|
-
'build/**',
|
|
57
|
-
'coverage/**',
|
|
58
|
-
'.nyc_output/**',
|
|
59
|
-
'*.tmp',
|
|
60
|
-
'*.temp'
|
|
61
|
-
];
|
|
62
|
-
}
|
|
63
|
-
if (processedOptions.ext === undefined) {
|
|
64
|
-
processedOptions.ext = ['js', 'mjs', 'json', 'ts', 'tsx', 'jsx'];
|
|
65
|
-
}
|
|
66
|
-
if (processedOptions.delay === undefined) {
|
|
67
|
-
processedOptions.delay = 1000;
|
|
68
|
-
}
|
|
69
|
-
if (processedOptions.verbose === undefined) {
|
|
70
|
-
processedOptions.verbose = false;
|
|
71
|
-
}
|
|
72
|
-
if (processedOptions.legacyWatch === undefined) {
|
|
73
|
-
processedOptions.legacyWatch = false;
|
|
74
|
-
}
|
|
75
|
-
if (processedOptions.pollingInterval === undefined) {
|
|
76
|
-
processedOptions.pollingInterval = 1000;
|
|
77
|
-
}
|
|
78
|
-
// 'cwd' and 'env' are optional and don't have explicit defaults in this setup;
|
|
79
|
-
// they will be taken from 'options' or remain undefined if not provided.
|
|
80
|
-
this.options = processedOptions;
|
|
81
|
-
this.setupIgnorePatterns();
|
|
82
|
-
}
|
|
83
|
-
setupIgnorePatterns() {
|
|
84
|
-
this.ignorePatterns = (this.options.ignore || []).map(pattern => {
|
|
85
|
-
// Convert glob patterns to regex
|
|
86
|
-
const regexPattern = pattern
|
|
87
|
-
.replace(/\*\*/g, '.*')
|
|
88
|
-
.replace(/\*/g, '[^/]*')
|
|
89
|
-
.replace(/\?/g, '[^/]')
|
|
90
|
-
.replace(/\./g, '\\.');
|
|
91
|
-
return new RegExp(regexPattern, 'i');
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
shouldIgnoreFile(filePath) {
|
|
95
|
-
const relativePath = path.relative(process.cwd(), filePath);
|
|
96
|
-
// Check ignore patterns
|
|
97
|
-
for (const pattern of this.ignorePatterns) {
|
|
98
|
-
if (pattern.test(relativePath) || pattern.test(filePath)) {
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// Check file extension
|
|
103
|
-
if (this.options.ext && this.options.ext.length > 0) {
|
|
104
|
-
const ext = path.extname(filePath).slice(1);
|
|
105
|
-
if (!this.options.ext.includes(ext)) {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
async isValidFile(filePath) {
|
|
112
|
-
try {
|
|
113
|
-
const stats = await fs.promises.stat(filePath);
|
|
114
|
-
return stats.isFile();
|
|
115
|
-
}
|
|
116
|
-
catch (_a) {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
async watchDirectory(dirPath) {
|
|
121
|
-
try {
|
|
122
|
-
const absolutePath = path.resolve(dirPath);
|
|
123
|
-
if (this.options.verbose) {
|
|
124
|
-
logger_1.default.printLine(`Watching directory: ${chalk_1.default.cyan(absolutePath)}`, 'info');
|
|
125
|
-
}
|
|
126
|
-
const watcher = fs.watch(absolutePath, { recursive: true }, async (eventType, filename) => {
|
|
127
|
-
if (!filename)
|
|
128
|
-
return;
|
|
129
|
-
const fullPath = path.join(absolutePath, filename);
|
|
130
|
-
if (this.shouldIgnoreFile(fullPath)) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (!(await this.isValidFile(fullPath))) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
this.handleFileChange(fullPath, eventType);
|
|
137
|
-
});
|
|
138
|
-
this.watchers.push(watcher);
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
if (this.options.verbose) {
|
|
142
|
-
logger_1.default.printLine(`Failed to watch directory ${dirPath}: ${error.message}`, 'warn');
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
async watchFile(filePath) {
|
|
147
|
-
try {
|
|
148
|
-
const absolutePath = path.resolve(filePath);
|
|
149
|
-
if (this.shouldIgnoreFile(absolutePath)) {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
if (!(await this.isValidFile(absolutePath))) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
if (this.options.verbose) {
|
|
156
|
-
logger_1.default.printLine(`Watching file: ${chalk_1.default.cyan(absolutePath)}`, 'info');
|
|
157
|
-
}
|
|
158
|
-
const watcher = fs.watch(absolutePath, (eventType) => {
|
|
159
|
-
this.handleFileChange(absolutePath, eventType);
|
|
160
|
-
});
|
|
161
|
-
this.watchers.push(watcher);
|
|
162
|
-
this.watchedFiles.add(absolutePath);
|
|
163
|
-
}
|
|
164
|
-
catch (error) {
|
|
165
|
-
if (this.options.verbose) {
|
|
166
|
-
logger_1.default.printLine(`Failed to watch file ${filePath}: ${error.message}`, 'warn');
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
handleFileChange(filePath, eventType) {
|
|
171
|
-
if (this.options.verbose) {
|
|
172
|
-
logger_1.default.printLine(`File ${eventType}: ${chalk_1.default.yellow(path.relative(process.cwd(), filePath))}`, 'info');
|
|
173
|
-
}
|
|
174
|
-
// Debounce file changes
|
|
175
|
-
if (this.debounceTimer) {
|
|
176
|
-
clearTimeout(this.debounceTimer);
|
|
177
|
-
}
|
|
178
|
-
this.debounceTimer = setTimeout(() => {
|
|
179
|
-
this.emit('change', {
|
|
180
|
-
path: filePath,
|
|
181
|
-
event: eventType,
|
|
182
|
-
relativePath: path.relative(process.cwd(), filePath)
|
|
183
|
-
});
|
|
184
|
-
}, this.options.delay);
|
|
185
|
-
}
|
|
186
|
-
async start() {
|
|
187
|
-
if (this.isWatching) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
this.isWatching = true;
|
|
191
|
-
logger_1.default.printLine('Starting file watcher...', 'info');
|
|
192
|
-
for (const watchPath of this.options.watch) {
|
|
193
|
-
const absolutePath = path.resolve(watchPath);
|
|
194
|
-
try {
|
|
195
|
-
const stats = await fs.promises.stat(absolutePath);
|
|
196
|
-
if (stats.isDirectory()) {
|
|
197
|
-
await this.watchDirectory(absolutePath);
|
|
198
|
-
}
|
|
199
|
-
else if (stats.isFile()) {
|
|
200
|
-
await this.watchFile(absolutePath);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
catch (error) {
|
|
204
|
-
logger_1.default.printLine(`Cannot watch ${watchPath}: ${error.message}`, 'warn');
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
const watchedCount = this.watchers.length;
|
|
208
|
-
logger_1.default.printLine(`File watcher started. Monitoring ${chalk_1.default.green(watchedCount)} locations`, 'info');
|
|
209
|
-
if (this.options.ext && this.options.ext.length > 0) {
|
|
210
|
-
logger_1.default.printLine(`Watching extensions: ${chalk_1.default.cyan(this.options.ext.join(', '))}`, 'info');
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
stop() {
|
|
214
|
-
if (!this.isWatching) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
logger_1.default.printLine('Stopping file watcher...', 'info');
|
|
218
|
-
this.watchers.forEach(watcher => {
|
|
219
|
-
try {
|
|
220
|
-
watcher.close();
|
|
221
|
-
}
|
|
222
|
-
catch (error) {
|
|
223
|
-
// Ignore errors when closing watchers
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
this.watchers = [];
|
|
227
|
-
this.watchedFiles.clear();
|
|
228
|
-
this.isWatching = false;
|
|
229
|
-
if (this.debounceTimer) {
|
|
230
|
-
clearTimeout(this.debounceTimer);
|
|
231
|
-
this.debounceTimer = null;
|
|
232
|
-
}
|
|
233
|
-
logger_1.default.printLine('File watcher stopped', 'info');
|
|
234
|
-
}
|
|
235
|
-
isActive() {
|
|
236
|
-
return this.isWatching;
|
|
237
|
-
}
|
|
238
|
-
getWatchedFiles() {
|
|
239
|
-
return Array.from(this.watchedFiles);
|
|
240
|
-
}
|
|
241
|
-
getOptions() {
|
|
242
|
-
return { ...this.options };
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
exports.FileWatcher = FileWatcher;
|