import chalk from 'chalk' ; import { existsSync } from 'fs-extra' ; import { filter , find , replace , debounce } from 'lodash' ; import untildify from 'untildify' ; import express from 'express' ; import { createMonitor } from 'watch' ; import consolidate from 'consolidate' ; import { ServerClient } from 'postmark' ; import { createManifest } from './helpers' ; import { TemplatePreviewArguments } from '../../types' ; import { TemplateValidationOptions } from 'postmark/dist/client/models' ; import { log , validateToken } from '../../utils' ; import path from 'path' ; const previewPath = path . join ( __dirname , 'preview' ) ; const templateLinks = '<base target="_blank" />' ; export const command = 'preview  <templates directory> [options]' ; export const desc = 'Preview your templates and layouts' ; export const builder = { 'server-token' : { type : 'string' , hidden : true , } , port : { type : 'number' , describe : 'The port to open up the preview server on' , default : 3005 , alias : 'p' , } , } ; export const handler = ( args ) => exec ( args ) ; /**
 * Execute the command
 */ const exec = ( args ) => { const { serverToken } = args ; return validateToken ( serverToken ) . then ( token => { validateDirectory ( token , args ) ; } ) ; } ; const validateDirectory = ( serverToken , args ) => { const { templatesdirectory } = args ; const rootPath = untildify ( templatesdirectory ) ; if ( ! existsSync ( rootPath ) ) { log ( 'The provided path does not exist' , { error : true } ) ; return process . exit ( 1 ) ; } return preview ( serverToken , args ) ; } ; /**
 * Preview
 */ const preview = ( serverToken , args ) => { const { port , templatesdirectory } = args ; log ( `${ title } Starting template preview server...` ) ; const app = express ( ) ; const server = require ( 'http' ) . createServer ( app ) ; const io = require ( 'socket.io' ) ( server ) ; const client = new ServerClient ( serverToken ) ; let manifest = createManifest ( templatesdirectory ) ; app . use ( express . static ( `${ previewPath }/assets` ) ) ; const updateEvent = ( ) => { manifest = createManifest ( templatesdirectory ) ; log ( `${ title } File changed. Reloading browser...` ) ; io . emit ( 'change' ) ; } ; createMonitor ( untildify ( templatesdirectory ) , { interval : 2 } , monitor => { monitor . on ( 'created' , debounce ( updateEvent , 1000 ) ) ; monitor . on ( 'changed' , debounce ( updateEvent , 1000 ) ) ; monitor . on ( 'removed' , debounce ( updateEvent , 1000 ) ) ; } ) ; app . get ( '/' , ( req , res ) => { manifest = createManifest ( templatesdirectory ) ; const templates = filter ( manifest , { TemplateType : 'Standard' } ) ; const layouts = filter ( manifest , { TemplateType : 'Layout' } ) ; const path = untildify ( templatesdirectory ) . replace ( /\/$/ , '' ) ; consolidate . ejs ( `${ previewPath }/index.ejs` , { templates , layouts , path } , ( err , html ) => renderTemplateContents ( res , err , html ) ) ; } ) ; /**
     * Get template by alias
     */ app . get ( '/:alias' , ( req , res ) => { const template = find ( manifest , { Alias : req . params . alias } ) ; if ( template ) { consolidate . ejs ( `${ previewPath }/template.ejs` , { template } , ( err , html ) => renderTemplateContents ( res , err , html ) ) ; } else { return res . redirect ( 301 , '/' ) ; } } ) ; /**
     * Get template HTML version by alias
     */ app . get ( '/html/:alias' , ( req , res ) => { const template = find ( manifest , { Alias : req . params . alias } ) ; if ( template && template . HtmlBody ) { const layout = find ( manifest , { Alias : template . LayoutTemplate } ) ; if ( layout && ! layout . HtmlBody ) return renderTemplateInvalid ( res , layoutError ) ; const { TemplateType , TestRenderModel } = template ; const payload = { HtmlBody : getSource ( 'html' , template , layout ) , TemplateType , TestRenderModel , Subject : template . Subject , } ; return validateTemplateRequest ( 'html' , payload , res ) ; } else { return renderTemplate404 ( res , 'HTML' ) ; } } ) ; /**
     * Get template text version by alias
     */ app . get ( '/text/:alias' , ( req , res ) => { const template = find ( manifest , { Alias : req . params . alias } ) ; if ( template && template . TextBody ) { const layout = find ( manifest , { Alias : template . LayoutTemplate } ) ; if ( layout && ! layout . TextBody ) return renderTemplateInvalid ( res , layoutError ) ; const { TemplateType , TestRenderModel } = template ; const payload = { TextBody : getSource ( 'text' , template , layout ) , TemplateType , TestRenderModel , } ; return validateTemplateRequest ( 'text' , payload , res ) ; } else { return renderTemplate404 ( res , 'Text' ) ; } } ) ; server . listen ( port , ( ) => { const url = `http://localhost:${ port }` ; log ( `${ title } Template preview server ready. Happy coding!` ) ; log ( divider ) ; log ( `URL: ${ chalk . green ( url ) }` ) ; log ( divider ) ; } ) ; const validateTemplateRequest = ( version , payload , res ) => { const versionKey = version === 'html' ? 'HtmlBody' : 'TextBody' ; client . validateTemplate ( payload ) . then ( result => { if ( result [ versionKey ] . ContentIsValid ) { const renderedContent = result [ versionKey ] . RenderedContent + templateLinks ; io . emit ( 'subject' , { ... result . Subject , rawSubject : payload . Subject } ) ; if ( version === 'html' ) { return res . send ( renderedContent ) ; } else { return renderTemplateText ( res , renderedContent ) ; } } return renderTemplateInvalid ( res , result [ versionKey ] . ValidationErrors ) ; } ) . catch ( error => { return res . status ( 500 ) . send ( error ) ; } ) ; } ; } ; const combineTemplate = ( layout , template ) : string => replace ( layout , /({{{)(.?@content.?)(}}})/g , template ) ; const title = `${ chalk . yellow ( 'ﾐ▢ Postmark' ) }${ chalk . gray ( ':' ) }` ; const divider = chalk . gray ( '-' . repeat ( 34 ) ) ; const getSource = ( version , template , layout ? ) => { const versionKey = version === 'html' ? 'HtmlBody' : 'TextBody' ; if ( layout ) return combineTemplate ( layout [ versionKey ] , template [ versionKey ] ) ; return template [ versionKey ] ; } ; const renderTemplateText = ( res , body ) => consolidate . ejs ( `${ previewPath }/templateText.ejs` , { body } , ( err , html ) => renderTemplateContents ( res , err , html ) ) ; const renderTemplateInvalid = ( res , errors ) => consolidate . ejs ( `${ previewPath }/templateInvalid.ejs` , { errors } , ( err , html ) => renderTemplateContents ( res , err , html ) ) ; const renderTemplate404 = ( res , version ) => consolidate . ejs ( `${ previewPath }/template404.ejs` , { version } , ( err , html ) => renderTemplateContents ( res , err , html ) ) ; const renderTemplateContents = ( res , err , html ) => { if ( err ) return res . send ( err ) ; return res . send ( html ) ; } ; const layoutError = [ { Message : 'A TemplateLayout is specified, but it is either empty or missing.' , } , ] ;