@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.
Files changed (2) hide show
  1. package/index.js +197 -0
  2. 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
+ }