Skip to content

Commit db3c204

Browse files
committed
validate listing exists
1 parent 202158a commit db3c204

File tree

4 files changed

+79
-2
lines changed

4 files changed

+79
-2
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {isStorefrontPasswordProtected} from '../utilities/theme-environment/stor
66
import {ensureValidPassword} from '../utilities/theme-environment/storefront-password-prompt.js'
77
import {emptyThemeExtFileSystem} from '../utilities/theme-fs-empty.js'
88
import {initializeDevServerSession} from '../utilities/theme-environment/dev-server-session.js'
9+
import {ensureListingExists} from '../utilities/theme-listing.js'
910
import {renderSuccess, renderWarning} from '@shopify/cli-kit/node/ui'
1011
import {AdminSession} from '@shopify/cli-kit/node/session'
1112
import {Theme} from '@shopify/cli-kit/node/themes/types'
@@ -44,6 +45,10 @@ export async function dev(options: DevOptions) {
4445
return
4546
}
4647

48+
if (options.listing) {
49+
await ensureListingExists(options.directory, options.listing)
50+
}
51+
4752
const storefrontPasswordPromise = await isStorefrontPasswordProtected(options.adminSession).then((needsPassword) =>
4853
needsPassword ? ensureValidPassword(options.storePassword, options.adminSession.storeFqdn) : undefined,
4954
)

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {findOrSelectTheme} from '../utilities/theme-selector.js'
88
import {Role} from '../utilities/theme-selector/fetch.js'
99
import {configureCLIEnvironment} from '../utilities/cli-config.js'
1010
import {runThemeCheck} from '../commands/theme/check.js'
11+
import {ensureListingExists} from '../utilities/theme-listing.js'
1112
import {AdminSession, ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session'
1213
import {themeCreate, fetchChecksums, themePublish} from '@shopify/cli-kit/node/themes/api'
1314
import {Result, Theme} from '@shopify/cli-kit/node/themes/types'
@@ -139,6 +140,10 @@ export async function push(flags: PushFlags): Promise<void> {
139140
return
140141
}
141142

143+
if (flags.listing) {
144+
await ensureListingExists(workingDirectory, flags.listing)
145+
}
146+
142147
const selectedTheme: Theme | undefined = await createOrSelectTheme(adminSession, flags)
143148
if (!selectedTheme) {
144149
return

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {getListingFilePath, updateSettingsDataForListing} from './theme-listing.js'
1+
import {getListingFilePath, updateSettingsDataForListing, ensureListingExists} from './theme-listing.js'
22
import {test, describe, expect} from 'vitest'
33
import {inTemporaryDirectory, mkdir, writeFile} from '@shopify/cli-kit/node/fs'
44
import {joinPath} from '@shopify/cli-kit/node/path'
@@ -151,4 +151,46 @@ describe('theme-listing', () => {
151151
})
152152
})
153153
})
154+
155+
describe('ensureListingExists', () => {
156+
test('resolves when the listing preset directory exists', async () => {
157+
await inTemporaryDirectory(async (tmpDir) => {
158+
// Given
159+
const themeDir = joinPath(tmpDir, 'theme')
160+
const listingDir = joinPath(themeDir, 'listings', 'modern')
161+
await mkdir(listingDir, {recursive: true})
162+
163+
// When / Then (no throw)
164+
await ensureListingExists(themeDir, 'modern')
165+
})
166+
})
167+
168+
test('throws with available presets listed when the preset is missing', async () => {
169+
await inTemporaryDirectory(async (tmpDir) => {
170+
// Given
171+
const themeDir = joinPath(tmpDir, 'theme')
172+
const listingsRoot = joinPath(themeDir, 'listings')
173+
await mkdir(joinPath(listingsRoot, 'modern'), {recursive: true})
174+
await mkdir(joinPath(listingsRoot, 'classic'), {recursive: true})
175+
176+
// When / Then
177+
await expect(ensureListingExists(themeDir, 'unknown')).rejects.toThrow(
178+
'Listing preset "unknown" was not found.',
179+
)
180+
181+
await expect(ensureListingExists(themeDir, 'unknown')).rejects.toThrow('Available presets: "Modern"')
182+
await expect(ensureListingExists(themeDir, 'unknown')).rejects.toThrow('"Classic"')
183+
})
184+
})
185+
186+
test('throws with no presets message when listings directory does not exist', async () => {
187+
await inTemporaryDirectory(async (tmpDir) => {
188+
// Given
189+
const themeDir = joinPath(tmpDir, 'theme')
190+
191+
// When / Then
192+
await expect(ensureListingExists(themeDir, 'unknown')).rejects.toThrow('No presets found under "listings/"')
193+
})
194+
})
195+
})
154196
})

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import {fileExists, readFile} from '@shopify/cli-kit/node/fs'
1+
import {fileExists, readFile, glob} from '@shopify/cli-kit/node/fs'
22
import {joinPath} from '@shopify/cli-kit/node/path'
33
import {capitalizeWords} from '@shopify/cli-kit/common/string'
4+
import {AbortError} from '@shopify/cli-kit/node/error'
45

56
function isListingFile(fileKey: string): boolean {
67
return (fileKey.startsWith('templates/') || fileKey.startsWith('sections/')) && fileKey.endsWith('.json')
@@ -40,3 +41,27 @@ export async function updateSettingsDataForListing(themeDirectory: string, listi
4041
throw error
4142
}
4243
}
44+
45+
export async function ensureListingExists(themeDirectory: string, listingName: string): Promise<void> {
46+
const listingsRoot = joinPath(themeDirectory, 'listings')
47+
const dir = joinPath(listingsRoot, listingName)
48+
const exists = await fileExists(dir)
49+
if (exists) return
50+
51+
let available: string[] = []
52+
if (await fileExists(listingsRoot)) {
53+
available = await glob('*', {cwd: listingsRoot, onlyDirectories: true, deep: 1})
54+
}
55+
56+
const availablePresetsMessage =
57+
available.length > 0
58+
? `Available presets: ${available
59+
.map((presetDirectoryName) => `"${capitalizeWords(presetDirectoryName)}"`)
60+
.join(', ')}`
61+
: 'No presets found under "listings/"'
62+
63+
throw new AbortError(
64+
`Listing preset "${listingName}" was not found. ${availablePresetsMessage}.`,
65+
`Add the preset to config/settings_data.json and its corresponding "listings/${listingName}" folder, or remove the --listing flag to use the default theme settings.`,
66+
)
67+
}

0 commit comments

Comments
 (0)