@@ -19,12 +19,12 @@ const FILE_DELETE_TIMEOUT_IN_MS = 500
1919/**
2020 * Event emitted by the file watcher
2121 *
22- * Includes the type of the event, the path of the file that triggered the event and the extension path that contains the file.
23- * path and extensionPath could be the same if the event is at the extension level (create, delete extension)
22+ * Includes the type of the event, the path of the file that triggered the event and the extension handle that owns the file.
23+ * For folder-level events (create, delete), extensionHandle is undefined since the extension may not exist yet.
2424 *
2525 * @typeParam type - The type of the event
2626 * @typeParam path - The path of the file that triggered the event
27- * @typeParam extensionPath - The path of the extension that contains the file
27+ * @typeParam extensionHandle - The unique handle of the extension that owns the file
2828 * @typeParam startTime - The time when the event was triggered
2929 */
3030export interface WatcherEvent {
@@ -37,6 +37,9 @@ export interface WatcherEvent {
3737 | 'extensions_config_updated'
3838 | 'app_config_deleted'
3939 path : string
40+ /** The unique handle of the extension that owns this file. Undefined for folder-level events. */
41+ extensionHandle ?: string
42+ /** The directory path of the extension. Used for folder-level events (create/delete) where no extension handle exists yet. */
4043 extensionPath : string
4144 startTime : StartTime
4245}
@@ -56,7 +59,7 @@ export class FileWatcher {
5659 private watcher ?: FSWatcher
5760 private readonly debouncedEmit : ( ) => void
5861 private readonly ignored : { [ key : string ] : ignore . Ignore | undefined } = { }
59- // Map of file paths to the extensions that watch them
62+ // Map of file paths to the extension handles that watch them
6063 private readonly extensionWatchedFiles = new Map < string , Set < string > > ( )
6164
6265 constructor (
@@ -115,15 +118,6 @@ export class FileWatcher {
115118 // Create new watcher
116119 const { default : chokidar } = await import ( 'chokidar' )
117120 this . watcher = chokidar . watch ( watchPaths , {
118- ignored : [
119- '**/node_modules/**' ,
120- '**/.git/**' ,
121- '**/*.test.*' ,
122- '**/dist/**' ,
123- '**/*.swp' ,
124- '**/generated/**' ,
125- '**/.gitignore' ,
126- ] ,
127121 persistent : true ,
128122 ignoreInitial : true ,
129123 } )
@@ -155,22 +149,21 @@ export class FileWatcher {
155149 private getAllWatchedFiles ( ) : string [ ] {
156150 this . extensionWatchedFiles . clear ( )
157151
158- const extensionResults = this . app . nonConfigExtensions . map ( ( extension ) => ( {
152+ const extensionResults = this . app . realExtensions . map ( ( extension ) => ( {
159153 extension,
160154 watchedFiles : extension . watchedFiles ( ) ,
161155 } ) )
162156
163157 const allFiles = new Set < string > ( )
164158 for ( const { extension, watchedFiles} of extensionResults ) {
165- const extensionDir = normalizePath ( extension . directory )
166159 for ( const file of watchedFiles ) {
167160 const normalizedPath = normalizePath ( file )
168161 allFiles . add ( normalizedPath )
169162
170- // Track which extensions watch this file
171- const extensionsSet = this . extensionWatchedFiles . get ( normalizedPath ) ?? new Set ( )
172- extensionsSet . add ( extensionDir )
173- this . extensionWatchedFiles . set ( normalizedPath , extensionsSet )
163+ // Track which extension handles watch this file
164+ const handlesSet = this . extensionWatchedFiles . get ( normalizedPath ) ?? new Set ( )
165+ handlesSet . add ( extension . handle )
166+ this . extensionWatchedFiles . set ( normalizedPath , handlesSet )
174167 }
175168 }
176169
@@ -204,13 +197,13 @@ export class FileWatcher {
204197 }
205198
206199 // If the event is already in the list, don't push it again
207- // Check path, type, AND extensionPath to properly handle shared files
200+ // Check path, type, AND extensionHandle to properly handle shared files
208201 if (
209202 this . currentEvents . some (
210203 ( extEvent ) =>
211204 extEvent . path === event . path &&
212205 extEvent . type === event . type &&
213- extEvent . extensionPath === event . extensionPath ,
206+ extEvent . extensionHandle === event . extensionHandle ,
214207 )
215208 )
216209 return
@@ -229,15 +222,17 @@ export class FileWatcher {
229222 private shouldIgnoreEvent ( event : WatcherEvent ) {
230223 if ( event . type === 'extension_folder_deleted' || event . type === 'extension_folder_created' ) return false
231224
232- const extension = this . app . realExtensions . find ( ( ext ) => ext . directory === event . extensionPath )
225+ const extension = event . extensionHandle
226+ ? this . app . realExtensions . find ( ( ext ) => ext . handle === event . extensionHandle )
227+ : undefined
233228 const watchPaths = extension ?. watchedFiles ( )
234- const ignoreInstance = this . ignored [ event . extensionPath ]
229+ const ignoreInstance = extension ? this . ignored [ extension . directory ] : undefined
235230
236231 if ( watchPaths ) {
237232 const isAValidWatchedPath = watchPaths . some ( ( pattern ) => matchGlob ( event . path , pattern ) )
238233 return ! isAValidWatchedPath
239234 } else if ( ignoreInstance ) {
240- const relative = relativePath ( event . extensionPath , event . path )
235+ const relative = relativePath ( extension ! . directory , event . path )
241236 return ignoreInstance . ignores ( relative )
242237 }
243238
@@ -255,8 +250,8 @@ export class FileWatcher {
255250 if ( isConfigAppPath ) {
256251 this . handleEventForExtension ( event , path , this . app . directory , startTime , false )
257252 } else {
258- const affectedExtensions = this . extensionWatchedFiles . get ( normalizedPath )
259- const isUnknownExtension = affectedExtensions === undefined || affectedExtensions . size === 0
253+ const affectedHandles = this . extensionWatchedFiles . get ( normalizedPath )
254+ const isUnknownExtension = affectedHandles === undefined || affectedHandles . size === 0
260255
261256 if ( isUnknownExtension && ! isExtensionToml && ! isConfigAppPath ) {
262257 // Ignore an event if it's not part of an existing extension
@@ -265,8 +260,10 @@ export class FileWatcher {
265260 return
266261 }
267262
268- for ( const extensionPath of affectedExtensions ?? [ ] ) {
269- this . handleEventForExtension ( event , path , extensionPath , startTime , false )
263+ for ( const handle of affectedHandles ?? [ ] ) {
264+ const extension = this . app . realExtensions . find ( ( ext ) => ext . handle === handle )
265+ const extensionPath = extension ? normalizePath ( extension . directory ) : this . app . directory
266+ this . handleEventForExtension ( event , path , extensionPath , startTime , false , handle )
270267 }
271268 if ( isUnknownExtension ) {
272269 this . handleEventForExtension ( event , path , this . app . directory , startTime , true )
@@ -281,6 +278,7 @@ export class FileWatcher {
281278 extensionPath : string ,
282279 startTime : StartTime ,
283280 isUnknownExtension : boolean ,
281+ extensionHandle ?: string ,
284282 ) {
285283 const isExtensionToml = path . endsWith ( '.extension.toml' )
286284 const isConfigAppPath = path === this . app . configPath
@@ -293,17 +291,17 @@ export class FileWatcher {
293291 break
294292 }
295293 if ( isExtensionToml || isConfigAppPath ) {
296- this . pushEvent ( { type : 'extensions_config_updated' , path, extensionPath, startTime} )
294+ this . pushEvent ( { type : 'extensions_config_updated' , path, extensionPath, extensionHandle , startTime} )
297295 } else {
298- this . pushEvent ( { type : 'file_updated' , path, extensionPath, startTime} )
296+ this . pushEvent ( { type : 'file_updated' , path, extensionPath, extensionHandle , startTime} )
299297 }
300298 break
301299 case 'add' :
302300 // If it's a normal non-toml file, just report a file_created event.
303301 // If a toml file was added, a new extension(s) is being created.
304302 // We need to wait for the lock file to disappear before triggering the event.
305303 if ( ! isExtensionToml ) {
306- this . pushEvent ( { type : 'file_created' , path, extensionPath, startTime} )
304+ this . pushEvent ( { type : 'file_created' , path, extensionPath, extensionHandle , startTime} )
307305 break
308306 }
309307 let totalWaitedTime = 0
@@ -339,7 +337,7 @@ export class FileWatcher {
339337 setTimeout ( ( ) => {
340338 // If the extensionPath is not longer in the list, the extension was deleted while the timeout was running.
341339 if ( ! this . extensionPaths . includes ( extensionPath ) ) return
342- this . pushEvent ( { type : 'file_deleted' , path, extensionPath, startTime} )
340+ this . pushEvent ( { type : 'file_deleted' , path, extensionPath, extensionHandle , startTime} )
343341 // Force an emit because we are inside a timeout callback
344342 this . debouncedEmit ( )
345343 } , FILE_DELETE_TIMEOUT_IN_MS )
0 commit comments