From 19bc8c586179e53489f0f40f2b5baf1cb4c73f44 Mon Sep 17 00:00:00 2001 From: Randy Tarampi Date: Sat, 8 Jul 2017 14:10:49 -0700 Subject: [PATCH 1/4] Add the ability for `mailmason` to generate and upload/update templates. --- Gruntfile.js | 38 +++- example_config.json | 3 + package.json | 4 +- previews.html | 169 ++++++++++-------- tasks/grunt-postmark-templates-generate.js | 50 ++++++ ...unt-postmark-templates-reload-templates.js | 17 ++ templates.json | 52 ++++++ 7 files changed, 252 insertions(+), 81 deletions(-) create mode 100644 tasks/grunt-postmark-templates-generate.js create mode 100644 tasks/grunt-postmark-templates-reload-templates.js create mode 100644 templates.json diff --git a/Gruntfile.js b/Gruntfile.js index 05df9e6..ecd5b42 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -6,6 +6,13 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-ftp-deploy'); + var secret = grunt.file.exists('secrets.json') ? grunt.file.readJSON('secrets.json') : {}; + var config = Object.assign({ + templates: { + file: 'templates.json' + } + }, grunt.file.readJSON('config.json')); + var path = { css_src: 'src/stylesheets/global.scss', css_dest: 'src/stylesheets/global.css', @@ -30,8 +37,8 @@ module.exports = function(grunt) { common changes centralized to key files instead of being littered throughout the Gruntfile. This also makes it easy to .gitignore secrets ================================================= */ - secret: grunt.file.exists('secrets.json') ? grunt.file.readJSON('secrets.json') : {}, - config: grunt.file.readJSON('config.json'), + secret: secret, + config: config, /* SASS ------------------------------------------------- */ @@ -304,14 +311,36 @@ module.exports = function(grunt) { to: "<%= config.strings.litmus_email %>", src: 'dist_test/user_invitation.html' } - } + }, + 'postmark-templates-generate': { + options: { + src: [path.email_src], + dist: path.dist, + outputFile: '<%= config.templates.output_file || config.templates.file %>' + } + }, + + 'postmark-templates-output': { + options: { + outputFile: '<%= config.templates.output_file || config.templates.file %>', + cleanOutput: '<%= config.templates.clean_output %>' + } + }, + + 'postmark-templates-upload': grunt.file.readJSON( + config.templates.output_file + ? config.templates.output_file + : config.templates.file + ) }); /* Tasks ================================================= */ + grunt.loadTasks('tasks'); + grunt.registerTask('default', ['css', 'html']); // Assets @@ -325,6 +354,9 @@ module.exports = function(grunt) { grunt.registerTask('litmus', ['testBuild', 'postmark:litmus']); grunt.registerTask('flood', ['testBuild', 'postmark:flood']); + // Upload + grunt.registerTask('upload', ['default', 'postmark-templates-generate', 'postmark-templates-reload-templates', 'postmark-templates']); + // Before sending tests via Postmark, ensure that test builds with inlined CSS are generated grunt.registerTask('testBuild', ['default', 'copy:testTemplates', 'premailer:html']); }; diff --git a/example_config.json b/example_config.json index 24d5e59..af0a542 100644 --- a/example_config.json +++ b/example_config.json @@ -50,5 +50,8 @@ "bucket": "", "region": "", "overwrite": true + }, + "templates": { + "file": "templates.json" } } diff --git a/package.json b/package.json index 7dd21c5..9963b76 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "spamcheck": "grunt spamcheck", "litmus": "grunt litmus", "flood": "grunt flood", + "upload": "grunt upload", "build": "grunt" }, "devDependencies": { @@ -36,6 +37,7 @@ "grunt-spamcheck": "0.1.1", "grunt-text-replace": "^0.4.0", "load-grunt-tasks": "^3.2.0", - "time-grunt": "^1.2.1" + "time-grunt": "^1.2.1", + "yaml-front-matter": "^3.4.0" } } diff --git a/previews.html b/previews.html index 5db4f22..ac59bf2 100644 --- a/previews.html +++ b/previews.html @@ -3,7 +3,7 @@ Postmark Template Previews - - +
- + - +

MailMason Template Previews

- +
- +
- +
- - diff --git a/tasks/grunt-postmark-templates-generate.js b/tasks/grunt-postmark-templates-generate.js new file mode 100644 index 0000000..1fdaf64 --- /dev/null +++ b/tasks/grunt-postmark-templates-generate.js @@ -0,0 +1,50 @@ +/* + * grunt-postmark-templates-generate + * Generate templates to push to a Postmark server for use with grunt-postmark + * + * https://github.com/wildbit/grunt-postmark.git + */ + +module.exports = function (grunt) { + grunt.registerTask('postmark-templates-generate', 'Generate templates to push to a Postmark server for use with grunt-postmark', function () { + var path = require('path'); + var yaml = require('yaml-front-matter'); + + var options = this.options(); + var templatePaths = grunt.file.expand(options.src); + var templateObjects = grunt.file.readJSON(grunt.config.get('config.templates.file')); + + templatePaths.map(function (templatePath) { + try { + var templateFile = yaml.loadFront(grunt.file.read(templatePath)); + var templateName = path.basename(templatePath, '.hbs'); + var templateContents = { + Name: templateObjects[templateName] && templateObjects[templateName].name || templateName, + HtmlBody: grunt.file.read(path.join(options.dist, templateName + '.html')), + TextBody: grunt.file.read(path.join(options.dist, templateName + '.txt')) + }; + + if (templateFile.name) { + templateContents.Name = templateFile.name; + } + + if (templateFile.subject) { + templateContents.Subject = templateFile.subject; + } + + if (!templateObjects[templateName]) { + templateObjects[templateName] = {}; + } + + Object.assign( + templateObjects[templateName], + templateContents + ); + } catch (e) { + grunt.log.error('templatePath', templatePath, e); + } + }); + + grunt.file.write(grunt.config.get('config.templates.file'), JSON.stringify(templateObjects, null, 2)); + }); +}; diff --git a/tasks/grunt-postmark-templates-reload-templates.js b/tasks/grunt-postmark-templates-reload-templates.js new file mode 100644 index 0000000..3011c54 --- /dev/null +++ b/tasks/grunt-postmark-templates-reload-templates.js @@ -0,0 +1,17 @@ +/* + * grunt-postmark-templates-generate + * Generate templates to push to a Postmark server for use with grunt-postmark + * + * https://github.com/wildbit/grunt-postmark.git + */ + +module.exports = function (grunt) { + grunt.registerTask('postmark-templates-reload-templates', 'Reload template configuration generated by `postmark-templates-generate` for `postmark-templates-upload`', function () { + grunt.config.set( + 'postmark-templates-upload', + grunt.config.get('config.templates.output_file') + ? grunt.file.readJSON(grunt.config.get('config.templates.output_file')) + : grunt.file.readJSON(grunt.config.get('config.templates.file')) + ); + }); +}; diff --git a/templates.json b/templates.json new file mode 100644 index 0000000..e392479 --- /dev/null +++ b/templates.json @@ -0,0 +1,52 @@ +{ + "welcome": { + "Name": "Welcome", + "description": "Send a welcome email to users after they sign up.", + "guide": "https://postmarkapp.com/guides/welcome-email-best-practices" + }, + "password_reset": { + "Name": "Password Reset", + "description": "Send users a link to reset their password.", + "guide": "https://postmarkapp.com/guides/password-reset-email-best-practices" + }, + "password_reset_help": { + "Name": "Password Reset Help", + "description": "Help users reset their password if they can’t remember their email.", + "guide": "https://postmarkapp.com/guides/password-reset-email-best-practices" + }, + "invoice": { + "Name": "Invoice", + "description": "Request payment from a user.", + "guide": "https://postmarkapp.com/guides/receipt-and-invoice-email-best-practices" + }, + "receipt": { + "Name": "Receipt", + "description": "Send a receipt to users after they made a purchase.", + "guide": "https://postmarkapp.com/guides/receipt-and-invoice-email-best-practices" + }, + "comment_notification": { + "Name": "Comment Notification", + "description": "Notify users of new comments by other users.", + "guide": "https://postmarkapp.com/guides/comment-notification-email-best-practices" + }, + "trial_expiring": { + "Name": "Trial Expiring", + "description": "Let users know when their trial is about to expire.", + "guide": "https://postmarkapp.com/guides/trial-expiration-email-best-practices" + }, + "trial_expired": { + "Name": "Trial Expired", + "description": "Let users know when their expired trial.", + "guide": "https://postmarkapp.com/guides/trial-expiration-email-best-practices" + }, + "user_invitation": { + "Name": "User Invitation", + "description": "Help users invite others to use your software.", + "guide": "https://postmarkapp.com/guides/user-invitation-email-best-practices" + }, + "example": { + "Name": "Example", + "description": "Examples of built-in elements and styles.", + "guide": "https://postmarkapp.com/guides/transactional-email-best-practices" + } +} \ No newline at end of file From 9af44cccec2f07475578de4e5d48a0e86210b2ed Mon Sep 17 00:00:00 2001 From: Randy Tarampi Date: Sun, 10 Dec 2017 21:16:38 -0800 Subject: [PATCH 2/4] Further decouple `grunt-postmark` from `mailmason`. Per @derekrushforth's comments on https://github.com/wildbit/grunt-postmark/pull/3. [This](https://github.com/wildbit/grunt-postmark/pull/3#discussion_r153006973) will break things for folks who are already using these changes (@gcoombe and @jmas) as we're now looking for `lowerCamel` variable names instead of `Title` cased ones. Just means a quick find/replace for `Name`/`name, `Subject`/`subject`, etc. I've internally changed the implementation of `postmark-templates-upload` to support in-memory `htmlBody` and `textBody` as well as lazy loaded paths `htmlSrc` and `textSrc` as @hybernaut originally had it. I didn't actually notice how far my changes had deviated from the default options for the `postmark-templates-upload` task when I filed the PR, but I actually like the idea of loading the template files instead of expecting them to be passed along as task data. --- Gruntfile.js | 21 ++++++++++--------- example_config.json | 4 +++- previews.html | 4 ++-- tasks/grunt-postmark-templates-generate.js | 14 ++++++------- ...unt-postmark-templates-reload-templates.js | 17 --------------- 5 files changed, 23 insertions(+), 37 deletions(-) delete mode 100644 tasks/grunt-postmark-templates-reload-templates.js diff --git a/Gruntfile.js b/Gruntfile.js index ecd5b42..0b59af2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -317,22 +317,23 @@ module.exports = function(grunt) { options: { src: [path.email_src], dist: path.dist, - outputFile: '<%= config.templates.output_file || config.templates.file %>' + file: '<%= config.templates.file %>' } }, - 'postmark-templates-output': { + 'postmark-templates-upload': { options: { - outputFile: '<%= config.templates.output_file || config.templates.file %>', - cleanOutput: '<%= config.templates.clean_output %>' + ephemeralUploadResultsProperty: '<%= config.templates && config.templates.ephemeralUploadResultsProperty %>' } }, - 'postmark-templates-upload': grunt.file.readJSON( - config.templates.output_file - ? config.templates.output_file - : config.templates.file - ) + 'postmark-templates-output': { + options: { + outputFile: '<%= config.templates && config.templates.output_file || config.templates && config.templates.file %>', + cleanOutput: '<%= config.templates && config.templates.clean_output %>', + ephemeralUploadResultsProperty: '<%= config.templates && config.templates.ephemeralUploadResultsProperty %>' + } + }, }); @@ -355,7 +356,7 @@ module.exports = function(grunt) { grunt.registerTask('flood', ['testBuild', 'postmark:flood']); // Upload - grunt.registerTask('upload', ['default', 'postmark-templates-generate', 'postmark-templates-reload-templates', 'postmark-templates']); + grunt.registerTask('upload', ['default', 'postmark-templates-generate', 'postmark-templates']); // Before sending tests via Postmark, ensure that test builds with inlined CSS are generated grunt.registerTask('testBuild', ['default', 'copy:testTemplates', 'premailer:html']); diff --git a/example_config.json b/example_config.json index af0a542..e087ede 100644 --- a/example_config.json +++ b/example_config.json @@ -52,6 +52,8 @@ "overwrite": true }, "templates": { - "file": "templates.json" + "file": "templates.json", + "cleanOutput": true, + "ephemeralUploadResultsProperty": "postmark-templates-upload-results" } } diff --git a/previews.html b/previews.html index ac59bf2..ee414c7 100644 --- a/previews.html +++ b/previews.html @@ -69,7 +69,7 @@ var list_item = document.createElement('li') var anchor = document.createElement('a') anchor.setAttribute('href', '#' + key) - anchor.appendChild(document.createTextNode(templates[key]['Name'] || templates[key]['name'])) + anchor.appendChild(document.createTextNode(templates[key]['name'])) list_item.appendChild(anchor) list_item.appendChild(document.createTextNode(' - ' + templates[key]['description'])) toc.appendChild(list_item) @@ -85,7 +85,7 @@ guide_link.setAttribute('target', '_blank') guide_link.setAttribute('rel', 'noopener noreferrer') guide_link.appendChild(document.createTextNode('πŸ“– View Best Practices Guide')) - heading.appendChild(document.createTextNode(templates[key]['Name'] || templates[key]['name'])) + heading.appendChild(document.createTextNode(templates[key]['name'])) heading.setAttribute('id', key) description.appendChild(document.createTextNode(templates[key]['description'])) description.appendChild(guide_link) diff --git a/tasks/grunt-postmark-templates-generate.js b/tasks/grunt-postmark-templates-generate.js index 1fdaf64..9de2ac0 100644 --- a/tasks/grunt-postmark-templates-generate.js +++ b/tasks/grunt-postmark-templates-generate.js @@ -12,24 +12,24 @@ module.exports = function (grunt) { var options = this.options(); var templatePaths = grunt.file.expand(options.src); - var templateObjects = grunt.file.readJSON(grunt.config.get('config.templates.file')); + var templateObjects = grunt.file.readJSON(options.file); templatePaths.map(function (templatePath) { try { var templateFile = yaml.loadFront(grunt.file.read(templatePath)); var templateName = path.basename(templatePath, '.hbs'); var templateContents = { - Name: templateObjects[templateName] && templateObjects[templateName].name || templateName, - HtmlBody: grunt.file.read(path.join(options.dist, templateName + '.html')), - TextBody: grunt.file.read(path.join(options.dist, templateName + '.txt')) + name: templateObjects[templateName] && templateObjects[templateName].name || templateName, + htmlSrc: path.join(options.dist, templateName + '.html'), + textSrc: path.join(options.dist, templateName + '.txt') }; if (templateFile.name) { - templateContents.Name = templateFile.name; + templateContents.name = templateFile.name; } if (templateFile.subject) { - templateContents.Subject = templateFile.subject; + templateContents.subject = templateFile.subject; } if (!templateObjects[templateName]) { @@ -45,6 +45,6 @@ module.exports = function (grunt) { } }); - grunt.file.write(grunt.config.get('config.templates.file'), JSON.stringify(templateObjects, null, 2)); + grunt.config('postmark-templates-upload', templateObjects); }); }; diff --git a/tasks/grunt-postmark-templates-reload-templates.js b/tasks/grunt-postmark-templates-reload-templates.js deleted file mode 100644 index 3011c54..0000000 --- a/tasks/grunt-postmark-templates-reload-templates.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * grunt-postmark-templates-generate - * Generate templates to push to a Postmark server for use with grunt-postmark - * - * https://github.com/wildbit/grunt-postmark.git - */ - -module.exports = function (grunt) { - grunt.registerTask('postmark-templates-reload-templates', 'Reload template configuration generated by `postmark-templates-generate` for `postmark-templates-upload`', function () { - grunt.config.set( - 'postmark-templates-upload', - grunt.config.get('config.templates.output_file') - ? grunt.file.readJSON(grunt.config.get('config.templates.output_file')) - : grunt.file.readJSON(grunt.config.get('config.templates.file')) - ); - }); -}; From bed89806587638609079778d4ea38bf52f5f4992 Mon Sep 17 00:00:00 2001 From: Randy Tarampi Date: Sun, 10 Dec 2017 22:14:45 -0800 Subject: [PATCH 3/4] Don't special case default `config.templates.file` here. --- Gruntfile.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 0b59af2..20b501d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -7,11 +7,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-ftp-deploy'); var secret = grunt.file.exists('secrets.json') ? grunt.file.readJSON('secrets.json') : {}; - var config = Object.assign({ - templates: { - file: 'templates.json' - } - }, grunt.file.readJSON('config.json')); + var config = grunt.file.readJSON('config.json'); var path = { css_src: 'src/stylesheets/global.scss', @@ -317,7 +313,7 @@ module.exports = function(grunt) { options: { src: [path.email_src], dist: path.dist, - file: '<%= config.templates.file %>' + file: '<%= config.templates && config.templates.file %>' } }, From 0a66470387ff8312af0b5bf7852102ee8a7be9b8 Mon Sep 17 00:00:00 2001 From: Randy Tarampi Date: Fri, 16 Mar 2018 11:58:03 -0700 Subject: [PATCH 4/4] grunt-postmark@0.0.8 Per https://github.com/wildbit/grunt-postmark/pull/3. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9963b76..a0e6f18 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "grunt-contrib-watch": "^1.0.0", "grunt-ftp-deploy": "^0.1.10", "grunt-inline": "^0.3.4", - "grunt-postmark": "0.0.7", + "grunt-postmark": "0.0.8", "grunt-premailer": "^1.0.0", "grunt-prettify": "^0.4.0", "grunt-sass": "^1.0.0",