|
1 | 1 | /* eslint-disable no-case-declarations */ |
2 | 2 | import {AppLinkedInterface} from '../../../models/app/app.js' |
3 | 3 | import {configurationFileNames} from '../../../constants.js' |
4 | | -import {dirname, joinPath, normalizePath, relativePath} from '@shopify/cli-kit/node/path' |
| 4 | +import {basename, dirname, joinPath, normalizePath, relativePath} from '@shopify/cli-kit/node/path' |
5 | 5 | import {FSWatcher} from 'chokidar' |
6 | 6 | import {outputDebug} from '@shopify/cli-kit/node/output' |
7 | 7 | import {AbortSignal} from '@shopify/cli-kit/node/abort' |
@@ -59,7 +59,7 @@ export class FileWatcher { |
59 | 59 | private watcher?: FSWatcher |
60 | 60 | private readonly debouncedEmit: () => void |
61 | 61 | private readonly ignored: {[key: string]: ignore.Ignore | undefined} = {} |
62 | | - // Map of file paths to the extension handles that watch them |
| 62 | + // Map of watched paths (files and directories) to the extension handles that own them |
63 | 63 | private readonly extensionWatchedFiles = new Map<string, Set<string>>() |
64 | 64 |
|
65 | 65 | constructor( |
@@ -159,18 +159,34 @@ export class FileWatcher { |
159 | 159 | for (const {extension, watchedFiles} of extensionResults) { |
160 | 160 | for (const file of watchedFiles) { |
161 | 161 | const normalizedPath = normalizePath(file) |
162 | | - allFiles.add(normalizedPath) |
163 | 162 |
|
164 | | - // Track which extension handles watch this file |
| 163 | + // Track which extension handles watch this path |
165 | 164 | const handlesSet = this.extensionWatchedFiles.get(normalizedPath) ?? new Set() |
166 | 165 | handlesSet.add(extension.handle) |
167 | 166 | this.extensionWatchedFiles.set(normalizedPath, handlesSet) |
| 167 | + |
| 168 | + // Only pass files to chokidar — directories are already covered by |
| 169 | + // extension directory watchers |
| 170 | + if (basename(normalizedPath).includes('.')) { |
| 171 | + allFiles.add(normalizedPath) |
| 172 | + } |
168 | 173 | } |
169 | 174 | } |
170 | 175 |
|
171 | 176 | return Array.from(allFiles) |
172 | 177 | } |
173 | 178 |
|
| 179 | + private getExtensionHandlesForFilePath(normalizedPath: string): Set<string> | undefined { |
| 180 | + const handles = this.extensionWatchedFiles.get(normalizedPath) |
| 181 | + if (handles) return handles |
| 182 | + for (const [watchedPath, pathHandles] of this.extensionWatchedFiles) { |
| 183 | + if (normalizedPath.startsWith(`${watchedPath}/`)) { |
| 184 | + return pathHandles |
| 185 | + } |
| 186 | + } |
| 187 | + return undefined |
| 188 | + } |
| 189 | + |
174 | 190 | /** |
175 | 191 | * Emits the accumulated events and resets the current events list. |
176 | 192 | * It also logs the number of events emitted and their paths for debugging purposes. |
@@ -251,7 +267,7 @@ export class FileWatcher { |
251 | 267 | if (isConfigAppPath) { |
252 | 268 | this.handleEventForExtension(event, path, this.app.directory, startTime, false) |
253 | 269 | } else { |
254 | | - const affectedHandles = this.extensionWatchedFiles.get(normalizedPath) |
| 270 | + const affectedHandles = this.getExtensionHandlesForFilePath(normalizedPath) |
255 | 271 | const isUnknownExtension = affectedHandles === undefined || affectedHandles.size === 0 |
256 | 272 |
|
257 | 273 | if (isUnknownExtension && !isExtensionToml && !isConfigAppPath) { |
|
0 commit comments