From d4fdcb57ece8ac211927a07a32e68b3b42e3e6d8 Mon Sep 17 00:00:00 2001 From: Gray Gilmore Date: Mon, 16 Mar 2026 14:46:25 -0700 Subject: [PATCH] fix: wire --poll flag through to chokidar in theme dev The --poll flag was defined on the dev command but never passed to chokidar's usePolling option. On Linux/Windows, chokidar's fs.watch backend compares mtimeMs and suppresses change events when mtime hasn't changed. Build tools (like Gulp + Sass) that preserve the original source file's timestamp on compiled output cause chokidar to silently drop these events. This wires poll through the full chain: command -> service -> mountThemeFileSystem -> chokidar.watch. When --poll is set, both usePolling: true and useFsEvents: false are passed to chokidar. useFsEvents: false is required because on macOS chokidar prefers FSEvents over polling even when usePolling is true. Also unhides the --poll flag and improves its description to tell users when they'd want it. Co-Authored-By: Claude Opus 4.6 --- .../cli-kit/src/public/node/themes/types.ts | 1 + packages/theme/src/cli/commands/theme/dev.ts | 5 +-- packages/theme/src/cli/services/dev.ts | 2 ++ .../theme/src/cli/utilities/theme-fs.test.ts | 36 +++++++++++++++++++ packages/theme/src/cli/utilities/theme-fs.ts | 1 + 5 files changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/cli-kit/src/public/node/themes/types.ts b/packages/cli-kit/src/public/node/themes/types.ts index 55f4a2ffefe..a878c88467d 100644 --- a/packages/cli-kit/src/public/node/themes/types.ts +++ b/packages/cli-kit/src/public/node/themes/types.ts @@ -30,6 +30,7 @@ export interface ThemeFileSystemOptions { listing?: string noDelete?: boolean notify?: string + poll?: boolean } /** diff --git a/packages/theme/src/cli/commands/theme/dev.ts b/packages/theme/src/cli/commands/theme/dev.ts index 99eb6c25b7f..5408f374090 100644 --- a/packages/theme/src/cli/commands/theme/dev.ts +++ b/packages/theme/src/cli/commands/theme/dev.ts @@ -69,8 +69,8 @@ You can run this command only in a directory that matches the [default Shopify t env: 'SHOPIFY_FLAG_ERROR_OVERLAY', }), poll: Flags.boolean({ - hidden: true, - description: 'Force polling to detect file changes.', + description: + 'Use polling to detect file changes. Use this when file system events are unreliable, such as with build tools that preserve timestamps, Docker volumes, or network filesystems.', env: 'SHOPIFY_FLAG_POLL', }), 'theme-editor-sync': Flags.boolean({ @@ -183,6 +183,7 @@ You can run this command only in a directory that matches the [default Shopify t ignore, only, notify: flags.notify, + poll: flags.poll, }) await metafieldsPull({ diff --git a/packages/theme/src/cli/services/dev.ts b/packages/theme/src/cli/services/dev.ts index 47ad2a174a7..57fc693a20b 100644 --- a/packages/theme/src/cli/services/dev.ts +++ b/packages/theme/src/cli/services/dev.ts @@ -40,6 +40,7 @@ interface DevOptions { only: string[] notify?: string listing?: string + poll?: boolean } export async function dev(options: DevOptions) { @@ -83,6 +84,7 @@ export async function dev(options: DevOptions) { listing: options.listing, noDelete: options.noDelete, notify: options.notify, + poll: options.poll, }) const host = options.host ?? DEFAULT_HOST diff --git a/packages/theme/src/cli/utilities/theme-fs.test.ts b/packages/theme/src/cli/utilities/theme-fs.test.ts index ac981cbf5ce..dca4d00b9f4 100644 --- a/packages/theme/src/cli/utilities/theme-fs.test.ts +++ b/packages/theme/src/cli/utilities/theme-fs.test.ts @@ -135,6 +135,42 @@ describe('theme-fs', () => { ) }) + test('passes usePolling and useFsEvents options to chokidar when poll is true', async () => { + // Given + const root = joinPath(locationOfThisFile, 'fixtures/theme') + const watchSpy = vi.spyOn(chokidar, 'watch') + + // When + const themeFileSystem = mountThemeFileSystem(root, {poll: true}) + await themeFileSystem.ready() + await themeFileSystem.startWatcher('123', {token: 'token'} as any) + + // Then + expect(watchSpy).toHaveBeenCalledWith(expect.any(Array), { + ignored: expect.any(Array), + persistent: expect.any(Boolean), + ignoreInitial: true, + usePolling: true, + useFsEvents: false, + }) + }) + + test('does not include usePolling or useFsEvents in chokidar options when poll is not set', async () => { + // Given + const root = joinPath(locationOfThisFile, 'fixtures/theme') + const watchSpy = vi.spyOn(chokidar, 'watch') + + // When + const themeFileSystem = mountThemeFileSystem(root) + await themeFileSystem.ready() + await themeFileSystem.startWatcher('123', {token: 'token'} as any) + + // Then + const chokidarOptions = watchSpy.mock.calls[0]?.[1] as Record + expect(chokidarOptions).not.toHaveProperty('usePolling') + expect(chokidarOptions).not.toHaveProperty('useFsEvents') + }) + test('does not include listing directory when no listing is specified', async () => { // Given const root = joinPath(locationOfThisFile, 'fixtures/theme') diff --git a/packages/theme/src/cli/utilities/theme-fs.ts b/packages/theme/src/cli/utilities/theme-fs.ts index 8b236845ec3..a61ea716763 100644 --- a/packages/theme/src/cli/utilities/theme-fs.ts +++ b/packages/theme/src/cli/utilities/theme-fs.ts @@ -319,6 +319,7 @@ export function mountThemeFileSystem(root: string, options?: ThemeFileSystemOpti ignored: DEFAULT_IGNORE_PATTERNS, persistent: !process.env.SHOPIFY_UNIT_TEST, ignoreInitial: true, + ...(options?.poll ? {usePolling: true, useFsEvents: false} : {}), }) watcher