import chalk from 'chalk' ; import ora from 'ora' ; import { find , cloneDeep } from 'lodash' ; import { prompt } from 'inquirer' ; import { table , getBorderCharacters } from 'table' ; import untildify from 'untildify' ; import { existsSync } from 'fs-extra' ; import { createManifest } from './helpers' ; import { ServerClient } from 'postmark' ; import { TemplateManifest , TemplatePushResults , TemplatePushReview , TemplatePushArguments , Templates , ProcessTemplates , } from '../../types' ; import { pluralize , log , validateToken } from '../../utils' ; let pushManifest = [ ] ; export const command = 'push <templates directory> [options]' ; export const desc = 'Push templates from <templates directory> to a Postmark server' ; export const builder = { 'server-token' : { type : 'string' , hidden : true , } , 'request-host' : { type : 'string' , hidden : true , } , force : { type : 'boolean' , describe : 'Disable confirmation before pushing templates' , alias : 'f' , } , all : { type : 'boolean' , describe : 'Push all local templates up to Postmark regardless of whether they changed' , alias : 'a' , } , } ; export const handler = ( args ) => exec ( args ) ; /**
 * Execute the command
 */ const exec = ( args ) => { const { serverToken } = args ; return validateToken ( serverToken ) . then ( token => { validateDirectory ( token , args ) ; } ) ; } ; /**
 * Check if directory exists before pushing
 */ const validateDirectory = ( serverToken , args ) => { const rootPath = untildify ( args . templatesdirectory ) ; if ( ! existsSync ( rootPath ) ) { log ( 'The provided path does not exist' , { error : true } ) ; return process . exit ( 1 ) ; } return push ( serverToken , args ) ; } ; /**
 * Begin pushing the templates
 */ const push = ( serverToken , args ) => { const { templatesdirectory , force , requestHost , all } = args ; const spinner = ora ( 'Fetching templates...' ) . start ( ) ; const manifest = createManifest ( templatesdirectory ) ; const client = new ServerClient ( serverToken ) ; if ( requestHost !== undefined && requestHost !== '' ) { client . clientOptions . requestHost = requestHost ; } if ( manifest . length > 0 ) { client . getTemplates ( { count : 300 } ) . then ( response => { if ( response . TotalCount === 0 ) { processTemplates ( { newList : [ ] , manifest : manifest , all : all , force : force , spinner : spinner , client : client , } ) ; } else { getTemplateContent ( client , response ) . then ( newList => { processTemplates ( { newList : newList , manifest : manifest , all : all , force : force , spinner : spinner , client : client , } ) ; } ) ; } } ) . catch ( ( error ) => { spinner . stop ( ) ; log ( error , { error : true } ) ; process . exit ( 1 ) ; } ) ; } else { spinner . stop ( ) ; log ( 'No templates or layouts were found.' , { error : true } ) ; process . exit ( 1 ) ; } } ; /**
 * Compare templates and CLI flow
 */ const processTemplates = ( config ) => { const { newList , manifest , all , force , spinner , client } = config ; compareTemplates ( newList , manifest , all ) ; spinner . stop ( ) ; if ( pushManifest . length === 0 ) return log ( 'There are no changes to push.' ) ; printReview ( review ) ; if ( force ) { spinner . text = 'Pushing templates to Postmark...' ; spinner . start ( ) ; return pushTemplates ( spinner , client , pushManifest ) ; } confirmation ( ) . then ( answer => { if ( answer . confirm ) { spinner . text = 'Pushing templates to Postmark...' ; spinner . start ( ) ; pushTemplates ( spinner , client , pushManifest ) ; } else { log ( 'Canceling push. Have a good day!' ) ; } } ) ; } ; /**
 * Gather template content from server to compare against local versions
 */ const getTemplateContent = ( client , templateList ) : Promise < any > => new Promise < TemplateManifest [ ] > ( resolve => { let newList = cloneDeep ( templateList . Templates ) ; let progress = 0 ; newList . forEach ( ( template , index ) => { client . getTemplate ( template . TemplateId ) . then ( ( result ) => { newList [ index ] = { ... newList [ index ] , HtmlBody : result . HtmlBody , TextBody : result . TextBody , Subject : result . Subject , TemplateType : result . TemplateType , LayoutTemplate : result . LayoutTemplate , } ; progress ++ ; if ( progress === newList . length ) { return resolve ( newList ) ; } } ) ; } ) ; } ) ; /**
 * Ask user to confirm the push
 */ const confirmation = ( ) : Promise < any > => new Promise < string > ( ( resolve , reject ) => { prompt ( [ { type : 'confirm' , name : 'confirm' , default : false , message : `Would you like to continue?` , } , ] ) . then ( ( answer ) => resolve ( answer ) ) . catch ( ( err ) => reject ( err ) ) ; } ) ; /**
 * Compare templates on server against local
 */ const compareTemplates = ( response , manifest , pushAll ) : void => { manifest . forEach ( template => { const match = find ( response , { Alias : template . Alias } ) ; template . New = ! match ; if ( ! match ) { template . Status = chalk . green ( 'Added' ) ; return pushTemplatePreview ( match , template ) ; } const modified = wasModified ( match , template ) ; template . Status = modified ? chalk . yellow ( 'Modified' ) : chalk . gray ( 'Unmodified' ) ; if ( pushAll ) { return pushTemplatePreview ( match , template ) ; } if ( modified ) { return pushTemplatePreview ( match , template ) ; } } ) ; } ; /**
 * Check if local template is different than server
 */ const wasModified = ( server , local ) : boolean => { const htmlModified = server . HtmlBody !== local . HtmlBody ; const textModified = server . TextBody !== local . TextBody ; const subjectModified = local . TemplateType === 'Standard' ? server . Subject !== local . Subject : false ; const nameModified = server . Name !== local . Name ; const layoutModified = local . TemplateType === 'Standard' ? server . LayoutTemplate !== local . LayoutTemplate : false ; return ( htmlModified || textModified || subjectModified || nameModified || layoutModified ) ; } ; /**
 * Push template details to review table
 */ const pushTemplatePreview = ( match , template ) : number => { pushManifest . push ( template ) ; let reviewData = [ template . Status , template . Name , template . Alias ] ; if ( template . TemplateType === 'Layout' ) return review . layouts . push ( reviewData ) ; reviewData . push ( layoutUsedLabel ( template . LayoutTemplate , match ? match . LayoutTemplate : template . LayoutTemplate ) ) ; return review . templates . push ( reviewData ) ; } ; /**
 * Render the "Layout used" column for Standard templates
 */ const layoutUsedLabel = ( localLayout , serverLayout ) : string => { let label = localLayout ? localLayout : chalk . gray ( 'None' ) ; if ( localLayout !== serverLayout ) { serverLayout = serverLayout ? serverLayout : 'None' ; label += chalk . red ( `  ✘ ${ serverLayout }` ) ; } return label ; } ; /**
 * Show which templates will change after the publish
 */ const printReview = ( review ) => { const { templates , layouts } = review ; const header = [ chalk . gray ( 'Status' ) , chalk . gray ( 'Name' ) , chalk . gray ( 'Alias' ) ] ; const templatesHeader = [ ... header , chalk . gray ( 'Layout used' ) ] ; const templatesLabel = templates . length > 0 ? `${ templates . length } ${ pluralize ( templates . length , 'template' , 'templates' ) }` : '' ; const layoutsLabel = layouts . length > 0 ? `${ layouts . length } ${ pluralize ( layouts . length , 'layout' , 'layouts' ) }` : '' ; if ( templates . length > 0 ) { log ( `\n${ templatesLabel }` ) ; log ( table ( [ templatesHeader , ... templates ] , { border : getBorderCharacters ( 'norc' ) , } ) ) ; } if ( layouts . length > 0 ) { log ( `\n${ layoutsLabel }` ) ; log ( table ( [ header , ... layouts ] , { border : getBorderCharacters ( 'norc' ) } ) ) ; } log ( chalk . yellow ( `${ templatesLabel }${ templates . length > 0 && layouts . length > 0 ? ' and ' : '' }${ layoutsLabel } will be pushed to Postmark.` ) ) ; } ; /**
 * Push all local templates
 */ const pushTemplates = ( spinner , client , templates ) => { templates . forEach ( template => pushTemplate ( spinner , client , template , templates . length ) ) ; } ; /**
 * Determine whether to create a new template or edit an existing
 */ const pushTemplate = ( spinner , client , template , total ) : void => { if ( template . New ) { return client . createTemplate ( template ) . then ( ( response ) => pushComplete ( true , response , template , spinner , total ) ) . catch ( ( response ) => pushComplete ( false , response , template , spinner , total ) ) ; } return client . editTemplate ( template . Alias , template ) . then ( ( response ) => pushComplete ( true , response , template , spinner , total ) ) . catch ( ( response ) => pushComplete ( false , response , template , spinner , total ) ) ; } ; /**
 * Run each time a push has been completed
 */ const pushComplete = ( success , response , template , spinner , total ) => { results [ success ? 'success' : 'failed' ] ++ ; const completed = results . success + results . failed ; if ( ! success ) { spinner . stop ( ) ; log ( `\n${ template . Alias }: ${ response . toString ( ) }` , { error : true } ) ; spinner . start ( ) ; } if ( completed === total ) { spinner . stop ( ) ; log ( '✅ All finished!' , { color : 'green' } ) ; if ( results . failed ) { log ( `⚠️ Failed to push ${ results . failed } ${ pluralize ( results . failed , 'template' , 'templates' ) }. Please see the output above for more details.` , { error : true } ) ; } } } ; let results = { success : 0 , failed : 0 , } ; let review = { layouts : [ ] , templates : [ ] , } ;