Skip to content

Commit 88d1a0d

Browse files
Extract custom watch paths to specifications
Co-authored-by: Claude Code <claude-code@anthropic.com>
1 parent cc5abaa commit 88d1a0d

5 files changed

Lines changed: 77 additions & 30 deletions

File tree

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

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {BaseConfigType, MAX_EXTENSION_HANDLE_LENGTH, MAX_UID_LENGTH} from './schemas.js'
22
import {FunctionConfigType} from './specifications/function.js'
3-
import {ExtensionFeature, ExtensionSpecification} from './specification.js'
3+
import {DevSessionWatchConfig, ExtensionFeature, ExtensionSpecification} from './specification.js'
44
import {SingleWebhookSubscriptionType} from './specifications/app_config_webhook_schemas/webhooks_schema.js'
55
import {ExtensionBuildOptions, bundleFunctionExtension} from '../../services/build/extension.js'
66
import {bundleThemeExtension} from '../../services/extensions/bundle.js'
@@ -277,20 +277,15 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
277277
return [this.entrySourceFilePath]
278278
}
279279

280-
// Custom paths to be watched in a dev session
281-
// Return undefiend if there aren't custom configured paths (everything is watched)
282-
// If there are, include some default paths.
283-
get devSessionCustomWatchPaths() {
284-
const config = this.configuration as unknown as FunctionConfigType
285-
if (!config.build || !config.build.watch) return undefined
286-
287-
const watchPaths = [config.build.watch].flat().map((path) => joinPath(this.directory, path))
288-
289-
watchPaths.push(joinPath(this.directory, 'locales', '**.json'))
290-
watchPaths.push(joinPath(this.directory, '**', '!(.)*.graphql'))
291-
watchPaths.push(joinPath(this.directory, '**.toml'))
280+
// Custom watch configuration for dev sessions
281+
// Return undefined to watch everything (default for 'extension' experience)
282+
// Return a config with empty paths to watch nothing (default for 'configuration' experience)
283+
get devSessionWatchConfig(): DevSessionWatchConfig | undefined {
284+
if (this.specification.devSessionWatchConfig) {
285+
return this.specification.devSessionWatchConfig(this)
286+
}
292287

293-
return watchPaths
288+
return this.specification.experience === 'configuration' ? {paths: []} : undefined
294289
}
295290

296291
async watchConfigurationPaths() {
@@ -436,20 +431,31 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
436431
watchedFiles(): string[] {
437432
const watchedFiles: string[] = []
438433

439-
// Add extension directory files based on devSessionCustomWatchPaths or all files
440-
const patterns = this.devSessionCustomWatchPaths ?? ['**/*']
434+
const defaultIgnore = [
435+
'**/node_modules/**',
436+
'**/.git/**',
437+
'**/*.test.*',
438+
'**/dist/**',
439+
'**/*.swp',
440+
'**/generated/**',
441+
'**/.gitignore',
442+
]
443+
const watchConfig = this.devSessionWatchConfig
444+
445+
const patterns = watchConfig?.paths ?? ['**/*']
446+
const ignore = watchConfig?.ignore ?? defaultIgnore
441447
const files = patterns.flatMap((pattern) =>
442448
globSync(pattern, {
443449
cwd: this.directory,
444450
absolute: true,
445451
followSymbolicLinks: false,
446-
ignore: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/*.swp', '**/generated/**'],
452+
ignore,
447453
}),
448454
)
449455
watchedFiles.push(...files.flat())
450456

451-
// Add imported files from outside the extension directory unless custom watch paths are defined
452-
if (!this.devSessionCustomWatchPaths) {
457+
// Add imported files from outside the extension directory unless custom watch config is defined
458+
if (!watchConfig) {
453459
const importedFiles = this.scanImports()
454460
watchedFiles.push(...importedFiles)
455461
}

packages/app/src/cli/models/extensions/specification.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,20 @@ export interface ExtensionSpecification<TConfiguration extends BaseConfigType =
136136
* Copy static assets from the extension directory to the output path
137137
*/
138138
copyStaticAssets?: (configuration: TConfiguration, directory: string, outputPath: string) => Promise<void>
139+
140+
/**
141+
* Custom watch configuration for dev sessions.
142+
* Return a DevSessionWatchConfig with paths to watch and optionally paths to ignore,
143+
* or undefined to watch all files in the extension directory.
144+
*/
145+
devSessionWatchConfig?: (extension: ExtensionInstance<TConfiguration>) => DevSessionWatchConfig | undefined
146+
}
147+
148+
export interface DevSessionWatchConfig {
149+
/** Absolute paths or globs to watch */
150+
paths: string[]
151+
/** Additional glob patterns to ignore (on top of the default ignore list) */
152+
ignore?: string[]
139153
}
140154

141155
/**
@@ -294,6 +308,7 @@ export function createContractBasedModuleSpecification<TConfiguration extends Ba
294308
| 'clientSteps'
295309
| 'experience'
296310
| 'transformRemoteToLocal'
311+
| 'devSessionWatchConfig'
297312
>,
298313
) {
299314
return createExtensionSpecification({

packages/app/src/cli/models/extensions/specifications/admin.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,31 @@
1-
import {createContractBasedModuleSpecification} from '../specification.js'
1+
import {createExtensionSpecification} from '../specification.js'
2+
import {BaseSchemaWithoutHandle} from '../schemas.js'
3+
import {zod} from '@shopify/cli-kit/node/schema'
4+
import {joinPath} from '@shopify/cli-kit/node/path'
25

3-
const adminSpecificationSpec = createContractBasedModuleSpecification({
6+
const AdminSchema = BaseSchemaWithoutHandle.extend({
7+
admin: zod
8+
.object({
9+
static_root: zod.string().optional(),
10+
})
11+
.optional(),
12+
})
13+
14+
const adminSpecificationSpec = createExtensionSpecification({
415
identifier: 'admin',
516
uidStrategy: 'single',
17+
experience: 'configuration',
18+
schema: AdminSchema,
19+
deployConfig: async (config, _) => {
20+
return {admin: config.admin}
21+
},
22+
devSessionWatchConfig: (extension) => {
23+
const staticRoot = extension.configuration.admin?.static_root
24+
if (!staticRoot) return {paths: []}
25+
26+
const path = joinPath(extension.directory, staticRoot, '**/*')
27+
return {paths: [path], ignore: []}
28+
},
629
transformRemoteToLocal: (remoteContent) => {
730
return {
831
admin: {

packages/app/src/cli/models/extensions/specifications/function.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,18 @@ const functionSpec = createExtensionSpecification({
9090
appModuleFeatures: (_) => ['function'],
9191
buildConfig: {mode: 'function'},
9292
getOutputRelativePath: (_extension: ExtensionInstance<FunctionConfigType>) => joinPath('dist', 'index.wasm'),
93+
devSessionWatchConfig: (extension: ExtensionInstance<FunctionConfigType>) => {
94+
const config = extension.configuration
95+
if (!config.build || !config.build.watch) return undefined
96+
97+
const paths = [config.build.watch].flat().map((path) => joinPath(extension.directory, path))
98+
99+
paths.push(joinPath(extension.directory, 'locales', '**.json'))
100+
paths.push(joinPath(extension.directory, '**', '!(.)*.graphql'))
101+
paths.push(joinPath(extension.directory, '**.toml'))
102+
103+
return {paths}
104+
},
93105
clientSteps: [
94106
{
95107
lifecycle: 'deploy',

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,6 @@ export class FileWatcher {
118118
// Create new watcher
119119
const {default: chokidar} = await import('chokidar')
120120
this.watcher = chokidar.watch(watchPaths, {
121-
ignored: [
122-
'**/node_modules/**',
123-
'**/.git/**',
124-
'**/*.test.*',
125-
'**/dist/**',
126-
'**/*.swp',
127-
'**/generated/**',
128-
'**/.gitignore',
129-
],
130121
persistent: true,
131122
ignoreInitial: true,
132123
})

0 commit comments

Comments
 (0)