neex 0.1.3 → 0.1.4
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/dist/src/cli.js +492 -6
- package/dist/src/dev-runner.js +203 -0
- package/dist/src/index.js +3 -1
- package/dist/src/logger.js +36 -0
- package/dist/src/process-manager.js +330 -0
- package/dist/src/runner.js +112 -78
- package/dist/src/utils.js +10 -0
- package/dist/src/watcher.js +245 -0
- package/package.json +1 -1
package/dist/src/runner.js
CHANGED
|
@@ -35,6 +35,7 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
35
35
|
const logger_1 = __importDefault(require("./logger"));
|
|
36
36
|
const p_map_1 = __importDefault(require("p-map"));
|
|
37
37
|
const npm_run_path_1 = __importDefault(require("npm-run-path"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
38
39
|
class Runner {
|
|
39
40
|
constructor(options) {
|
|
40
41
|
this.activeProcesses = new Map();
|
|
@@ -44,6 +45,41 @@ class Runner {
|
|
|
44
45
|
this.options = options;
|
|
45
46
|
this.activeProcesses = new Map();
|
|
46
47
|
}
|
|
48
|
+
async expandWildcardCommands(commands) {
|
|
49
|
+
const expandedCommands = [];
|
|
50
|
+
let packageJson;
|
|
51
|
+
try {
|
|
52
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
53
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
54
|
+
const packageJsonContent = await fsPromises.readFile(packageJsonPath, 'utf-8');
|
|
55
|
+
packageJson = JSON.parse(packageJsonContent);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
logger_1.default.printLine(`Could not read or parse package.json: ${error.message}`, 'warn');
|
|
60
|
+
packageJson = { scripts: {} };
|
|
61
|
+
}
|
|
62
|
+
for (const command of commands) {
|
|
63
|
+
if (command.includes('*') && packageJson && packageJson.scripts) {
|
|
64
|
+
const pattern = new RegExp(`^${command.replace(/\*/g, '.*')}$`);
|
|
65
|
+
let foundMatch = false;
|
|
66
|
+
for (const scriptName in packageJson.scripts) {
|
|
67
|
+
if (pattern.test(scriptName)) {
|
|
68
|
+
expandedCommands.push(scriptName); // Or packageJson.scripts[scriptName] if you want the script value
|
|
69
|
+
foundMatch = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!foundMatch) {
|
|
73
|
+
logger_1.default.printLine(`No scripts found in package.json matching wildcard: ${command}`, 'warn');
|
|
74
|
+
expandedCommands.push(command); // Add original command if no match
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
expandedCommands.push(command);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return expandedCommands;
|
|
82
|
+
}
|
|
47
83
|
async resolveScriptAndCwd(scriptNameOrCommand, baseDir) {
|
|
48
84
|
try {
|
|
49
85
|
const packageJsonPath = path.join(baseDir, 'package.json');
|
|
@@ -103,7 +139,7 @@ class Runner {
|
|
|
103
139
|
// Update server info
|
|
104
140
|
this.serverInfo.set(command, serverInfo);
|
|
105
141
|
}
|
|
106
|
-
async runCommand(originalCommand) {
|
|
142
|
+
async runCommand(originalCommand, currentRetry = 0) {
|
|
107
143
|
const { executableCommand: command, executionCwd: cwd } = await this.resolveScriptAndCwd(originalCommand, process.cwd());
|
|
108
144
|
const startTime = new Date();
|
|
109
145
|
const result = {
|
|
@@ -117,7 +153,8 @@ class Runner {
|
|
|
117
153
|
if (this.options.printOutput) {
|
|
118
154
|
logger_1.default.printStart(originalCommand);
|
|
119
155
|
}
|
|
120
|
-
return new Promise((resolve) => {
|
|
156
|
+
return new Promise(async (resolve) => {
|
|
157
|
+
var _a, _b;
|
|
121
158
|
const [cmd, ...args] = command.split(' ');
|
|
122
159
|
const env = {
|
|
123
160
|
...process.env,
|
|
@@ -140,96 +177,93 @@ class Runner {
|
|
|
140
177
|
startTime: new Date()
|
|
141
178
|
});
|
|
142
179
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (!this.options.groupOutput) {
|
|
177
|
-
logger_1.default.printBuffer(originalCommand);
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
proc.on('close', (code) => {
|
|
182
|
-
const endTime = new Date();
|
|
183
|
-
const duration = endTime.getTime() - startTime.getTime();
|
|
184
|
-
result.endTime = endTime;
|
|
185
|
-
result.duration = duration;
|
|
186
|
-
result.code = code;
|
|
187
|
-
result.success = code === 0;
|
|
180
|
+
(_a = proc.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
|
|
181
|
+
const output = {
|
|
182
|
+
command: originalCommand,
|
|
183
|
+
type: 'stdout',
|
|
184
|
+
data: data.toString(),
|
|
185
|
+
timestamp: new Date()
|
|
186
|
+
};
|
|
187
|
+
if (this.options.isServerMode)
|
|
188
|
+
this.detectServerInfo(originalCommand, data.toString());
|
|
189
|
+
if (result.output)
|
|
190
|
+
result.output.push(output);
|
|
191
|
+
logger_1.default.bufferOutput(output);
|
|
192
|
+
if (!this.options.groupOutput && this.options.printOutput)
|
|
193
|
+
logger_1.default.printBuffer(originalCommand);
|
|
194
|
+
});
|
|
195
|
+
(_b = proc.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
|
|
196
|
+
const output = {
|
|
197
|
+
command: originalCommand,
|
|
198
|
+
type: 'stderr',
|
|
199
|
+
data: data.toString(),
|
|
200
|
+
timestamp: new Date()
|
|
201
|
+
};
|
|
202
|
+
if (result.output)
|
|
203
|
+
result.output.push(output);
|
|
204
|
+
logger_1.default.bufferOutput(output);
|
|
205
|
+
if (!this.options.groupOutput && this.options.printOutput)
|
|
206
|
+
logger_1.default.printBuffer(originalCommand);
|
|
207
|
+
});
|
|
208
|
+
proc.on('error', async (err) => {
|
|
209
|
+
result.endTime = new Date();
|
|
210
|
+
result.error = err;
|
|
211
|
+
result.success = false;
|
|
212
|
+
result.duration = result.endTime.getTime() - startTime.getTime();
|
|
188
213
|
this.activeProcesses.delete(originalCommand);
|
|
189
214
|
if (this.options.isServerMode) {
|
|
190
215
|
const serverInfo = this.serverInfo.get(originalCommand);
|
|
191
216
|
if (serverInfo) {
|
|
192
|
-
serverInfo.status =
|
|
217
|
+
serverInfo.status = 'error';
|
|
193
218
|
this.serverInfo.set(originalCommand, serverInfo);
|
|
194
219
|
}
|
|
195
|
-
|
|
196
|
-
if (code !== 0) {
|
|
197
|
-
logger_1.default.printLine(`Server ${originalCommand} crashed with code ${code}`, 'error');
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
// Print grouped output at the end if enabled
|
|
201
|
-
if (this.options.groupOutput && result.output && result.output.length > 0) {
|
|
202
|
-
logger_1.default.printBuffer(originalCommand);
|
|
220
|
+
logger_1.default.printLine(`Command "${originalCommand}" failed to start: ${err.message}`, 'error');
|
|
203
221
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
222
|
+
logger_1.default.printBuffer(originalCommand);
|
|
223
|
+
if (this.options.printOutput)
|
|
224
|
+
logger_1.default.printEnd(result, this.options.minimalOutput);
|
|
225
|
+
if (this.options.retry && this.options.retry > 0 && currentRetry < this.options.retry) {
|
|
226
|
+
logger_1.default.printLine(`Command "${originalCommand}" failed with error. Retrying (${currentRetry + 1}/${this.options.retry})...`, 'warn');
|
|
227
|
+
if (this.options.retryDelay && this.options.retryDelay > 0) {
|
|
228
|
+
await new Promise(res => setTimeout(res, this.options.retryDelay));
|
|
210
229
|
}
|
|
230
|
+
logger_1.default.clearBuffer(originalCommand);
|
|
231
|
+
resolve(this.runCommand(originalCommand, currentRetry + 1));
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
resolve(result);
|
|
211
235
|
}
|
|
212
|
-
resolve(result);
|
|
213
236
|
});
|
|
214
|
-
proc.on('
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
result.endTime =
|
|
218
|
-
result.duration =
|
|
219
|
-
result.error = error;
|
|
220
|
-
result.success = false;
|
|
237
|
+
proc.on('close', async (code) => {
|
|
238
|
+
result.code = code;
|
|
239
|
+
result.success = code === 0;
|
|
240
|
+
result.endTime = new Date();
|
|
241
|
+
result.duration = result.endTime.getTime() - startTime.getTime();
|
|
221
242
|
this.activeProcesses.delete(originalCommand);
|
|
222
243
|
if (this.options.isServerMode) {
|
|
223
244
|
const serverInfo = this.serverInfo.get(originalCommand);
|
|
224
245
|
if (serverInfo) {
|
|
225
|
-
serverInfo.status = 'error';
|
|
246
|
+
serverInfo.status = code === 0 ? 'stopped' : 'error';
|
|
226
247
|
this.serverInfo.set(originalCommand, serverInfo);
|
|
227
248
|
}
|
|
249
|
+
if (code !== 0) {
|
|
250
|
+
logger_1.default.printLine(`Server "${originalCommand}" exited with code ${code}`, 'error');
|
|
251
|
+
}
|
|
228
252
|
}
|
|
229
|
-
|
|
230
|
-
|
|
253
|
+
logger_1.default.printBuffer(originalCommand);
|
|
254
|
+
if (this.options.printOutput)
|
|
255
|
+
logger_1.default.printEnd(result, this.options.minimalOutput);
|
|
256
|
+
if (!result.success && this.options.retry && this.options.retry > 0 && currentRetry < this.options.retry) {
|
|
257
|
+
logger_1.default.printLine(`Command "${originalCommand}" failed with code ${code}. Retrying (${currentRetry + 1}/${this.options.retry})...`, 'warn');
|
|
258
|
+
if (this.options.retryDelay && this.options.retryDelay > 0) {
|
|
259
|
+
await new Promise(res => setTimeout(res, this.options.retryDelay));
|
|
260
|
+
}
|
|
261
|
+
logger_1.default.clearBuffer(originalCommand);
|
|
262
|
+
resolve(this.runCommand(originalCommand, currentRetry + 1));
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
resolve(result);
|
|
231
266
|
}
|
|
232
|
-
resolve(result);
|
|
233
267
|
});
|
|
234
268
|
});
|
|
235
269
|
}
|
|
@@ -238,7 +272,6 @@ class Runner {
|
|
|
238
272
|
for (const cmd of commands) {
|
|
239
273
|
const result = await this.runCommand(cmd);
|
|
240
274
|
results.push(result);
|
|
241
|
-
// Stop on error if enabled
|
|
242
275
|
if (!result.success && this.options.stopOnError) {
|
|
243
276
|
break;
|
|
244
277
|
}
|
|
@@ -257,18 +290,19 @@ class Runner {
|
|
|
257
290
|
});
|
|
258
291
|
}
|
|
259
292
|
catch (error) {
|
|
260
|
-
// If pMap stops due to stopOnError
|
|
261
293
|
if (this.options.isServerMode) {
|
|
262
294
|
logger_1.default.printLine('One or more servers failed to start. Stopping all servers.', 'error');
|
|
263
295
|
}
|
|
264
296
|
return [];
|
|
265
297
|
}
|
|
266
298
|
}
|
|
267
|
-
async run(
|
|
299
|
+
async run(initialCommands) {
|
|
300
|
+
const commands = await this.expandWildcardCommands(initialCommands);
|
|
268
301
|
if (commands.length === 0) {
|
|
302
|
+
logger_1.default.printLine('No commands to run after wildcard expansion.', 'warn');
|
|
269
303
|
return [];
|
|
270
304
|
}
|
|
271
|
-
//
|
|
305
|
+
// Initialize logger with the final list of commands
|
|
272
306
|
logger_1.default.setCommands(commands);
|
|
273
307
|
// Run in parallel or sequential mode
|
|
274
308
|
if (this.options.parallel) {
|
|
@@ -0,0 +1,10 @@
|
|
|
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;
|
|
@@ -0,0 +1,245 @@
|
|
|
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;
|