@@ -5,16 +5,41 @@ import {getProxyHandler} from './proxy.js'
55import { reconcileAndPollThemeEditorChanges } from './remote-theme-watcher.js'
66import { uploadTheme } from '../theme-uploader.js'
77import { renderTasksToStdErr } from '../theme-ui.js'
8- import { createAbortCatchError } from '../errors.js'
8+ import { renderThrownError } from '../errors.js'
99import { createApp , defineEventHandler , defineLazyEventHandler , toNodeListener , handleCors } from 'h3'
1010import { fetchChecksums } from '@shopify/cli-kit/node/themes/api'
1111import { createServer } from 'node:http'
1212import type { Checksum , Theme } from '@shopify/cli-kit/node/themes/types'
1313import type { DevServerContext } from './types.js'
1414
15+ // Polyfill for Promise.withResolvers
16+ // Can remove once our minimum supported Node version is 22
17+ interface PromiseWithResolvers < T > {
18+ promise : Promise < T >
19+ resolve : ( value : T | PromiseLike < T > ) => void
20+ reject : ( reason ?: unknown ) => void
21+ }
22+
23+ function promiseWithResolvers < T > ( ) : PromiseWithResolvers < T > {
24+ if ( typeof Promise . withResolvers === 'function' ) {
25+ return Promise . withResolvers < T > ( )
26+ }
27+
28+ let resolve ! : ( value : T | PromiseLike < T > ) => void
29+ let reject ! : ( reason ?: unknown ) => void
30+ const promise = new Promise < T > ( ( _resolve , _reject ) => {
31+ resolve = _resolve
32+ reject = _reject
33+ } )
34+
35+ return { promise, resolve, reject}
36+ }
37+
1538export function setupDevServer ( theme : Theme , ctx : DevServerContext ) {
39+ const { promise : backgroundJobPromise , reject : rejectBackgroundJob } = promiseWithResolvers < never > ( )
40+
1641 const watcherPromise = setupInMemoryTemplateWatcher ( theme , ctx )
17- const envSetup = ensureThemeEnvironmentSetup ( theme , ctx )
42+ const envSetup = ensureThemeEnvironmentSetup ( theme , ctx , rejectBackgroundJob )
1843 const workPromise = Promise . all ( [ watcherPromise , envSetup . workPromise ] ) . then ( ( ) =>
1944 ctx . localThemeFileSystem . startWatcher ( theme . id . toString ( ) , ctx . session ) ,
2045 )
@@ -25,16 +50,25 @@ export function setupDevServer(theme: Theme, ctx: DevServerContext) {
2550 serverStart : server . start ,
2651 dispatchEvent : server . dispatch ,
2752 renderDevSetupProgress : envSetup . renderProgress ,
53+ backgroundJobPromise,
2854 }
2955}
3056
31- function ensureThemeEnvironmentSetup ( theme : Theme , ctx : DevServerContext ) {
32- const abort = createAbortCatchError ( 'Failed to perform the initial theme synchronization.' )
57+ function ensureThemeEnvironmentSetup (
58+ theme : Theme ,
59+ ctx : DevServerContext ,
60+ rejectBackgroundJob : ( reason ?: unknown ) => void ,
61+ ) {
62+ const abort = ( error : Error ) : never => {
63+ renderThrownError ( 'Failed to perform the initial theme synchronization.' , error )
64+ rejectBackgroundJob ( error )
65+ throw error
66+ }
3367
3468 const remoteChecksumsPromise = fetchChecksums ( theme . id , ctx . session ) . catch ( abort )
3569
3670 const reconcilePromise = remoteChecksumsPromise
37- . then ( ( remoteChecksums ) => handleThemeEditorSync ( theme , ctx , remoteChecksums ) )
71+ . then ( ( remoteChecksums ) => handleThemeEditorSync ( theme , ctx , remoteChecksums , rejectBackgroundJob ) )
3872 . catch ( abort )
3973
4074 const uploadPromise = reconcilePromise
@@ -74,16 +108,24 @@ function handleThemeEditorSync(
74108 theme : Theme ,
75109 ctx : DevServerContext ,
76110 remoteChecksums : Checksum [ ] ,
111+ rejectBackgroundJob : ( reason ?: unknown ) => void ,
77112) : Promise < {
78113 updatedRemoteChecksumsPromise : Promise < Checksum [ ] >
79114 workPromise : Promise < void >
80115} > {
81116 if ( ctx . options . themeEditorSync ) {
82- return reconcileAndPollThemeEditorChanges ( theme , ctx . session , remoteChecksums , ctx . localThemeFileSystem , {
83- noDelete : ctx . options . noDelete ,
84- ignore : ctx . options . ignore ,
85- only : ctx . options . only ,
86- } )
117+ return reconcileAndPollThemeEditorChanges (
118+ theme ,
119+ ctx . session ,
120+ remoteChecksums ,
121+ ctx . localThemeFileSystem ,
122+ {
123+ noDelete : ctx . options . noDelete ,
124+ ignore : ctx . options . ignore ,
125+ only : ctx . options . only ,
126+ } ,
127+ rejectBackgroundJob ,
128+ )
87129 } else {
88130 return Promise . resolve ( {
89131 updatedRemoteChecksumsPromise : Promise . resolve ( remoteChecksums ) ,
0 commit comments