Skip to content

Commit 1e43830

Browse files
committed
fix(theme): restore theme dev analytics on Ctrl+C exit
When users press Ctrl+C to exit `shopify theme dev`, the process immediately called `process.exit()`, terminating before the oclif post-run hook could send analytics events. Fix by resolving the background job promise on Ctrl+C instead of exiting directly, allowing the promise chain to complete and analytics to fire before exit.
1 parent 2f9bc85 commit 1e43830

4 files changed

Lines changed: 43 additions & 12 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/theme': patch
3+
---
4+
5+
Fix analytics not being sent when exiting `theme dev` via Ctrl+C. The process now waits for the post-run analytics hook to complete before terminating.

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

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ describe('createKeypressHandler', () => {
124124

125125
test('opens localhost when "t" is pressed', () => {
126126
// Given
127-
const handler = createKeypressHandler(urls, ctx)
127+
const handler = createKeypressHandler(urls, ctx, vi.fn())
128128

129129
// When
130130
handler('t', {name: 't'})
@@ -135,7 +135,7 @@ describe('createKeypressHandler', () => {
135135

136136
test('opens theme preview when "p" is pressed', () => {
137137
// Given
138-
const handler = createKeypressHandler(urls, ctx)
138+
const handler = createKeypressHandler(urls, ctx, vi.fn())
139139

140140
// When
141141
handler('p', {name: 'p'})
@@ -146,7 +146,7 @@ describe('createKeypressHandler', () => {
146146

147147
test('opens theme editor when "e" is pressed', () => {
148148
// Given
149-
const handler = createKeypressHandler(urls, ctx)
149+
const handler = createKeypressHandler(urls, ctx, vi.fn())
150150

151151
// When
152152
handler('e', {name: 'e'})
@@ -157,7 +157,7 @@ describe('createKeypressHandler', () => {
157157

158158
test('opens gift card preview when "g" is pressed', () => {
159159
// Given
160-
const handler = createKeypressHandler(urls, ctx)
160+
const handler = createKeypressHandler(urls, ctx, vi.fn())
161161

162162
// When
163163
handler('g', {name: 'g'})
@@ -169,7 +169,7 @@ describe('createKeypressHandler', () => {
169169
test('appends preview path to theme editor URL when lastRequestedPath is not "/"', () => {
170170
// Given
171171
const ctxWithPath = {lastRequestedPath: '/products/test-product'}
172-
const handler = createKeypressHandler(urls, ctxWithPath)
172+
const handler = createKeypressHandler(urls, ctxWithPath, vi.fn())
173173

174174
// When
175175
handler('e', {name: 'e'})
@@ -182,7 +182,7 @@ describe('createKeypressHandler', () => {
182182

183183
test('debounces rapid keypresses - only opens URL once during debounce window', () => {
184184
// Given
185-
const handler = createKeypressHandler(urls, ctx)
185+
const handler = createKeypressHandler(urls, ctx, vi.fn())
186186

187187
// When
188188
handler('t', {name: 't'})
@@ -197,7 +197,7 @@ describe('createKeypressHandler', () => {
197197

198198
test('allows keypresses after debounce period expires', () => {
199199
// Given
200-
const handler = createKeypressHandler(urls, ctx)
200+
const handler = createKeypressHandler(urls, ctx, vi.fn())
201201

202202
// When
203203
handler('t', {name: 't'})
@@ -220,7 +220,7 @@ describe('createKeypressHandler', () => {
220220

221221
test('debounces different keys during the same debounce window', () => {
222222
// Given
223-
const handler = createKeypressHandler(urls, ctx)
223+
const handler = createKeypressHandler(urls, ctx, vi.fn())
224224

225225
// When
226226
handler('t', {name: 't'})
@@ -232,4 +232,17 @@ describe('createKeypressHandler', () => {
232232
expect(openURL).toHaveBeenCalledTimes(1)
233233
expect(openURL).toHaveBeenCalledWith(urls.local)
234234
})
235+
236+
test('calls onClose when Ctrl+C is pressed', () => {
237+
// Given
238+
const onClose = vi.fn()
239+
const handler = createKeypressHandler(urls, ctx, onClose)
240+
241+
// When
242+
handler('', {ctrl: true, name: 'c'})
243+
244+
// Then
245+
expect(onClose).toHaveBeenCalledOnce()
246+
expect(openURL).not.toHaveBeenCalled()
247+
})
235248
})

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,14 @@ export async function dev(options: DevOptions) {
128128
},
129129
}
130130

131-
const {serverStart, renderDevSetupProgress, backgroundJobPromise} = setupDevServer(options.theme, ctx)
131+
const {serverStart, renderDevSetupProgress, backgroundJobPromise, resolveBackgroundJob} = setupDevServer(
132+
options.theme,
133+
ctx,
134+
)
132135

133136
readline.emitKeypressEvents(process.stdin)
134137

135-
const keypressHandler = createKeypressHandler(urls, ctx)
138+
const keypressHandler = createKeypressHandler(urls, ctx, resolveBackgroundJob)
136139
process.stdin.on('keypress', keypressHandler)
137140

138141
await Promise.all([
@@ -149,17 +152,21 @@ export async function dev(options: DevOptions) {
149152
}
150153
}),
151154
])
155+
156+
process.exit(0)
152157
}
153158

154159
export function createKeypressHandler(
155160
urls: {local: string; giftCard: string; themeEditor: string; preview: string},
156161
ctx: {lastRequestedPath: string},
162+
onClose: () => void,
157163
) {
158164
const debouncedOpenURL = debounce(openURLSafely, 100, {leading: true, trailing: false})
159165

160166
return (_str: string, key: {ctrl?: boolean; name?: string}) => {
161167
if (key.ctrl && key.name === 'c') {
162-
process.exit()
168+
onClose()
169+
return
163170
}
164171

165172
switch (key.name) {
@@ -180,6 +187,7 @@ export function createKeypressHandler(
180187
case 'g':
181188
debouncedOpenURL(urls.giftCard, 'gift card preview')
182189
break
190+
case undefined:
183191
default:
184192
break
185193
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import type {Checksum, Theme} from '@shopify/cli-kit/node/themes/types'
1818
import type {DevServerContext} from './types.js'
1919

2020
export function setupDevServer(theme: Theme, ctx: DevServerContext) {
21-
const {promise: backgroundJobPromise, reject: rejectBackgroundJob} = promiseWithResolvers<never>()
21+
const {
22+
promise: backgroundJobPromise,
23+
resolve: resolveBackgroundJob,
24+
reject: rejectBackgroundJob,
25+
} = promiseWithResolvers<void>()
2226

2327
const watcherPromise = setupInMemoryTemplateWatcher(theme, ctx)
2428
const envSetup = ensureThemeEnvironmentSetup(theme, ctx, rejectBackgroundJob)
@@ -33,6 +37,7 @@ export function setupDevServer(theme: Theme, ctx: DevServerContext) {
3337
dispatchEvent: server.dispatch,
3438
renderDevSetupProgress: envSetup.renderProgress,
3539
backgroundJobPromise,
40+
resolveBackgroundJob,
3641
}
3742
}
3843

0 commit comments

Comments
 (0)