@chengyixu/filewatch 1.0.0
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/index.js +197 -0
- package/package.json +33 -0
package/index.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { spawn, execSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
const VERSION = '1.0.0';
|
|
9
|
+
const NAME = '@chengyixu/filewatch';
|
|
10
|
+
|
|
11
|
+
// Parse CLI args
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
|
|
14
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
15
|
+
console.log(`
|
|
16
|
+
${NAME} v${VERSION}
|
|
17
|
+
|
|
18
|
+
Watch files/directories and run a command on change. (mini nodemon)
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
filewatch [options] -- <command>
|
|
22
|
+
filewatch [options] <directory> -- <command>
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
--version, -v Show version
|
|
26
|
+
--help, -h Show this help
|
|
27
|
+
--dir, -d <path> Directory or file to watch (default: .)
|
|
28
|
+
--ext, -e <exts> Comma-separated extensions to watch (e.g. js,ts,json)
|
|
29
|
+
--ignore, -i <pat> Ignore pattern (can repeat)
|
|
30
|
+
--debounce <ms> Debounce delay in ms (default: 300)
|
|
31
|
+
--clear Clear terminal before each run
|
|
32
|
+
--quiet, -q Suppress startup message
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
filewatch -- node server.js
|
|
36
|
+
filewatch -d src -e js,ts -- npm test
|
|
37
|
+
filewatch -d . -i node_modules -i .git -- make build
|
|
38
|
+
filewatch --debounce 500 --clear -- python main.py
|
|
39
|
+
`);
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
44
|
+
// Filter out --version from args in case it's combined with other flags
|
|
45
|
+
const isVersionOnly = args.length === 1 || (args.length === 2 && (args.includes('--version') || args.includes('-v')));
|
|
46
|
+
if (isVersionOnly || args.indexOf('--version') === 0 || args.indexOf('-v') === 0) {
|
|
47
|
+
console.log(VERSION);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Parse structured args
|
|
53
|
+
let watchDir = '.';
|
|
54
|
+
let extensions = [];
|
|
55
|
+
let ignorePatterns = [];
|
|
56
|
+
let debounceMs = 300;
|
|
57
|
+
let clearScreen = false;
|
|
58
|
+
let quiet = false;
|
|
59
|
+
let cmdParts = null;
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < args.length; i++) {
|
|
62
|
+
const arg = args[i];
|
|
63
|
+
if (arg === '--') {
|
|
64
|
+
cmdParts = args.slice(i + 1);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
if (arg === '--dir' || arg === '-d') {
|
|
68
|
+
watchDir = args[++i];
|
|
69
|
+
} else if (arg === '--ext' || arg === '-e') {
|
|
70
|
+
extensions = args[++i].split(',').map(e => e.trim().replace(/^\./, ''));
|
|
71
|
+
} else if (arg === '--ignore' || arg === '-i') {
|
|
72
|
+
ignorePatterns.push(args[++i]);
|
|
73
|
+
} else if (arg === '--debounce') {
|
|
74
|
+
debounceMs = parseInt(args[++i], 10) || 300;
|
|
75
|
+
} else if (arg === '--clear') {
|
|
76
|
+
clearScreen = true;
|
|
77
|
+
} else if (arg === '--quiet' || arg === '-q') {
|
|
78
|
+
quiet = true;
|
|
79
|
+
} else if (!arg.startsWith('-')) {
|
|
80
|
+
// Positional arg: treat as directory if no --dir given
|
|
81
|
+
if (watchDir === '.' && i < args.indexOf('--')) {
|
|
82
|
+
watchDir = arg;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!cmdParts || cmdParts.length === 0) {
|
|
88
|
+
console.error('Error: No command specified. Use -- before the command.');
|
|
89
|
+
console.error('Example: filewatch -- node server.js');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const cmd = cmdParts[0];
|
|
94
|
+
const cmdArgs = cmdParts.slice(1);
|
|
95
|
+
|
|
96
|
+
// Resolve watch directory
|
|
97
|
+
const watchPath = path.resolve(watchDir);
|
|
98
|
+
if (!fs.existsSync(watchPath)) {
|
|
99
|
+
console.error(`Error: Path not found: ${watchPath}`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const stat = fs.statSync(watchPath);
|
|
104
|
+
const watchIsFile = stat.isFile();
|
|
105
|
+
|
|
106
|
+
// Build ignore set
|
|
107
|
+
const ignoreSet = new Set(ignorePatterns.map(p => path.resolve(p)));
|
|
108
|
+
|
|
109
|
+
function shouldWatch(filePath) {
|
|
110
|
+
if (ignoreSet.has(path.resolve(filePath))) return false;
|
|
111
|
+
// Check if path contains any ignore pattern as substring
|
|
112
|
+
for (const ig of ignorePatterns) {
|
|
113
|
+
if (filePath.includes(ig)) return false;
|
|
114
|
+
}
|
|
115
|
+
if (extensions.length > 0) {
|
|
116
|
+
const ext = path.extname(filePath).replace(/^\./, '');
|
|
117
|
+
if (ext && !extensions.includes(ext)) return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let child = null;
|
|
123
|
+
let debounceTimer = null;
|
|
124
|
+
let running = false;
|
|
125
|
+
|
|
126
|
+
function runCommand() {
|
|
127
|
+
if (running) {
|
|
128
|
+
if (child) {
|
|
129
|
+
child.kill('SIGTERM');
|
|
130
|
+
// Give it a moment to die
|
|
131
|
+
try { child.kill('SIGKILL'); } catch (_) {}
|
|
132
|
+
}
|
|
133
|
+
// Re-schedule
|
|
134
|
+
debounceTimer = setTimeout(runCommand, debounceMs);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
running = true;
|
|
139
|
+
|
|
140
|
+
if (clearScreen) {
|
|
141
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const now = new Date().toLocaleTimeString();
|
|
145
|
+
console.log(`\n[${now}] $ ${cmd} ${cmdArgs.join(' ')}`);
|
|
146
|
+
|
|
147
|
+
child = spawn(cmd, cmdArgs, {
|
|
148
|
+
stdio: 'inherit',
|
|
149
|
+
shell: true
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
child.on('exit', (code) => {
|
|
153
|
+
running = false;
|
|
154
|
+
if (code !== null && code !== 0) {
|
|
155
|
+
console.log(`[filewatch] Command exited with code ${code}`);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
child.on('error', (err) => {
|
|
160
|
+
running = false;
|
|
161
|
+
console.error(`[filewatch] Failed to start: ${err.message}`);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function onChange(eventType, filename) {
|
|
166
|
+
if (!filename) return;
|
|
167
|
+
const fullPath = path.join(watchPath, watchIsFile ? '' : filename);
|
|
168
|
+
if (!shouldWatch(fullPath)) return;
|
|
169
|
+
|
|
170
|
+
clearTimeout(debounceTimer);
|
|
171
|
+
debounceTimer = setTimeout(runCommand, debounceMs);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Initial run
|
|
175
|
+
if (!quiet) {
|
|
176
|
+
console.log(`[filewatch] Watching: ${watchPath}`);
|
|
177
|
+
if (extensions.length) console.log(`[filewatch] Extensions: ${extensions.join(',')}`);
|
|
178
|
+
console.log(`[filewatch] Command: ${cmd} ${cmdArgs.join(' ')}`);
|
|
179
|
+
console.log(`[filewatch] Ready for changes...`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
runCommand();
|
|
183
|
+
|
|
184
|
+
// Start watching
|
|
185
|
+
try {
|
|
186
|
+
if (watchIsFile) {
|
|
187
|
+
fs.watch(watchPath, onChange);
|
|
188
|
+
} else {
|
|
189
|
+
fs.watch(watchPath, { recursive: true }, onChange);
|
|
190
|
+
}
|
|
191
|
+
} catch (err) {
|
|
192
|
+
// Fallback for platforms without recursive support
|
|
193
|
+
console.error(`[filewatch] Warning: ${err.message}`);
|
|
194
|
+
if (!watchIsFile) {
|
|
195
|
+
fs.watch(watchPath, onChange);
|
|
196
|
+
}
|
|
197
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chengyixu/filewatch",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Watch files/directories and run a command on change — a zero-dependency mini-nodemon",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"filewatch": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "node test.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"watch",
|
|
17
|
+
"filewatcher",
|
|
18
|
+
"nodemon",
|
|
19
|
+
"cli",
|
|
20
|
+
"file-watch",
|
|
21
|
+
"dev",
|
|
22
|
+
"zero-dependency"
|
|
23
|
+
],
|
|
24
|
+
"author": "chengyixu",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=14.0.0"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/chengyixu/filewatch"
|
|
32
|
+
}
|
|
33
|
+
}
|