Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 24 additions & 18 deletions packages/app/src/cli/models/app/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
getUIExtensionRendererVersion,
isCurrentAppSchema,
isLegacyAppSchema,
normalizeExtensionDirectories,
validateExtensionsHandlesInCollection,
validateFunctionExtensionsWithUiHandle,
} from './app.js'
Expand Down Expand Up @@ -99,32 +100,37 @@ describe('app schema validation', () => {
expect(isCurrentAppSchema(config)).toBe(false)
})

test('extension_directories should be transformed to double asterisks', () => {
test('extension_directories preserves raw values without transformation', () => {
const config = {
...CORRECT_CURRENT_APP_SCHEMA,
extension_directories: ['extensions/*'],
}
const parsed = AppSchema.parse(config)
expect(parsed.extension_directories).toEqual(['extensions/**'])
expect(parsed.extension_directories).toEqual(['extensions/*'])
})
})
})

test('extension_directories is not transformed if it ends with double asterisks', () => {
const config = {
...CORRECT_CURRENT_APP_SCHEMA,
extension_directories: ['extensions/**'],
}
const parsed = AppSchema.parse(config)
expect(parsed.extension_directories).toEqual(['extensions/**'])
})
describe('normalizeExtensionDirectories', () => {
test('converts single trailing wildcard to double asterisks', () => {
expect(normalizeExtensionDirectories(['extensions/*'])).toEqual(['extensions/**'])
})

test('extension_directories is not transformed if it doesnt end with a wildcard', () => {
const config = {
...CORRECT_CURRENT_APP_SCHEMA,
extension_directories: ['extensions'],
}
const parsed = AppSchema.parse(config)
expect(parsed.extension_directories).toEqual(['extensions'])
})
test('preserves double asterisks', () => {
expect(normalizeExtensionDirectories(['extensions/**'])).toEqual(['extensions/**'])
})

test('does not transform paths without wildcards', () => {
expect(normalizeExtensionDirectories(['extensions'])).toEqual(['extensions'])
})

test('strips trailing path separators before fixing wildcards', () => {
expect(normalizeExtensionDirectories(['extensions/'])).toEqual(['extensions'])
expect(normalizeExtensionDirectories(['extensions\\'])).toEqual(['extensions'])
})

test('returns undefined for undefined input', () => {
expect(normalizeExtensionDirectories(undefined)).toBeUndefined()
})
})

Expand Down
14 changes: 9 additions & 5 deletions packages/app/src/cli/models/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,7 @@ import {deepMergeObjects} from '@shopify/cli-kit/common/object'

// Schemas for loading app configuration

const ExtensionDirectoriesSchema = zod
.array(zod.string())
.optional()
.transform(removeTrailingPathSeparator)
.transform(fixSingleWildcards)
const ExtensionDirectoriesSchema = zod.array(zod.string()).optional()

/**
* Schema for a freshly minted app template.
Expand Down Expand Up @@ -74,6 +70,14 @@ function fixSingleWildcards(value: string[] | undefined) {
return value?.map((dir) => dir.replace(/([^\*])\*$/, '$1**'))
}

/**
* Normalize extension directories for glob/chokidar consumption:
* strips trailing path separators, then converts single trailing `*` to `**`.
*/
export function normalizeExtensionDirectories(dirs: string[] | undefined): string[] | undefined {
return fixSingleWildcards(removeTrailingPathSeparator(dirs))
}

/**
* Schema for loading template config during app init.
* Uses passthrough() to allow any extra keys from full-featured templates
Expand Down
4 changes: 3 additions & 1 deletion packages/app/src/cli/models/app/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
isLegacyAppSchema,
TemplateConfigSchema,
getTemplateScopesArray,
normalizeExtensionDirectories,
} from './app.js'
import {parseHumanReadableError} from './error-parsing.js'
import {configurationFileNames, dotEnvFileNames} from '../../constants.js'
Expand Down Expand Up @@ -630,7 +631,8 @@ class AppLoader<TConfig extends AppConfiguration, TModuleSpec extends ExtensionS
}

private async createExtensionInstances(appDirectory: string, extensionDirectories?: string[]) {
const extensionConfigPaths = [...(extensionDirectories ?? [defaultExtensionDirectory])].map((extensionPath) => {
const normalizedDirs = normalizeExtensionDirectories(extensionDirectories)
const extensionConfigPaths = [...(normalizedDirs ?? [defaultExtensionDirectory])].map((extensionPath) => {
return joinPath(appDirectory, extensionPath, '*.extension.toml')
})
extensionConfigPaths.push(`!${joinPath(appDirectory, '**/node_modules/**')}`)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-case-declarations */
import {AppLinkedInterface} from '../../../models/app/app.js'
import {AppLinkedInterface, normalizeExtensionDirectories} from '../../../models/app/app.js'
import {configurationFileNames} from '../../../constants.js'
import {dirname, joinPath, normalizePath, relativePath} from '@shopify/cli-kit/node/path'
import {FSWatcher} from 'chokidar'
Expand Down Expand Up @@ -85,7 +85,9 @@ export class FileWatcher {
* This ensures the watcher picks up any changes in what files need to be watched.
*/
async start(): Promise<void> {
const extensionDirectories = [...(this.app.configuration.extension_directories ?? ['extensions'])]
const extensionDirectories = [
...(normalizeExtensionDirectories(this.app.configuration.extension_directories) ?? ['extensions']),
]
const fullExtensionDirectories = extensionDirectories.map((directory) => joinPath(this.app.directory, directory))
const watchPaths = [this.app.configPath, ...fullExtensionDirectories]

Expand Down
Loading