diff --git a/.changeset/few-buses-greet.md b/.changeset/few-buses-greet.md new file mode 100644 index 0000000000..15169fee5c --- /dev/null +++ b/.changeset/few-buses-greet.md @@ -0,0 +1,5 @@ +--- +'@shopify/theme': patch +--- + +Add a 250ms debounce on our filewatcher for themes to stop potential file deletes diff --git a/packages/theme/src/cli/utilities/theme-fs.ts b/packages/theme/src/cli/utilities/theme-fs.ts index 4d07884955..c66e289bd4 100644 --- a/packages/theme/src/cli/utilities/theme-fs.ts +++ b/packages/theme/src/cli/utilities/theme-fs.ts @@ -22,6 +22,8 @@ import type { ThemeFSEventPayload, } from '@shopify/cli-kit/node/themes/types' +const FILE_EVENT_DEBOUNCE_TIME_IN_MS = 250 + const THEME_DIRECTORY_PATTERNS = [ 'assets/**/*.*', 'config/**/*.json', @@ -268,10 +270,29 @@ export function mountThemeFileSystem(root: string, options?: ThemeFileSystemOpti ignoreInitial: true, }) + // Debounce file events per-file + const pendingEvents = new Map() + + const queueFsEvent = (eventName: 'add' | 'change' | 'unlink', filePath: string) => { + const fileKey = getKey(filePath) + + const pending = pendingEvents.get(fileKey) + if (pending) { + clearTimeout(pending.timeout) + } + + const timeout = setTimeout(() => { + pendingEvents.delete(fileKey) + handleFsEvent(eventName, themeId, adminSession, filePath) + }, FILE_EVENT_DEBOUNCE_TIME_IN_MS) + + pendingEvents.set(fileKey, {eventName, timeout}) + } + watcher - .on('add', handleFsEvent.bind(null, 'add', themeId, adminSession)) - .on('change', handleFsEvent.bind(null, 'change', themeId, adminSession)) - .on('unlink', handleFsEvent.bind(null, 'unlink', themeId, adminSession)) + .on('add', queueFsEvent.bind(null, 'add')) + .on('change', queueFsEvent.bind(null, 'change')) + .on('unlink', queueFsEvent.bind(null, 'unlink')) }, } }