@@ -118,6 +118,30 @@ export default class WebappDev extends SfCommand<WebAppDevResult> {
118118 }
119119 }
120120
121+ /**
122+ * Check if Vite's WebAppProxyHandler is active at the dev server URL.
123+ * The Vite plugin responds to a health check query parameter with a custom header
124+ * when the proxy middleware is active.
125+ *
126+ * @param devServerUrl - The dev server URL to check
127+ * @returns true if Vite's proxy is handling requests, false otherwise
128+ */
129+ private static async checkViteProxyActive ( devServerUrl : string ) : Promise < boolean > {
130+ try {
131+ // The Vite plugin uses a query parameter for health checks, not a path
132+ const healthUrl = new URL ( devServerUrl ) ;
133+ healthUrl . searchParams . set ( 'sfProxyHealthCheck' , 'true' ) ;
134+ const response = await fetch ( healthUrl . toString ( ) , {
135+ method : 'GET' ,
136+ signal : AbortSignal . timeout ( 3000 ) , // 3 second timeout
137+ } ) ;
138+ return response . headers . get ( 'X-Salesforce-WebApp-Proxy' ) === 'true' ;
139+ } catch {
140+ // Health check failed - Vite proxy not active
141+ return false ;
142+ }
143+ }
144+
121145 // eslint-disable-next-line complexity
122146 public async run ( ) : Promise < WebAppDevResult > {
123147 const { flags } = await this . parse ( WebappDev ) ;
@@ -290,7 +314,7 @@ export default class WebappDev extends SfCommand<WebAppDevResult> {
290314 const actualDevServerUrl = await new Promise < string > ( ( resolve , reject ) => {
291315 const timeout = setTimeout ( ( ) => {
292316 reject (
293- new SfError ( 'Dev server did not start within 30 seconds.' , 'DevServerTimeoutError' , [
317+ new SfError ( '❌ Dev server did not start within 30 seconds.' , 'DevServerTimeoutError' , [
294318 'The dev server may be taking longer than expected to start' ,
295319 'Check if the dev server command is correct in webapplication.json' ,
296320 'Try running the dev server command manually to see if it starts' ,
@@ -327,51 +351,78 @@ export default class WebappDev extends SfCommand<WebAppDevResult> {
327351 // Ensure devServerUrl is set (should always be set by step 3)
328352 if ( ! devServerUrl ) {
329353 throw new SfError (
330- 'Unable to determine dev server URL. Please specify --url or configure dev.url in webapplication.json.' ,
354+ '❌ Unable to determine dev server URL. Please specify --url or configure dev.url in webapplication.json.' ,
331355 'DevServerUrlError'
332356 ) ;
333357 }
334358
335- // Step 5: Start proxy server
336- this . logger . debug ( `Starting proxy server on port ${ flags . port } ...` ) ;
337- const salesforceInstanceUrl = orgConnection . instanceUrl ;
338- this . proxyServer = new ProxyServer ( {
339- devServerUrl,
340- salesforceInstanceUrl,
341- port : flags . port ,
342- manifest : manifest ?? undefined ,
343- orgAlias : orgUsername ,
344- } ) ;
359+ // Step 5: Check for Vite proxy and conditionally start standalone proxy
360+ this . logger . debug ( 'Checking if Vite WebApp proxy is active...' ) ;
361+ const viteProxyActive = await WebappDev . checkViteProxyActive ( devServerUrl ) ;
345362
346- await this . proxyServer . start ( ) ;
347- const proxyUrl = this . proxyServer . getProxyUrl ( ) ;
348- this . logger . debug ( `Proxy server running on ${ proxyUrl } ` ) ;
363+ // Track the final URL to open in browser (either proxy or dev server)
364+ let finalUrl : string ;
349365
350- // Listen for dev server status changes (minimal output)
351- this . proxyServer . on ( 'dev-server-up' , ( url : string ) => {
352- this . logger ?. debug ( messages . getMessage ( 'info.dev-server-detected' , [ url ] ) ) ;
353- } ) ;
366+ if ( viteProxyActive ) {
367+ // Vite's WebAppProxyHandler is handling the proxy - skip standalone proxy
368+ this . log ( messages . getMessage ( 'info.vite-proxy-detected' , [ devServerUrl ] ) ) ;
369+ this . logger . debug ( 'Vite proxy detected, skipping standalone proxy server' ) ;
370+ finalUrl = devServerUrl ;
371+ } else {
372+ // Start standalone proxy server
373+ this . logger . debug ( `Starting proxy server on port ${ flags . port } ...` ) ;
374+ const salesforceInstanceUrl = orgConnection . instanceUrl ;
375+ this . proxyServer = new ProxyServer ( {
376+ devServerUrl,
377+ salesforceInstanceUrl,
378+ port : flags . port ,
379+ manifest : manifest ?? undefined ,
380+ orgAlias : orgUsername ,
381+ } ) ;
354382
355- this . proxyServer . on ( 'dev-server-down' , ( url : string ) => {
356- this . log ( messages . getMessage ( 'warning.dev-server-unreachable-status' , [ url ] ) ) ;
357- this . log ( messages . getMessage ( 'info.start-dev-server-hint' ) ) ;
358- } ) ;
383+ await this . proxyServer . start ( ) ;
384+ const proxyUrl = this . proxyServer . getProxyUrl ( ) ;
385+ this . logger . debug ( `Proxy server running on ${ proxyUrl } ` ) ;
386+
387+ // Listen for dev server status changes (minimal output)
388+ this . proxyServer . on ( 'dev-server-up' , ( url : string ) => {
389+ this . logger ?. debug ( messages . getMessage ( 'info.dev-server-detected' , [ url ] ) ) ;
390+ } ) ;
391+
392+ this . proxyServer . on ( 'dev-server-down' , ( url : string ) => {
393+ this . log ( messages . getMessage ( 'warning.dev-server-unreachable-status' , [ url ] ) ) ;
394+ this . log ( messages . getMessage ( 'info.start-dev-server-hint' ) ) ;
395+ } ) ;
359396
360- // Step 6: Check if dev server is reachable (non-blocking warning)
361- if ( devServerUrl ) {
397+ finalUrl = proxyUrl ;
398+ }
399+
400+ // Step 6: Check if dev server is reachable (non-blocking warning) - only when using standalone proxy
401+ if ( ! viteProxyActive && devServerUrl ) {
362402 await this . checkDevServerHealth ( devServerUrl ) ;
363403 }
364404
365405 // Step 7: Open browser if requested
366406 if ( flags . open ) {
367407 this . logger . debug ( 'Opening browser...' ) ;
368- await WebappDev . openBrowser ( proxyUrl ) ;
408+ await WebappDev . openBrowser ( finalUrl ) ;
369409 }
370410
371411 // Display usage instructions
372412 this . log ( '' ) ;
373- this . log ( messages . getMessage ( 'info.ready-for-development' , [ proxyUrl ] ) ) ;
374- this . log ( messages . getMessage ( 'info.press-ctrl-c' ) ) ;
413+ if ( viteProxyActive ) {
414+ this . log ( messages . getMessage ( 'info.ready-for-development-vite' , [ devServerUrl ] ) ) ;
415+ } else {
416+ this . log ( messages . getMessage ( 'info.ready-for-development' , [ finalUrl ] ) ) ;
417+ }
418+ // Show appropriate stop message based on execution context
419+ // In TTY (interactive terminal): show "Press Ctrl+C to stop"
420+ // In non-TTY (IDE, CI, piped): show generic "Server running" message
421+ if ( process . stdout . isTTY ) {
422+ this . log ( messages . getMessage ( 'info.press-ctrl-c' ) ) ;
423+ } else {
424+ this . log ( messages . getMessage ( 'info.server-running' ) ) ;
425+ }
375426 this . log ( '' ) ;
376427
377428 // Keep the command running until interrupted or dev server exits
@@ -403,7 +454,7 @@ export default class WebappDev extends SfCommand<WebAppDevResult> {
403454
404455 // Return result (never reached, but required for type safety)
405456 return {
406- url : proxyUrl ,
457+ url : finalUrl ,
407458 devServerUrl : devServerUrl ?? '' ,
408459 } ;
409460 } catch ( error ) {
@@ -417,7 +468,7 @@ export default class WebappDev extends SfCommand<WebAppDevResult> {
417468
418469 // Wrap unknown errors
419470 const errorMessage = error instanceof Error ? error . message : String ( error ) ;
420- throw new SfError ( `Failed to start webapp dev command: ${ errorMessage } ` , 'UnexpectedError' , [
471+ throw new SfError ( `❌ Failed to start webapp dev command: ${ errorMessage } ` , 'UnexpectedError' , [
421472 'This is an unexpected error' ,
422473 'Please try again' ,
423474 'If the problem persists, check the command logs with SF_LOG_LEVEL=debug' ,
0 commit comments