Skip to content

Commit 70558d8

Browse files
graygilmoreclaude
andcommitted
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 <noreply@anthropic.com>
1 parent fa4034d commit 70558d8

9 files changed

Lines changed: 67 additions & 9 deletions

File tree

docs-shopify.dev/commands/interfaces/theme-dev.interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ export interface themedev {
9090
*/
9191
'--path <value>'?: string
9292

93+
/**
94+
* 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.
95+
* @environment SHOPIFY_FLAG_POLL
96+
*/
97+
'--poll'?: ''
98+
9399
/**
94100
* Local port to serve theme preview from.
95101
* @environment SHOPIFY_FLAG_PORT

docs-shopify.dev/generated/generated_docs_data.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6259,6 +6259,15 @@
62596259
"isOptional": true,
62606260
"environmentValue": "SHOPIFY_FLAG_PATH"
62616261
},
6262+
{
6263+
"filePath": "docs-shopify.dev/commands/interfaces/theme-dev.interface.ts",
6264+
"syntaxKind": "PropertySignature",
6265+
"name": "--poll",
6266+
"value": "\"\"",
6267+
"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.",
6268+
"isOptional": true,
6269+
"environmentValue": "SHOPIFY_FLAG_POLL"
6270+
},
62626271
{
62636272
"filePath": "docs-shopify.dev/commands/interfaces/theme-dev.interface.ts",
62646273
"syntaxKind": "PropertySignature",
@@ -6359,7 +6368,7 @@
63596368
"environmentValue": "SHOPIFY_FLAG_IGNORE"
63606369
}
63616370
],
6362-
"value": "export interface themedev {\n /**\n * Allow development on a live theme.\n * @environment SHOPIFY_FLAG_ALLOW_LIVE\n */\n '-a, --allow-live'?: ''\n\n /**\n * The environment to apply to the current command.\n * @environment SHOPIFY_FLAG_ENVIRONMENT\n */\n '-e, --environment <value>'?: string\n\n /**\n * Controls the visibility of the error overlay when an theme asset upload fails:\n- silent Prevents the error overlay from appearing.\n- default Displays the error overlay.\n \n * @environment SHOPIFY_FLAG_ERROR_OVERLAY\n */\n '--error-overlay <value>'?: string\n\n /**\n * Set which network interface the web server listens on. The default value is 127.0.0.1.\n * @environment SHOPIFY_FLAG_HOST\n */\n '--host <value>'?: string\n\n /**\n * Skip hot reloading any files that match the specified pattern.\n * @environment SHOPIFY_FLAG_IGNORE\n */\n '-x, --ignore <value>'?: string\n\n /**\n * The listing preset to use for multi-preset themes. Applies preset files from listings/[preset-name] directory.\n * @environment SHOPIFY_FLAG_LISTING\n */\n '--listing <value>'?: string\n\n /**\n * The live reload mode switches the server behavior when a file is modified:\n- hot-reload Hot reloads local changes to CSS and sections (default)\n- full-page Always refreshes the entire page\n- off Deactivate live reload\n * @environment SHOPIFY_FLAG_LIVE_RELOAD\n */\n '--live-reload <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Prevents files from being deleted in the remote theme when a file has been deleted locally. This applies to files that are deleted while the command is running, and files that have been deleted locally before the command is run.\n * @environment SHOPIFY_FLAG_NODELETE\n */\n '-n, --nodelete'?: ''\n\n /**\n * The file path or URL. The file path is to a file that you want updated on idle. The URL path is where you want a webhook posted to report on file changes.\n * @environment SHOPIFY_FLAG_NOTIFY\n */\n '--notify <value>'?: string\n\n /**\n * Hot reload only files that match the specified pattern.\n * @environment SHOPIFY_FLAG_ONLY\n */\n '-o, --only <value>'?: string\n\n /**\n * Automatically launch the theme preview in your default web browser.\n * @environment SHOPIFY_FLAG_OPEN\n */\n '--open'?: ''\n\n /**\n * Password generated from the Theme Access app or an Admin API token.\n * @environment SHOPIFY_CLI_THEME_TOKEN\n */\n '--password <value>'?: string\n\n /**\n * The path where you want to run the command. Defaults to the current working directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path <value>'?: string\n\n /**\n * Local port to serve theme preview from.\n * @environment SHOPIFY_FLAG_PORT\n */\n '--port <value>'?: string\n\n /**\n * Store URL. It can be the store prefix (example) or the full myshopify.com URL (example.myshopify.com, https://example.myshopify.com).\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>'?: string\n\n /**\n * The password for storefronts with password protection.\n * @environment SHOPIFY_FLAG_STORE_PASSWORD\n */\n '--store-password <value>'?: string\n\n /**\n * Theme ID or name of the remote theme.\n * @environment SHOPIFY_FLAG_THEME_ID\n */\n '-t, --theme <value>'?: string\n\n /**\n * Synchronize Theme Editor updates in the local theme files.\n * @environment SHOPIFY_FLAG_THEME_EDITOR_SYNC\n */\n '--theme-editor-sync'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
6371+
"value": "export interface themedev {\n /**\n * Allow development on a live theme.\n * @environment SHOPIFY_FLAG_ALLOW_LIVE\n */\n '-a, --allow-live'?: ''\n\n /**\n * The environment to apply to the current command.\n * @environment SHOPIFY_FLAG_ENVIRONMENT\n */\n '-e, --environment <value>'?: string\n\n /**\n * Controls the visibility of the error overlay when an theme asset upload fails:\n- silent Prevents the error overlay from appearing.\n- default Displays the error overlay.\n \n * @environment SHOPIFY_FLAG_ERROR_OVERLAY\n */\n '--error-overlay <value>'?: string\n\n /**\n * Set which network interface the web server listens on. The default value is 127.0.0.1.\n * @environment SHOPIFY_FLAG_HOST\n */\n '--host <value>'?: string\n\n /**\n * Skip hot reloading any files that match the specified pattern.\n * @environment SHOPIFY_FLAG_IGNORE\n */\n '-x, --ignore <value>'?: string\n\n /**\n * The listing preset to use for multi-preset themes. Applies preset files from listings/[preset-name] directory.\n * @environment SHOPIFY_FLAG_LISTING\n */\n '--listing <value>'?: string\n\n /**\n * The live reload mode switches the server behavior when a file is modified:\n- hot-reload Hot reloads local changes to CSS and sections (default)\n- full-page Always refreshes the entire page\n- off Deactivate live reload\n * @environment SHOPIFY_FLAG_LIVE_RELOAD\n */\n '--live-reload <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Prevents files from being deleted in the remote theme when a file has been deleted locally. This applies to files that are deleted while the command is running, and files that have been deleted locally before the command is run.\n * @environment SHOPIFY_FLAG_NODELETE\n */\n '-n, --nodelete'?: ''\n\n /**\n * The file path or URL. The file path is to a file that you want updated on idle. The URL path is where you want a webhook posted to report on file changes.\n * @environment SHOPIFY_FLAG_NOTIFY\n */\n '--notify <value>'?: string\n\n /**\n * Hot reload only files that match the specified pattern.\n * @environment SHOPIFY_FLAG_ONLY\n */\n '-o, --only <value>'?: string\n\n /**\n * Automatically launch the theme preview in your default web browser.\n * @environment SHOPIFY_FLAG_OPEN\n */\n '--open'?: ''\n\n /**\n * Password generated from the Theme Access app or an Admin API token.\n * @environment SHOPIFY_CLI_THEME_TOKEN\n */\n '--password <value>'?: string\n\n /**\n * The path where you want to run the command. Defaults to the current working directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path <value>'?: string\n\n /**\n * 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.\n * @environment SHOPIFY_FLAG_POLL\n */\n '--poll'?: ''\n\n /**\n * Local port to serve theme preview from.\n * @environment SHOPIFY_FLAG_PORT\n */\n '--port <value>'?: string\n\n /**\n * Store URL. It can be the store prefix (example) or the full myshopify.com URL (example.myshopify.com, https://example.myshopify.com).\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>'?: string\n\n /**\n * The password for storefronts with password protection.\n * @environment SHOPIFY_FLAG_STORE_PASSWORD\n */\n '--store-password <value>'?: string\n\n /**\n * Theme ID or name of the remote theme.\n * @environment SHOPIFY_FLAG_THEME_ID\n */\n '-t, --theme <value>'?: string\n\n /**\n * Synchronize Theme Editor updates in the local theme files.\n * @environment SHOPIFY_FLAG_THEME_EDITOR_SYNC\n */\n '--theme-editor-sync'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
63636372
}
63646373
}
63656374
}

packages/cli-kit/src/public/node/themes/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface ThemeFileSystemOptions {
3030
listing?: string
3131
noDelete?: boolean
3232
notify?: string
33+
poll?: boolean
3334
}
3435

3536
/**

packages/cli/README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2163,8 +2163,8 @@ Uploads the current theme as a development theme to the connected store, then pr
21632163
USAGE
21642164
$ shopify theme dev [-a] [-e <value>...] [--error-overlay silent|default] [--host <value>] [-x <value>...]
21652165
[--listing <value>] [--live-reload hot-reload|full-page|off] [--no-color] [-n] [--notify <value>] [-o <value>...]
2166-
[--open] [--password <value>] [--path <value>] [--port <value>] [-s <value>] [--store-password <value>] [-t <value>]
2167-
[--theme-editor-sync] [--verbose]
2166+
[--open] [--password <value>] [--path <value>] [--poll] [--port <value>] [-s <value>] [--store-password <value>] [-t
2167+
<value>] [--theme-editor-sync] [--verbose]
21682168
21692169
FLAGS
21702170
-a, --allow-live
@@ -2230,6 +2230,10 @@ FLAGS
22302230
--path=<value>
22312231
[env: SHOPIFY_FLAG_PATH] The path where you want to run the command. Defaults to the current working directory.
22322232
2233+
--poll
2234+
[env: SHOPIFY_FLAG_POLL] Use polling to detect file changes. Use this when file system events are unreliable, such
2235+
as with build tools that preserve timestamps, Docker volumes, or network filesystems.
2236+
22332237
--port=<value>
22342238
[env: SHOPIFY_FLAG_PORT] Local port to serve theme preview from.
22352239

packages/cli/oclif.manifest.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6218,9 +6218,8 @@
62186218
},
62196219
"poll": {
62206220
"allowNo": false,
6221-
"description": "Force polling to detect file changes.",
6221+
"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.",
62226222
"env": "SHOPIFY_FLAG_POLL",
6223-
"hidden": true,
62246223
"name": "poll",
62256224
"type": "boolean"
62266225
},
@@ -7856,9 +7855,8 @@
78567855
},
78577856
"poll": {
78587857
"allowNo": false,
7859-
"description": "Force polling to detect file changes.",
7858+
"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.",
78607859
"env": "SHOPIFY_FLAG_POLL",
7861-
"hidden": true,
78627860
"name": "poll",
78637861
"type": "boolean"
78647862
},

packages/theme/src/cli/commands/theme/dev.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ You can run this command only in a directory that matches the [default Shopify t
6969
env: 'SHOPIFY_FLAG_ERROR_OVERLAY',
7070
}),
7171
poll: Flags.boolean({
72-
hidden: true,
73-
description: 'Force polling to detect file changes.',
72+
description:
73+
'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.',
7474
env: 'SHOPIFY_FLAG_POLL',
7575
}),
7676
'theme-editor-sync': Flags.boolean({
@@ -183,6 +183,7 @@ You can run this command only in a directory that matches the [default Shopify t
183183
ignore,
184184
only,
185185
notify: flags.notify,
186+
poll: flags.poll,
186187
})
187188

188189
await metafieldsPull({

packages/theme/src/cli/services/dev.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ interface DevOptions {
4040
only: string[]
4141
notify?: string
4242
listing?: string
43+
poll?: boolean
4344
}
4445

4546
export async function dev(options: DevOptions) {
@@ -83,6 +84,7 @@ export async function dev(options: DevOptions) {
8384
listing: options.listing,
8485
noDelete: options.noDelete,
8586
notify: options.notify,
87+
poll: options.poll,
8688
})
8789

8890
const host = options.host ?? DEFAULT_HOST

packages/theme/src/cli/utilities/theme-fs.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,42 @@ describe('theme-fs', () => {
135135
)
136136
})
137137

138+
test('passes usePolling and useFsEvents options to chokidar when poll is true', async () => {
139+
// Given
140+
const root = joinPath(locationOfThisFile, 'fixtures/theme')
141+
const watchSpy = vi.spyOn(chokidar, 'watch')
142+
143+
// When
144+
const themeFileSystem = mountThemeFileSystem(root, {poll: true})
145+
await themeFileSystem.ready()
146+
await themeFileSystem.startWatcher('123', {token: 'token'} as any)
147+
148+
// Then
149+
expect(watchSpy).toHaveBeenCalledWith(expect.any(Array), {
150+
ignored: expect.any(Array),
151+
persistent: expect.any(Boolean),
152+
ignoreInitial: true,
153+
usePolling: true,
154+
useFsEvents: false,
155+
})
156+
})
157+
158+
test('does not include usePolling or useFsEvents in chokidar options when poll is not set', async () => {
159+
// Given
160+
const root = joinPath(locationOfThisFile, 'fixtures/theme')
161+
const watchSpy = vi.spyOn(chokidar, 'watch')
162+
163+
// When
164+
const themeFileSystem = mountThemeFileSystem(root)
165+
await themeFileSystem.ready()
166+
await themeFileSystem.startWatcher('123', {token: 'token'} as any)
167+
168+
// Then
169+
const chokidarOptions = watchSpy.mock.calls[0]?.[1] as Record<string, unknown>
170+
expect(chokidarOptions).not.toHaveProperty('usePolling')
171+
expect(chokidarOptions).not.toHaveProperty('useFsEvents')
172+
})
173+
138174
test('does not include listing directory when no listing is specified', async () => {
139175
// Given
140176
const root = joinPath(locationOfThisFile, 'fixtures/theme')

packages/theme/src/cli/utilities/theme-fs.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ export function mountThemeFileSystem(root: string, options?: ThemeFileSystemOpti
321321
ignored: DEFAULT_IGNORE_PATTERNS,
322322
persistent: !process.env.SHOPIFY_UNIT_TEST,
323323
ignoreInitial: true,
324+
...(options?.poll ? {usePolling: true, useFsEvents: false} : {}),
324325
})
325326

326327
// Debounce file events per-file and per-event-type

0 commit comments

Comments
 (0)