Skip to content

Commit bc73a26

Browse files
authored
Merge pull request #6407 from Shopify/gg-fail-on-missing-path
Abort theme command if path doesn't exist
2 parents 0625063 + 7295ffb commit bc73a26

File tree

6 files changed

+84
-9
lines changed

6 files changed

+84
-9
lines changed

.changeset/big-ducks-joke.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/theme': minor
3+
---
4+
5+
Abort theme commands if path doesn't exist
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {themeFlags} from './flags.js'
2+
import {describe, expect, test} from 'vitest'
3+
import Command from '@shopify/cli-kit/node/base-command'
4+
import {inTemporaryDirectory} from '@shopify/cli-kit/node/fs'
5+
import {cwd, resolvePath} from '@shopify/cli-kit/node/path'
6+
import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output'
7+
8+
class MockCommand extends Command {
9+
static flags = {
10+
...themeFlags,
11+
}
12+
13+
async run(): Promise<{[flag: string]: unknown}> {
14+
const {flags} = await this.parse(MockCommand)
15+
return flags
16+
}
17+
18+
async catch(): Promise<void> {}
19+
}
20+
21+
describe('themeFlags', () => {
22+
describe('path', () => {
23+
test('defaults to the current working directory', async () => {
24+
const flags = await MockCommand.run([])
25+
26+
expect(flags.path).toEqual(cwd())
27+
})
28+
29+
test('can be expclitly provided', async () => {
30+
await inTemporaryDirectory(async (tmpDir) => {
31+
const flags = await MockCommand.run(['--path', tmpDir])
32+
33+
expect(flags.path).toEqual(resolvePath(tmpDir))
34+
})
35+
})
36+
37+
test("renders an error message and exists when the path doesn't exist", async () => {
38+
const mockOutput = mockAndCaptureOutput()
39+
40+
await MockCommand.run(['--path', 'boom'])
41+
42+
expect(mockOutput.error()).toMatch("A path was explicitly provided but doesn't exist")
43+
})
44+
})
45+
})

packages/theme/src/cli/flags.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {Flags} from '@oclif/core'
22
import {normalizeStoreFqdn} from '@shopify/cli-kit/node/context/fqdn'
33
import {resolvePath, cwd} from '@shopify/cli-kit/node/path'
4+
import {fileExistsSync} from '@shopify/cli-kit/node/fs'
5+
import {renderError} from '@shopify/cli-kit/node/ui'
46

57
/**
68
* An object that contains the flags that
@@ -10,7 +12,21 @@ export const themeFlags = {
1012
path: Flags.string({
1113
description: 'The path where you want to run the command. Defaults to the current working directory.',
1214
env: 'SHOPIFY_FLAG_PATH',
13-
parse: async (input) => resolvePath(input),
15+
parse: async (input) => {
16+
const resolvedPath = resolvePath(input)
17+
18+
if (fileExistsSync(resolvedPath)) {
19+
return resolvedPath
20+
}
21+
22+
// We can't use AbortError because oclif catches it and adds its own
23+
// messaging that breaks our UI
24+
renderError({
25+
headline: "A path was explicitly provided but doesn't exist.",
26+
body: [`Please check the path and try again: ${resolvedPath}`],
27+
})
28+
process.exit(1)
29+
},
1430
default: async () => cwd(),
1531
noCacheDefault: true,
1632
}),

packages/theme/src/cli/index.test.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {AdminSession, ensureAuthenticatedThemes} from '@shopify/cli-kit/node/ses
66
import {loadEnvironment} from '@shopify/cli-kit/node/environments'
77
import {renderConcurrent, renderConfirmationPrompt, renderError} from '@shopify/cli-kit/node/ui'
88
import {fileExistsSync} from '@shopify/cli-kit/node/fs'
9+
import {AbortError} from '@shopify/cli-kit/node/error'
910
import type {Writable} from 'stream'
1011

1112
vi.mock('@shopify/cli-kit/node/session')
@@ -121,6 +122,7 @@ describe('ThemeCommand', () => {
121122
}
122123
vi.mocked(ensureThemeStore).mockReturnValue('test-store.myshopify.com')
123124
vi.mocked(ensureAuthenticatedThemes).mockResolvedValue(mockSession)
125+
vi.mocked(fileExistsSync).mockReturnValue(true)
124126
})
125127

126128
describe('run', () => {
@@ -207,6 +209,16 @@ describe('ThemeCommand', () => {
207209
}),
208210
)
209211
})
212+
213+
test("throws an AbortError if the path doesn't exist", async () => {
214+
await CommandConfig.load()
215+
const command = new TestThemeCommand([], CommandConfig)
216+
217+
vi.mocked(fileExistsSync).mockReturnValue(false)
218+
219+
await expect(command.run()).rejects.toThrow(AbortError)
220+
expect(fileExistsSync).toHaveBeenCalledWith('current/working/directory')
221+
})
210222
})
211223

212224
describe('multi environment', () => {
@@ -483,7 +495,6 @@ describe('ThemeCommand', () => {
483495
const environmentConfig = {store: 'store.myshopify.com'}
484496
vi.mocked(loadEnvironment).mockResolvedValue(environmentConfig)
485497
vi.mocked(renderConfirmationPrompt).mockResolvedValue(true)
486-
vi.mocked(fileExistsSync).mockReturnValue(true)
487498

488499
await CommandConfig.load()
489500
const command = new TestThemeCommand(

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
renderError,
1515
} from '@shopify/cli-kit/node/ui'
1616
import {AbortController} from '@shopify/cli-kit/node/abort'
17+
import {AbortError} from '@shopify/cli-kit/node/error'
1718
import {recordEvent, compileData} from '@shopify/cli-kit/node/analytics'
1819
import {addPublicMetadata, addSensitiveMetadata} from '@shopify/cli-kit/node/metadata'
1920
import {cwd, joinPath} from '@shopify/cli-kit/node/path'
@@ -100,6 +101,10 @@ export default abstract class ThemeCommand extends Command {
100101

101102
recordEvent(`theme-command:${commandName}:single-env:authenticated`)
102103

104+
if (flags.path && !fileExistsSync(flags.path)) {
105+
throw new AbortError(`Path does not exist: ${flags.path}`)
106+
}
107+
103108
await this.command(flags, session)
104109
await this.logAnalyticsData(session)
105110
return

0 commit comments

Comments
 (0)