@push.rocks/smartchok
Version:
A cross-runtime file watcher with glob pattern support for Node.js, Deno, and Bun.
197 lines • 15.2 kB
JavaScript
import * as plugins from './smartwatch.plugins.js';
import { Stringmap } from '@push.rocks/lik';
import { createWatcher } from './watchers/index.js';
/**
* Smartwatch allows easy watching of files
* Uses native file watching APIs (Node.js fs.watch, Deno.watchFs) for cross-runtime support
*/
export class Smartwatch {
/**
* constructor of class Smartwatch
*/
constructor(watchArrayArg) {
this.watchStringmap = new Stringmap();
this.status = 'idle';
this.watcher = null;
this.globPatterns = [];
this.globMatchers = new Map();
this.watchingDeferred = plugins.smartpromise.defer();
// Event subjects for each event type
this.eventSubjects = new Map();
this.watchStringmap.addStringArray(watchArrayArg);
// Initialize subjects for each event type
const eventTypes = ['add', 'addDir', 'change', 'error', 'unlink', 'unlinkDir', 'ready', 'raw'];
for (const eventType of eventTypes) {
this.eventSubjects.set(eventType, new plugins.smartrx.rxjs.Subject());
}
}
getGlobBase(globPattern) {
// Characters that mark the beginning of a glob pattern
const globChars = ['*', '?', '[', ']', '{', '}'];
// Find the index of the first glob character
const firstGlobCharIndex = globPattern.split('').findIndex((char) => globChars.includes(char));
// If no glob characters are found, return the entire string
if (firstGlobCharIndex === -1) {
return globPattern;
}
// Extract the substring up to the first glob character
const basePathPortion = globPattern.substring(0, firstGlobCharIndex);
// Find the last slash before the glob pattern starts
const lastSlashIndex = basePathPortion.lastIndexOf('/');
// If there is no slash, return the basePathPortion as is
if (lastSlashIndex === -1) {
return basePathPortion;
}
// Return the base path up to and including the last slash
return basePathPortion.substring(0, lastSlashIndex + 1);
}
/**
* adds files to the list of watched files
*/
add(pathArrayArg) {
this.watchStringmap.addStringArray(pathArrayArg);
}
/**
* removes files from the list of watched files
*/
remove(pathArg) {
this.watchStringmap.removeString(pathArg);
}
/**
* gets an observable for a certain event
*/
getObservableFor(fsEvent) {
const done = plugins.smartpromise.defer();
this.watchingDeferred.promise.then(() => {
const subject = this.eventSubjects.get(fsEvent);
if (subject) {
done.resolve(subject.asObservable());
}
else {
done.reject(new Error(`Unknown event type: ${fsEvent}`));
}
});
return done.promise;
}
/**
* starts the watcher
* @returns Promise<void>
*/
async start() {
this.status = 'starting';
// Store original glob patterns and create matchers
this.globPatterns = this.watchStringmap.getStringArray();
const basePaths = new Set();
this.globPatterns.forEach((pattern) => {
const basePath = this.getGlobBase(pattern);
basePaths.add(basePath);
// Create a picomatch matcher for each glob pattern
const matcher = plugins.picomatch(pattern, {
dot: true,
basename: false
});
this.globMatchers.set(pattern, matcher);
});
// Convert Set to Array for the watcher
const watchPaths = Array.from(basePaths);
console.log('Base paths to watch:', watchPaths);
// Create the platform-appropriate watcher
this.watcher = await createWatcher({
basePaths: watchPaths,
depth: 4,
followSymlinks: false,
stabilityThreshold: 300,
pollInterval: 100
});
// Subscribe to watcher events and dispatch to appropriate subjects
this.watcher.events$.subscribe((event) => {
this.handleWatchEvent(event);
});
// Start the watcher
await this.watcher.start();
this.status = 'watching';
this.watchingDeferred.resolve();
}
/**
* Handle events from the native watcher
*/
handleWatchEvent(event) {
console.log(`[Smartwatch] Received event: ${event.type} - ${event.path}`);
// Handle ready event
if (event.type === 'ready') {
const subject = this.eventSubjects.get('ready');
if (subject) {
subject.next(['', undefined]);
}
return;
}
// Handle error event
if (event.type === 'error') {
const subject = this.eventSubjects.get('error');
if (subject) {
subject.next([event.error?.message || 'Unknown error', undefined]);
}
return;
}
// Filter file/directory events by glob patterns
const shouldWatch = this.shouldWatchPath(event.path);
console.log(`[Smartwatch] shouldWatchPath(${event.path}): ${shouldWatch}`);
if (!shouldWatch) {
return;
}
const subject = this.eventSubjects.get(event.type);
if (subject) {
console.log(`[Smartwatch] Emitting ${event.type} for ${event.path}`);
subject.next([event.path, event.stats]);
}
}
/**
* stop the watcher process if watching
*/
async stop() {
const closeWatcher = async () => {
if (this.watcher) {
await this.watcher.stop();
this.watcher = null;
}
};
if (this.status === 'watching') {
console.log('closing while watching');
await closeWatcher();
}
else if (this.status === 'starting') {
await this.watchingDeferred.promise;
await closeWatcher();
}
this.status = 'idle';
}
/**
* Checks if a path should be watched based on glob patterns
*/
shouldWatchPath(filePath) {
// Normalize the path - remove leading ./ if present
let normalizedPath = filePath.replace(/\\/g, '/');
if (normalizedPath.startsWith('./')) {
normalizedPath = normalizedPath.substring(2);
}
// Check if the path matches any of our glob patterns
for (const [pattern, matcher] of this.globMatchers) {
// Also normalize the pattern for comparison
let normalizedPattern = pattern;
if (normalizedPattern.startsWith('./')) {
normalizedPattern = normalizedPattern.substring(2);
}
// Try matching with both the original pattern and normalized
if (matcher(normalizedPath) || matcher(filePath)) {
return true;
}
// Also try matching without the leading path
const withoutLeading = 'test/' + normalizedPath.split('test/').slice(1).join('test/');
if (matcher(withoutLeading)) {
return true;
}
}
return false;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnR3YXRjaC5jbGFzc2VzLnNtYXJ0d2F0Y2guanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHdhdGNoLmNsYXNzZXMuc21hcnR3YXRjaC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHlCQUF5QixDQUFDO0FBQ25ELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUM1QyxPQUFPLEVBQUUsYUFBYSxFQUF5RCxNQUFNLHFCQUFxQixDQUFDO0FBYTNHOzs7R0FHRztBQUNILE1BQU0sT0FBTyxVQUFVO0lBV3JCOztPQUVHO0lBQ0gsWUFBWSxhQUF1QjtRQWI1QixtQkFBYyxHQUFHLElBQUksU0FBUyxFQUFFLENBQUM7UUFDakMsV0FBTSxHQUFzQixNQUFNLENBQUM7UUFDbEMsWUFBTyxHQUFvQixJQUFJLENBQUM7UUFDaEMsaUJBQVksR0FBYSxFQUFFLENBQUM7UUFDNUIsaUJBQVksR0FBMkMsSUFBSSxHQUFHLEVBQUUsQ0FBQztRQUNqRSxxQkFBZ0IsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLEtBQUssRUFBUSxDQUFDO1FBRTlELHFDQUFxQztRQUM3QixrQkFBYSxHQUF3RixJQUFJLEdBQUcsRUFBRSxDQUFDO1FBTXJILElBQUksQ0FBQyxjQUFjLENBQUMsY0FBYyxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBRWxELDBDQUEwQztRQUMxQyxNQUFNLFVBQVUsR0FBZSxDQUFDLEtBQUssRUFBRSxRQUFRLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsV0FBVyxFQUFFLE9BQU8sRUFBRSxLQUFLLENBQUMsQ0FBQztRQUMzRyxLQUFLLE1BQU0sU0FBUyxJQUFJLFVBQVUsRUFBRSxDQUFDO1lBQ25DLElBQUksQ0FBQyxhQUFhLENBQUMsR0FBRyxDQUFDLFNBQVMsRUFBRSxJQUFJLE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxD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