@@ -10,26 +10,33 @@ const {
1010
1111const ANSI = {
1212 reset : '\x1b[0m' ,
13+ red : '\x1b[31m' ,
1314 gray : '\x1b[90m' ,
1415 green : '\x1b[32m' ,
1516 yellow : '\x1b[33m' ,
1617 cyan : '\x1b[36m' ,
17- magenta : '\x1b[35m' ,
1818} ;
1919
2020function createChangeFeedbackWatcher ( projectRoot ) {
2121 const DUPLICATE_LOG_WINDOW_MS = 2000 ;
22+ const TWIG_DEBOUNCE_MS = 90 ;
2223 const rootPath = path . resolve ( projectRoot ) ;
2324 const storefrontApp = resolveStorefrontApp ( rootPath ) ;
2425 const storefrontRequire = createStorefrontRequire ( rootPath ) ;
2526 const Watchpack = storefrontRequire ( 'watchpack' ) ;
2627 const coreOnlyHotMode = process . env . SHOPWARE_STOREFRONT_HOT_CORE_ONLY === '1' ;
2728 const disableJsCompilation = process . env . SHOPWARE_STOREFRONT_DISABLE_JS === '1' ;
29+ const jsCompileFeedbackEnabled = process . env . SHOPWARE_STOREFRONT_JS_COMPILE_FEEDBACK !== '0' ;
2830 const disableTwigWatch = process . env . SHOPWARE_STOREFRONT_DISABLE_TWIG === '1' ;
29- const coreStorefrontJsRoot = path . resolve ( storefrontApp , 'src' ) ;
3031
3132 let watchpack = null ;
3233 const recentlyLogged = new Map ( ) ;
34+ const twigState = {
35+ timer : null ,
36+ waitLogged : false ,
37+ pendingEventType : '' ,
38+ pendingFiles : new Set ( ) ,
39+ } ;
3340
3441 function hasInteractiveTty ( ) {
3542 return Boolean ( process . stdout && process . stdout . isTTY ) ;
@@ -44,7 +51,7 @@ function createChangeFeedbackWatcher(projectRoot) {
4451 }
4552
4653 function logFileEvent ( fileType , eventType , formattedFile , details = '' ) {
47- const typeColor = fileType === 'twig' ? ANSI . magenta : ANSI . cyan ;
54+ const typeColor = ANSI . cyan ;
4855 const eventColor = eventType === 'remove' ? ANSI . yellow : ANSI . green ;
4956 const typeTag = colorize ( `[${ fileType . toUpperCase ( ) } ]` , typeColor ) ;
5057 const eventTag = colorize ( `[${ eventType . toUpperCase ( ) } ]` , eventColor ) ;
@@ -53,6 +60,24 @@ function createChangeFeedbackWatcher(projectRoot) {
5360 console . log ( `[SidworksDevTools] ${ typeTag } ${ eventTag } ${ formattedFile } ${ suffix } ` ) ;
5461 }
5562
63+ function logTwigStatus ( status , message , asError = false ) {
64+ const typeTag = colorize ( '[TWIG]' , ANSI . cyan ) ;
65+ const statusColor = status === 'OK'
66+ ? ANSI . green
67+ : status === 'ERR'
68+ ? ANSI . red
69+ : ANSI . yellow ;
70+ const statusTag = colorize ( `[${ status } ]` , statusColor ) ;
71+ const line = `[SidworksDevTools] ${ typeTag } ${ statusTag } ${ message } ` ;
72+
73+ if ( asError ) {
74+ console . error ( line ) ;
75+ return ;
76+ }
77+
78+ console . log ( line ) ;
79+ }
80+
5681 function isExistingDirectory ( directoryPath ) {
5782 return fs . existsSync ( directoryPath ) && fs . statSync ( directoryPath ) . isDirectory ( ) ;
5883 }
@@ -190,9 +215,67 @@ function createChangeFeedbackWatcher(projectRoot) {
190215 return now - previous < DUPLICATE_LOG_WINDOW_MS ;
191216 }
192217
193- function isCoreStorefrontJsFile ( filePath ) {
194- const normalizedFile = path . resolve ( filePath ) ;
195- return normalizedFile . startsWith ( coreStorefrontJsRoot + path . sep ) ;
218+ function summarizeFiles ( files ) {
219+ const uniqueFiles = [ ...new Set ( ( files || [ ] ) . filter ( ( file ) => typeof file === 'string' && file !== '' ) ) ] ;
220+ if ( uniqueFiles . length === 0 ) {
221+ return '' ;
222+ }
223+
224+ if ( uniqueFiles . length <= 3 ) {
225+ return uniqueFiles . join ( ', ' ) ;
226+ }
227+
228+ return `${ uniqueFiles . slice ( 0 , 3 ) . join ( ', ' ) } +${ uniqueFiles . length - 3 } more` ;
229+ }
230+
231+ function rememberTwigPending ( eventType , formattedFile ) {
232+ if ( typeof eventType === 'string' && eventType !== '' ) {
233+ twigState . pendingEventType = eventType ;
234+ }
235+
236+ if ( typeof formattedFile === 'string' && formattedFile !== '' ) {
237+ twigState . pendingFiles . add ( formattedFile ) ;
238+ }
239+ }
240+
241+ function formatTwigReasonLabel ( ) {
242+ const trigger = twigState . pendingEventType || 'change' ;
243+ const fileSummary = summarizeFiles ( [ ...twigState . pendingFiles ] ) ;
244+ return fileSummary ? `${ trigger } : ${ fileSummary } ` : trigger ;
245+ }
246+
247+ function flushTwigReloadFeedback ( ) {
248+ const reasonLabel = formatTwigReasonLabel ( ) ;
249+ const startedAt = Date . now ( ) ;
250+
251+ twigState . pendingEventType = '' ;
252+ twigState . pendingFiles . clear ( ) ;
253+ twigState . waitLogged = false ;
254+
255+ logTwigStatus ( 'RUN' , `reloading (${ reasonLabel } )` ) ;
256+ logTwigStatus ( 'OK' , `reloaded (${ reasonLabel } ) in ${ Date . now ( ) - startedAt } ms` ) ;
257+ }
258+
259+ function scheduleTwigReloadFeedback ( eventType , formattedFile ) {
260+ rememberTwigPending ( eventType , formattedFile ) ;
261+
262+ if ( twigState . timer ) {
263+ if ( ! twigState . waitLogged ) {
264+ const queuedFiles = summarizeFiles ( [ ...twigState . pendingFiles ] ) ;
265+ if ( queuedFiles ) {
266+ logTwigStatus ( 'WAIT' , `change queued while reload is running (${ queuedFiles } )` ) ;
267+ } else {
268+ logTwigStatus ( 'WAIT' , 'change queued while reload is running' ) ;
269+ }
270+ twigState . waitLogged = true ;
271+ }
272+ return ;
273+ }
274+
275+ twigState . timer = setTimeout ( ( ) => {
276+ twigState . timer = null ;
277+ flushTwigReloadFeedback ( ) ;
278+ } , TWIG_DEBOUNCE_MS ) ;
196279 }
197280
198281 function handleFileEvent ( eventType , absoluteFilePath ) {
@@ -202,13 +285,12 @@ function createChangeFeedbackWatcher(projectRoot) {
202285 }
203286
204287 const formattedFile = formatFilePath ( absoluteFilePath ) ;
205- if ( ! formattedFile || shouldSkipDuplicate ( eventType , formattedFile ) ) {
288+ if ( ! formattedFile ) {
206289 return ;
207290 }
208291
209292 if ( fileType === 'js' ) {
210- if ( isCoreStorefrontJsFile ( absoluteFilePath ) && ! disableJsCompilation ) {
211- // Core storefront JS is already logged via webpack compiler hooks.
293+ if ( shouldSkipDuplicate ( eventType , formattedFile ) ) {
212294 return ;
213295 }
214296
@@ -222,17 +304,25 @@ function createChangeFeedbackWatcher(projectRoot) {
222304 return ;
223305 }
224306
307+ if ( jsCompileFeedbackEnabled ) {
308+ return ;
309+ }
310+
225311 logFileEvent ( 'js' , eventType , formattedFile ) ;
226312 return ;
227313 }
228314
229315 if ( fileType === 'twig' ) {
230316 if ( disableTwigWatch ) {
317+ if ( shouldSkipDuplicate ( eventType , formattedFile ) ) {
318+ return ;
319+ }
320+
231321 logFileEvent ( 'twig' , eventType , formattedFile , '(skipped: --no-twig)' ) ;
232322 return ;
233323 }
234324
235- logFileEvent ( 'twig' , eventType , formattedFile , '(live reload)' ) ;
325+ scheduleTwigReloadFeedback ( eventType , formattedFile ) ;
236326 }
237327 }
238328
@@ -263,6 +353,11 @@ function createChangeFeedbackWatcher(projectRoot) {
263353 }
264354
265355 function close ( ) {
356+ if ( twigState . timer ) {
357+ clearTimeout ( twigState . timer ) ;
358+ twigState . timer = null ;
359+ }
360+
266361 if ( watchpack ) {
267362 watchpack . close ( ) ;
268363 watchpack = null ;
0 commit comments