11import  {  lstat  }  from  'node:fs/promises' ; 
22
3- import  {  doIUseEmail   }  from  '@jsx-email/doiuse-email ' ; 
3+ import  {  type   IssueGroup ,   caniemail ,   groupIssues ,   sortIssues   }  from  'caniemail ' ; 
44import  chalk  from  'chalk' ; 
55import  chalkTmpl  from  'chalk-template' ; 
6- import  {  parse  as  assert ,  object ,  type  InferOutput  as  Infer  }  from  'valibot' ; 
6+ import  stripAnsi  from  'strip-ansi' ; 
7+ import  {  type  InferOutput  as  Infer ,  parse  as  assert ,  object  }  from  'valibot' ; 
78
89import  {  formatBytes ,  gmailByteLimit ,  gmailBytesSafe  }  from  '../helpers.js' ; 
910
@@ -25,28 +26,6 @@ const emailClients = [
2526  'fastmail.*' 
2627] ; 
2728
28- const  formatSubject  =  ( what : string )  => 
29-   what . replace ( / ` ( [ \s \w < > . - ] + ) ` / g,  ( _ ,  bit )  =>  chalkTmpl `{bold ${ bit }  ) . trim ( ) ; 
30- 
31- const  combine  =  ( lines : string [ ] )  =>  { 
32-   const  rePreamble  =  / ( ` ( [ \s \w < > . - ] + ) ` [ \s \w ] + ) / ; 
33- 
34-   const  result  =  lines . reduce < Record < string ,  string [ ] > > ( ( prev ,  curr )  =>  { 
35-     const  matches  =  curr . match ( rePreamble ) ; 
36-     const  [ preamble ]  =  matches ! ; 
37- 
38-     prev [ preamble ]  =  ( prev [ preamble ]  ||  [ ] ) . concat ( curr . replace ( rePreamble ,  '' ) . replace ( / ` / g,  '' ) ) ; 
39- 
40-     return  prev ; 
41-   } ,  { } ) ; 
42- 
43-   for  ( const  key  of  Object . keys ( result ) )  { 
44-     result [ key ]  =  Array . from ( new  Set ( result [ key ] ) ) ; 
45-   } 
46- 
47-   return  result ; 
48- } ; 
49- 
5029export  const  help  =  chalkTmpl ` 
5130{blue email check} 
5231
@@ -59,59 +38,76 @@ Check jsx-email templates for client compatibility
5938  $ email check ./emails/Batman.tsx 
6039` ; 
6140
41+ const  formatNotes  =  ( notes : string [ ] ,  indent : string )  =>  { 
42+   if  ( ! notes . length )  return  '' ; 
43+   const  noteLines  =  ( notes  as  string [ ] ) . join ( `\n${ '.' . repeat ( indent . length ) }  ) ; 
44+   console . log ( {  noteLines } ) ; 
45+   return  chalkTmpl `\n${ indent } ${ '.' . repeat ( indent . length ) }  ; 
46+ } ; 
47+ 
48+ const  formatIssue  =  ( group : IssueGroup ) : string  =>  { 
49+   const  {  issue,  clients }  =  group ; 
50+   const  {  position,  support,  title }  =  issue ; 
51+   const  positionTuple  =  chalkTmpl `{dim ${ position ! . start . line } ${ position ! . start . column }  ; 
52+   const  notes  =  issue . notes . length  ? issue . notes . map ( ( note ,  index )  =>  `${ index  +  1 } ${ note }  )  : [ ] ; 
53+   let  lineType  =  '' ; 
54+ 
55+   if  ( support  ===  'none' )  { 
56+     lineType  =  chalkTmpl `  {red error}` ; 
57+   }  else  { 
58+     lineType  =  chalkTmpl `  {yellow warn} ` ; 
59+   } 
60+ 
61+   const  preamble  =  `${ lineType } ${ positionTuple }  ; 
62+   const  indent  =  ' ' . repeat ( stripAnsi ( preamble ) . length  +  2 ) ; 
63+   const  footnotes  =  formatNotes ( notes ,  indent ) ; 
64+ 
65+   return  chalkTmpl `${ preamble } ${ title } ${ footnotes } ${ indent } ${ clients . join ( `\n${ indent }  ) }  ; 
66+ } ; 
67+ 
6268const  runCheck  =  ( fileName : string ,  html : string )  =>  { 
6369  const  bytes  =  Buffer . byteLength ( html ,  'utf8' ) ; 
64-   const  counts  =  {  errors : 0 ,  notes : 0 ,  warnings : 0  } ; 
6570  const  htmlSize  =  formatBytes ( bytes ) ; 
66-   const  result  =  doIUseEmail ( html ,  {  emailClients } ) ; 
67-   const  {  success }  =  result ; 
71+   const  result  =  caniemail ( {  clients : emailClients  as  any ,  html } ) ; 
72+   const  {  issues,  success }  =  result ; 
73+   const  {  errors,  warnings }  =  issues ; 
74+   const  counts  =  { 
75+     errors : 0 , 
76+     notes : 0 , 
77+     warnings : 0 
78+   } ; 
6879
69-   if  ( success  &&  ! result . warnings )  return ; 
80+   if  ( success  &&  ! issues . warnings )  return ; 
7081
7182  log ( chalkTmpl `{underline ${ fileName } ${ htmlSize }  ) ; 
7283
73-   if  ( ! success  &&  result . errors ?. length )  { 
74-     const  errors  =  combine ( result . errors ) ; 
75-     const  indent  =  '           ' ; 
76-     for  ( const  [ preamble ,  clients ]  of  Object . entries ( errors ) )  { 
77-       log ( 
78-         chalkTmpl `  {red error}  ${ formatSubject ( preamble ) } ${ indent } ${ clients . join (  
79-           `\n${ indent }   
80-         ) }  }\n`
81-       ) ; 
82- 
83-       counts . errors  +=  1 ; 
84-     } 
85-   } 
86- 
8784  if  ( bytes  >=  gmailByteLimit )  { 
8885    log ( chalkTmpl `  {red error}  HTML content is over the Gmail Clipping Limit: ${ htmlSize }  ) ; 
8986    counts . errors  +=  1 ; 
87+   }  else  if  ( bytes  >  gmailBytesSafe  &&  bytes  <  gmailByteLimit )  { 
88+     log ( chalkTmpl `  {red warn}  HTML content is near the Gmail Clipping Limit: ${ htmlSize }  ) ; 
89+     counts . warnings  +=  1 ; 
9090  } 
9191
92-   if  ( result . warnings ?. length )  { 
93-     const  warnings  =  combine ( result . warnings ) ; 
94-     const  indent  =  '          ' ; 
95-     for  ( const  [ preamble ,  clients ]  of  Object . entries ( warnings ) )  { 
96-       log ( 
97-         chalkTmpl `  {yellow warn}  ${ formatSubject ( preamble ) } ${ indent } ${ clients . join (  
98-           `\n${ indent }   
99-         ) }  }\n`
100-       ) ; 
101-       counts . warnings  +=  1 ; 
102-     } 
103-   } 
92+   if  ( errors ?. size  ||  warnings ?. size )  { 
93+     const  groupedErrors  =  groupIssues ( errors ) ; 
94+     const  groupedWarnings  =  groupIssues ( warnings ) ; 
95+     const  sorted  =  sortIssues ( [ ...groupedErrors ,  ...groupedWarnings ] ) ; 
10496
105-   if  ( bytes  >  gmailBytesSafe  &&  bytes  <  gmailByteLimit )  { 
106-     log ( chalkTmpl `  {red warn}  HTML content is near the Gmail Clipping Limit: ${ htmlSize }  ) ; 
107-     counts . warnings  +=  1 ; 
97+     for  ( const  group  of  sorted )  { 
98+       if  ( group . issue . support  ===  'none' )  counts . errors  +=  1 ; 
99+       if  ( group . issue . support  ===  'partial' )  counts . warnings  +=  1 ; 
100+       log ( formatIssue ( group ) ) ; 
101+     } 
108102  } 
109103
110-   const  errors  =  counts . errors  >  0  ? chalk . red ( counts . errors )  : chalk . green ( counts . errors ) ; 
111-   const  warnings  = 
104+   const  errorCount  =  counts . errors  >  0  ? chalk . red ( counts . errors )  : chalk . green ( counts . errors ) ; 
105+   const  warningCount  = 
112106    counts . warnings  >  0  ? chalk . yellow ( counts . warnings )  : chalk . green ( counts . warnings ) ; 
113107
114-   log ( chalkTmpl `{green Check Complete:} ${ errors } ${ warnings }  ) ; 
108+   log ( 
109+     chalkTmpl ` {green {bold ✓}} {dim Check Complete}: ${ errorCount } ${ warningCount }  
110+   ) ; 
115111} ; 
116112
117113export  const  command : CommandFn  =  async  ( argv : CheckOptions ,  input )  =>  { 
0 commit comments