11import { watchReplay , type WatchReplayOptions , type ReplayWatchEvent } from './watch.js'
2- import { functionReplayEventSchema } from './types.js'
3- import { renderCommandEventAsJson } from '@shopify/cli-kit/node/command-events'
2+ import { functionReplayRecordSchema } from './types.js'
3+ import { emitCommandEvent } from '@shopify/cli-kit/node/command-events'
44import { formatJsonOutputTimestamp } from '@shopify/cli-kit/node/json-output-schema'
55import { outputResult , unstyled } from '@shopify/cli-kit/node/output'
6+ import { AsyncResource } from 'node:async_hooks'
67
78export async function watchReplayJson ( options : Omit < WatchReplayOptions , 'onEvent' > ) : Promise < void > {
9+ let stdoutClosed = false
10+ const onStdoutError = ( error : NodeJS . ErrnoException ) => {
11+ if ( error . code !== 'EPIPE' ) return
12+ stdoutClosed = true
13+ options . abortController . abort ( )
14+ }
815 const onSignal = ( receivedSignal : 'SIGINT' | 'SIGTERM' | 'SIGQUIT' ) => {
916 process . exitCode = { SIGINT : 130 , SIGTERM : 143 , SIGQUIT : 131 } [ receivedSignal ]
1017 options . abortController . abort ( )
1118 }
1219 process . on ( 'shopify:signal' , onSignal )
20+ process . stdout . on ( 'error' , onStdoutError )
21+ // Native watcher callbacks can run outside the command event context.
22+ const onEvent = AsyncResource . bind ( ( event : ReplayWatchEvent ) => {
23+ if ( ! stdoutClosed ) writeEvent ( event )
24+ } )
1325 try {
14- await watchReplay ( { ...options , onEvent : writeEvent } )
26+ await watchReplay ( { ...options , onEvent} )
1527 } finally {
1628 process . removeListener ( 'shopify:signal' , onSignal )
29+ process . stdout . removeListener ( 'error' , onStdoutError )
1730 }
1831}
1932
@@ -23,12 +36,12 @@ function writeEvent(event: ReplayWatchEvent): void {
2336 if ( event . type === 'diagnostic' ) {
2437 const message = unstyled ( event . message )
2538 if ( message . trim ( ) . length > 0 ) {
26- renderCommandEventAsJson ( { type : 'diagnostic' , timestamp , level : 'warning' , message} )
39+ emitCommandEvent ( { type : 'diagnostic' , level : 'warning' , message} )
2740 }
2841 return
2942 }
3043 if ( event . type === 'execution' ) {
31- functionReplayEventSchema . validate ( {
44+ functionReplayRecordSchema . validate ( {
3245 type : 'execution' ,
3346 timestamp,
3447 result : JSON . parse ( event . resultJson ) ,
@@ -43,10 +56,14 @@ function writeEvent(event: ReplayWatchEvent): void {
4356 )
4457 } else {
4558 outputResult (
46- JSON . stringify ( functionReplayEventSchema . validate ( { type : event . type , timestamp, status : event . status } ) ) ,
59+ JSON . stringify (
60+ functionReplayRecordSchema . validate ( {
61+ type : event . type ,
62+ timestamp,
63+ status : event . status ,
64+ ...( event . type === 'build' && event . status === 'failed' ? { error : unstyled ( event . error ) } : { } ) ,
65+ } ) ,
66+ ) ,
4767 )
48- if ( event . type === 'build' && event . status === 'failed' ) {
49- renderCommandEventAsJson ( { type : 'diagnostic' , timestamp, level : 'error' , message : event . error } )
50- }
5168 }
5269}
0 commit comments