Skip to content

Commit 5f3d9d6

Browse files
committed
add-directories-to-watched-files
1 parent 9f31416 commit 5f3d9d6

4 files changed

Lines changed: 48 additions & 6 deletions

File tree

packages/app/src/cli/models/extensions/extension-instance.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
5151
specification: ExtensionSpecification
5252
uid: string
5353
private cachedImportPaths?: string[]
54+
private readonly additionalWatchedPaths = new Set<string>()
5455

5556
get graphQLType() {
5657
return (this.specification.graphQLType ?? this.specification.identifier).toUpperCase()
@@ -460,9 +461,23 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
460461
watchedFiles.push(...importedFiles)
461462
}
462463

464+
watchedFiles.push(...this.additionalWatchedPaths)
465+
463466
return [...new Set(watchedFiles.map((file) => normalizePath(file)))]
464467
}
465468

469+
addWatchedPath(absolutePath: string): void {
470+
this.additionalWatchedPaths.add(normalizePath(absolutePath))
471+
}
472+
473+
getWatchedPaths(): ReadonlySet<string> {
474+
return this.additionalWatchedPaths
475+
}
476+
477+
clearWatchedPaths(): void {
478+
this.additionalWatchedPaths.clear()
479+
}
480+
466481
/**
467482
* Rescans imports for this extension and updates the cached import paths
468483
* Returns true if the imports changed

packages/app/src/cli/services/build/steps/include-assets-step.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,12 @@ export async function executeIncludeAssetsStep(
151151
usedBasenames,
152152
preserveFilePaths: entry.preserveFilePaths,
153153
})
154-
result.pathMap.forEach((val, key) => aggregatedPathMap.set(key, val))
154+
result.pathMap.forEach((val, key) => {
155+
aggregatedPathMap.set(key, val)
156+
if (Array.isArray(val)) {
157+
extension.addWatchedPath(joinPath(extension.directory, key))
158+
}
159+
})
155160
configKeyCount += result.filesCopied
156161
}
157162

packages/app/src/cli/services/dev/app-events/app-event-watcher.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,13 @@ export class AppEventWatcher extends EventEmitter {
139139
const buildableEvents = appEvent.extensionEvents.filter((extEvent) => extEvent.type !== EventType.Deleted)
140140

141141
// Build the created/updated extensions and update the extension events with the build result
142+
const watchedPathsBefore = new Set(buildableEvents.flatMap((ev) => [...ev.extension.getWatchedPaths()]))
142143
await this.buildExtensions(buildableEvents)
144+
const watchedPathsAfter = new Set(buildableEvents.flatMap((ev) => [...ev.extension.getWatchedPaths()]))
145+
const watchedPathsChanged =
146+
watchedPathsBefore.size !== watchedPathsAfter.size ||
147+
[...watchedPathsAfter].some((watchedPath) => !watchedPathsBefore.has(watchedPath))
148+
if (watchedPathsChanged) await this.fileWatcher?.start()
143149

144150
// Generate the extension types after building the extensions so new imports are included
145151
// Skip if the app was reloaded, as generateExtensionTypes was already called during reload

packages/app/src/cli/services/dev/app-events/file-watcher.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable no-case-declarations */
22
import {AppLinkedInterface} from '../../../models/app/app.js'
33
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'
55
import {FSWatcher} from 'chokidar'
66
import {outputDebug} from '@shopify/cli-kit/node/output'
77
import {AbortSignal} from '@shopify/cli-kit/node/abort'
@@ -59,7 +59,7 @@ export class FileWatcher {
5959
private watcher?: FSWatcher
6060
private readonly debouncedEmit: () => void
6161
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
6363
private readonly extensionWatchedFiles = new Map<string, Set<string>>()
6464

6565
constructor(
@@ -159,18 +159,34 @@ export class FileWatcher {
159159
for (const {extension, watchedFiles} of extensionResults) {
160160
for (const file of watchedFiles) {
161161
const normalizedPath = normalizePath(file)
162-
allFiles.add(normalizedPath)
163162

164-
// Track which extension handles watch this file
163+
// Track which extension handles watch this path
165164
const handlesSet = this.extensionWatchedFiles.get(normalizedPath) ?? new Set()
166165
handlesSet.add(extension.handle)
167166
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+
}
168173
}
169174
}
170175

171176
return Array.from(allFiles)
172177
}
173178

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+
174190
/**
175191
* Emits the accumulated events and resets the current events list.
176192
* It also logs the number of events emitted and their paths for debugging purposes.
@@ -251,7 +267,7 @@ export class FileWatcher {
251267
if (isConfigAppPath) {
252268
this.handleEventForExtension(event, path, this.app.directory, startTime, false)
253269
} else {
254-
const affectedHandles = this.extensionWatchedFiles.get(normalizedPath)
270+
const affectedHandles = this.getExtensionHandlesForFilePath(normalizedPath)
255271
const isUnknownExtension = affectedHandles === undefined || affectedHandles.size === 0
256272

257273
if (isUnknownExtension && !isExtensionToml && !isConfigAppPath) {

0 commit comments

Comments
 (0)