diff --git a/packages/cli-kit/src/public/node/themes/types.ts b/packages/cli-kit/src/public/node/themes/types.ts index 55f4a2ffef..a878c88467 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 99eb6c25b7..5408f37409 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 47ad2a174a..57fc693a20 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 ac981cbf5c..dca4d00b9f 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 8b236845ec..a61ea71676 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