Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1e81644
Integrate Project model into loader and app-context
ryancbahan Mar 16, 2026
e775694
Decompose loader into composable stages with narrow interfaces
ryancbahan Mar 17, 2026
e4487d9
fix rebase artifacts: conflict marker, missing import, missing config…
ryancbahan Mar 18, 2026
c2ace14
fix activeConfigFile getter: use configPath instead of configuration.…
ryancbahan Mar 18, 2026
5f12f8e
fix lint: unused import, unnecessary type assertion, extra blank line
ryancbahan Mar 18, 2026
7e87e29
address review: remove dead fallback, extract config-file-naming utility
ryancbahan Mar 19, 2026
9d589df
add integration tests for reload with new extensions mid-dev
ryancbahan Mar 19, 2026
cd3647e
fix dev reload: ensure extension dirs exist + always write manifest
ryancbahan Mar 19, 2026
2c3b45a
fix lint and type errors in project-integration tests
ryancbahan Mar 19, 2026
193d440
add unit tests for config-file-naming utility
ryancbahan Mar 19, 2026
ffeb48f
fix mkdir with glob suffixes, drop .gitkeep, add comment on client_id…
ryancbahan Mar 19, 2026
87cf67c
clean up loader: remove vestigial delete rawConfig.path, single metad…
ryancbahan Mar 19, 2026
be1b734
reduce CI memory pressure: hoist loadLocalExtensionsSpecifications
ryancbahan Mar 19, 2026
852487b
fix CI crash: catch mkdir errors in file watcher, reduce test memory
ryancbahan Mar 19, 2026
b8a27ef
fix CI: mock AppEventWatcher.start in useFunctionWatcher tests
ryancbahan Mar 19, 2026
378611c
fix lint: remove manual vi.restoreAllMocks (vitest does it automatica…
ryancbahan Mar 19, 2026
c6ef6fd
Add e2e tests for multi-config dev and hot reload
ryancbahan Mar 20, 2026
5418467
address review: remove dead _extensionDirectories param, narrow setCu…
ryancbahan Mar 20, 2026
c5eef3c
fix e2e: use undefined (not empty string) to remove SHOPIFY_FLAG_CLIE…
ryancbahan Mar 20, 2026
c5331be
address review: preserve glob semantics in config filtering, make use…
ryancbahan Mar 20, 2026
1e6e6de
add dev session test for extension created mid-dev
ryancbahan Mar 20, 2026
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
2 changes: 1 addition & 1 deletion packages/app/src/cli/commands/app/config/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default class ConfigLink extends AppLinkedCommand {
directory: flags.path,
clientId: undefined,
forceRelink: false,
userProvidedConfigName: result.state.configurationFileName,
userProvidedConfigName: result.configFileName,
})

return {app}
Expand Down
58 changes: 58 additions & 0 deletions packages/app/src/cli/models/app/config-file-naming.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
getAppConfigurationFileName,
getAppConfigurationShorthand,
isValidFormatAppConfigurationFileName,
} from './config-file-naming.js'
import {describe, expect, test} from 'vitest'

describe('getAppConfigurationFileName', () => {
test('returns default filename when no config name is provided', () => {
expect(getAppConfigurationFileName()).toBe('shopify.app.toml')
expect(getAppConfigurationFileName(undefined)).toBe('shopify.app.toml')
})

test('returns the config name as-is when it matches the valid format', () => {
expect(getAppConfigurationFileName('shopify.app.production.toml')).toBe('shopify.app.production.toml')
expect(getAppConfigurationFileName('shopify.app.staging.toml')).toBe('shopify.app.staging.toml')
expect(getAppConfigurationFileName('shopify.app.toml')).toBe('shopify.app.toml')
})

test('slugifies arbitrary strings into the filename pattern', () => {
expect(getAppConfigurationFileName('production')).toBe('shopify.app.production.toml')
expect(getAppConfigurationFileName('My Store')).toBe('shopify.app.my-store.toml')
})
})

describe('getAppConfigurationShorthand', () => {
test('returns undefined for the default config filename', () => {
expect(getAppConfigurationShorthand('shopify.app.toml')).toBeUndefined()
expect(getAppConfigurationShorthand('/some/path/shopify.app.toml')).toBeUndefined()
})

test('extracts the shorthand from a named config', () => {
expect(getAppConfigurationShorthand('shopify.app.production.toml')).toBe('production')
expect(getAppConfigurationShorthand('/path/to/shopify.app.staging.toml')).toBe('staging')
expect(getAppConfigurationShorthand('shopify.app.my-store.toml')).toBe('my-store')
})

test('returns undefined for non-matching filenames', () => {
expect(getAppConfigurationShorthand('random.toml')).toBeUndefined()
expect(getAppConfigurationShorthand('shopify.web.toml')).toBeUndefined()
})
})

describe('isValidFormatAppConfigurationFileName', () => {
test('returns true for valid app configuration filenames', () => {
expect(isValidFormatAppConfigurationFileName('shopify.app.toml')).toBe(true)
expect(isValidFormatAppConfigurationFileName('shopify.app.production.toml')).toBe(true)
expect(isValidFormatAppConfigurationFileName('shopify.app.my-store.toml')).toBe(true)
expect(isValidFormatAppConfigurationFileName('shopify.app.test_env.toml')).toBe(true)
})

test('returns false for invalid filenames', () => {
expect(isValidFormatAppConfigurationFileName('production')).toBe(false)
expect(isValidFormatAppConfigurationFileName('shopify.web.toml')).toBe(false)
expect(isValidFormatAppConfigurationFileName('shopify.app..toml')).toBe(false)
expect(isValidFormatAppConfigurationFileName('')).toBe(false)
})
})
42 changes: 42 additions & 0 deletions packages/app/src/cli/models/app/config-file-naming.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {configurationFileNames} from '../../constants.js'
import {slugify} from '@shopify/cli-kit/common/string'
import {basename} from '@shopify/cli-kit/node/path'

const appConfigurationFileNameRegex = /^shopify\.app(\.[-\w]+)?\.toml$/
export type AppConfigurationFileName = 'shopify.app.toml' | `shopify.app.${string}.toml`

/**
* Gets the name of the app configuration file (e.g. `shopify.app.production.toml`) based on a provided config name.
*
* @param configName - Optional config name to base the file name upon
* @returns Either the default app configuration file name (`shopify.app.toml`), the given config name (if it matched the valid format), or `shopify.app.<config name>.toml` if it was an arbitrary string
*/
export function getAppConfigurationFileName(configName?: string): AppConfigurationFileName {
if (!configName) {
return configurationFileNames.app
}

if (isValidFormatAppConfigurationFileName(configName)) {
return configName
} else {
return `shopify.app.${slugify(configName)}.toml`
}
}

/**
* Given a path to an app configuration file, extract the shorthand section from the file name.
*
* This is undefined for `shopify.app.toml` files, or returns e.g. `production` for `shopify.app.production.toml`.
*/
export function getAppConfigurationShorthand(path: string) {
const match = basename(path).match(appConfigurationFileNameRegex)
return match?.[1]?.slice(1)
}

/** Checks if configName is a valid one (`shopify.app.toml`, or `shopify.app.<something>.toml`) */
export function isValidFormatAppConfigurationFileName(configName: string): configName is AppConfigurationFileName {
if (appConfigurationFileNameRegex.test(configName)) {
return true
}
return false
}
Loading
Loading