diff --git a/.eslintrc.js b/.eslintrc.js index 7b039ae..6d7e679 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,7 +16,7 @@ module.exports = { 'no-console': 'off', eqeqeq: ['error', 'always'], 'linebreak-style': ['error', 'unix'], - '@typescript-eslint/indent': ['error', 2], + '@typescript-eslint/indent': 'off', '@typescript-eslint/member-delimiter-style': [ 'error', { multiline: { delimiter: 'none' } }, diff --git a/media/push-confirm.png b/media/push-confirm.png index 7f3b523..4ad7676 100644 Binary files a/media/push-confirm.png and b/media/push-confirm.png differ diff --git a/package-lock.json b/package-lock.json index 4eddab9..971512b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "postmark-cli", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -141,6 +141,11 @@ "@types/node": "*" } }, + "@types/traverse": { + "version": "0.6.32", + "resolved": "https://registry.npmjs.org/@types/traverse/-/traverse-0.6.32.tgz", + "integrity": "sha512-RBz2uRZVCXuMg93WD//aTS5B120QlT4lR/gL+935QtGsKHLS6sCtZBaKfWjIfk7ZXv/r8mtGbwjVIee6/3XTow==" + }, "@types/yargs": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.0.tgz", @@ -1669,9 +1674,9 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "postmark": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/postmark/-/postmark-2.2.4.tgz", - "integrity": "sha512-qzqcXrKQnSGIScOpCNz2ArC69zHSuVZKo8RLg8Nioz633NIi4aVYeBNBwCn1hfAcQ/g/eCAyTAvVbMYyPmlSzQ==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/postmark/-/postmark-2.2.7.tgz", + "integrity": "sha512-vqDqh9/N/weyFQglewU4O2+6OLaEdibZrOyCEubyukfWz5DKUsBk3GGoU4c8P+QLk7DtiX/TAwwsXZ3bgX+fXA==", "requires": { "request": "^2.88.0" } @@ -2081,6 +2086,11 @@ } } }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=" + }, "ts-node": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.0.3.tgz", diff --git a/package.json b/package.json index f00e00f..f220129 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,19 @@ { "name": "postmark-cli", - "version": "1.0.0", + "version": "1.1.0", "description": "A CLI tool for managing templates, sending emails, and fetching servers on Postmark.", "main": "./dist/index.js", "dependencies": { + "@types/traverse": "^0.6.32", "chalk": "^2.4.2", "fs-extra": "^7.0.1", "inquirer": "^6.2.1", "lodash": "^4.17.11", "ora": "^3.0.0", - "postmark": "^2.2.4", + "postmark": "^2.2.7", "request": "^2.88.0", "table": "^5.2.0", + "traverse": "^0.6.6", "untildify": "^4.0.0", "yargonaut": "^1.1.4", "yargs": "^13.2.4" diff --git a/src/commands/cheats.ts b/src/commands/cheats.ts index a528816..f7929a2 100644 --- a/src/commands/cheats.ts +++ b/src/commands/cheats.ts @@ -48,7 +48,7 @@ const cheatInput = (hideMessage: boolean): Promise => choices: choices, message: hideMessage ? '\n' : title, }, - ]).then((answer: { code?: string }) => { + ]).then((answer: any) => { return resolve(answer.code) }) }) diff --git a/src/commands/templates/pull.ts b/src/commands/templates/pull.ts index 7dfc14a..b024015 100644 --- a/src/commands/templates/pull.ts +++ b/src/commands/templates/pull.ts @@ -10,6 +10,7 @@ import { Template, TemplateListOptions, TemplatePullArguments, + MetaFile, } from '../../types' import { log, validateToken, pluralize } from '../../utils' @@ -66,9 +67,9 @@ const overwritePrompt = (serverToken: string, outputdirectory: string) => { type: 'confirm', name: 'overwrite', default: false, - message: `Are you sure you want to overwrite the files in ${outputdirectory}?`, + message: `Overwrite the files in ${outputdirectory}?`, }, - ]).then((answer: { overwrite?: boolean }) => { + ]).then((answer: any) => { if (answer.overwrite) { return fetchTemplateList({ sourceServer: serverToken, @@ -140,7 +141,7 @@ const processTemplates = (options: ProcessTemplatesOptions) => { } client - .getTemplate(template.TemplateId) + .getTemplate(template.Alias) .then((response: Template) => { requestCount++ @@ -174,9 +175,8 @@ const processTemplates = (options: ProcessTemplatesOptions) => { * @return An object containing the HTML and Text body */ const saveTemplate = (outputDir: string, template: Template) => { - template = pruneTemplateObject(template) - - // Create the directory + outputDir = + template.TemplateType === 'Layout' ? join(outputDir, '_layouts') : outputDir const path: string = untildify(join(outputDir, template.Alias)) ensureDirSync(path) @@ -191,21 +191,15 @@ const saveTemplate = (outputDir: string, template: Template) => { outputFileSync(join(path, 'content.txt'), template.TextBody) } - // Create metadata JSON - delete template.HtmlBody - delete template.TextBody - - outputFileSync(join(path, 'meta.json'), JSON.stringify(template, null, 2)) -} - -/** - * Remove unneeded fields on the template object - * @returns the pruned object - */ -const pruneTemplateObject = (template: Template) => { - delete template.AssociatedServerId - delete template.Active - delete template.TemplateId + const meta: MetaFile = { + Name: template.Name, + Alias: template.Alias, + ...(template.Subject && { Subject: template.Subject }), + TemplateType: template.TemplateType, + ...(template.TemplateType === 'Standard' && { + LayoutTemplate: template.LayoutTemplate, + }), + } - return template + outputFileSync(join(path, 'meta.json'), JSON.stringify(meta, null, 2)) } diff --git a/src/commands/templates/push.ts b/src/commands/templates/push.ts index 7ecb756..c6a7eec 100644 --- a/src/commands/templates/push.ts +++ b/src/commands/templates/push.ts @@ -1,24 +1,22 @@ import chalk from 'chalk' import ora from 'ora' -import { join } from 'path' +import { join, dirname } from 'path' import { find } from 'lodash' import { prompt } from 'inquirer' +import traverse from 'traverse' import { table, getBorderCharacters } from 'table' import untildify from 'untildify' -import { - readJsonSync, - readFileSync, - readdirSync, - existsSync, - statSync, -} from 'fs-extra' - +import { readJsonSync, readFileSync, existsSync } from 'fs-extra' +import dirTree from 'directory-tree' import { ServerClient } from 'postmark' import { TemplateManifest, TemplatePushResults, TemplatePushReview, TemplatePushArguments, + Templates, + MetaFileTraverse, + MetaFile, } from '../../types' import { pluralize, log, validateToken } from '../../utils' @@ -57,8 +55,11 @@ const validateDirectory = ( serverToken: string, args: TemplatePushArguments ) => { - if (!existsSync(untildify(args.templatesdirectory))) { - log('Could not find the template directory provided', { error: true }) + const rootPath: string = untildify(args.templatesdirectory) + + // Check if path exists + if (!existsSync(rootPath)) { + log('The provided path does not exist', { error: true }) return process.exit(1) } @@ -80,36 +81,21 @@ const push = (serverToken: string, args: TemplatePushArguments) => { client .getTemplates() .then(response => { - // Compare local templates with server - manifest.forEach(template => { - template.New = !find(response.Templates, { Alias: template.Alias }) - template.New ? review.added++ : review.modified++ - review.files.push([ - template.New ? chalk.green('Added') : chalk.yellow('Modified'), - template.Name, - template.Alias, - ]) - }) + compareTemplates(response, manifest) + // Show which templates are changing spinner.stop() printReview(review) - // Push templates + // Push templates if force arg is present if (force) { spinner.text = 'Pushing templates to Postmark...' spinner.start() return pushTemplates(spinner, client, manifest) } - // User confirmation before pushing - prompt([ - { - type: 'confirm', - name: 'confirm', - default: false, - message: `Are you sure you want to push these templates to Postmark?`, - }, - ]).then((answer: any) => { + // Ask for user confirmation + confirmation().then(answer => { if (answer.confirm) { spinner.text = 'Pushing templates to Postmark...' spinner.start() @@ -125,71 +111,189 @@ const push = (serverToken: string, args: TemplatePushArguments) => { process.exit(1) }) } else { - log('No templates were found in this directory', { error: true }) + spinner.stop() + log('No templates or layouts were found.', { error: true }) process.exit(1) } } /** - * Gather up templates on the file system - * @returns An object containing all locally stored templates + * Ask user to confirm the push */ -const createManifest = (path: string) => { - let manifest: TemplateManifest[] = [] - const dirs = readdirSync(path).filter(f => - statSync(join(path, f)).isDirectory() - ) +const confirmation = (): Promise => + new Promise((resolve, reject) => { + prompt([ + { + type: 'confirm', + name: 'confirm', + default: false, + message: `Would you like to continue?`, + }, + ]) + .then((answer: any) => resolve(answer)) + .catch((err: any) => reject(err)) + }) - dirs.forEach(dir => { - const metaPath = join(path, join(dir, 'meta.json')) - const htmlPath = join(path, join(dir, 'content.html')) - const textPath = join(path, join(dir, 'content.txt')) - let template: TemplateManifest = {} - - if (existsSync(metaPath)) { - template.HtmlBody = existsSync(htmlPath) - ? readFileSync(htmlPath, 'utf-8') - : '' - template.TextBody = existsSync(textPath) - ? readFileSync(textPath, 'utf-8') - : '' - - if (template.HtmlBody !== '' || template.TextBody !== '') { - template = Object.assign(template, readJsonSync(metaPath)) - manifest.push(template) - } +/** + * Compare templates on server against local + */ +const compareTemplates = ( + response: Templates, + manifest: TemplateManifest[] +): void => { + // Iterate through manifest + manifest.forEach(template => { + // See if this local template exists on the server + const match = find(response.Templates, { Alias: template.Alias }) + template.New = !match + + let reviewData = [ + template.New ? chalk.green('Added') : chalk.yellow('Modified'), + template.Name, + template.Alias, + ] + + if (template.TemplateType === 'Standard') { + // Add layout used column + reviewData.push( + layoutUsedLabel( + template.LayoutTemplate, + match ? match.LayoutTemplate : template.LayoutTemplate + ) + ) + + review.templates.push(reviewData) + } else { + review.layouts.push(reviewData) } }) +} + +/** + * Render the "Layout used" column for Standard templates + */ +const layoutUsedLabel = ( + localLayout: string | null | undefined, + serverLayout: string | null | undefined +): string => { + let label: string = localLayout ? localLayout : chalk.gray('None') + + // If layout template on server doesn't match local template + if (localLayout !== serverLayout) { + serverLayout = serverLayout ? serverLayout : 'None' + + // Append old server layout to label + label += chalk.red(` ✘ ${serverLayout}`) + } + + return label +} + +/** + * Parses templates folder and files + */ +const createManifest = (path: string): TemplateManifest[] => { + let manifest: TemplateManifest[] = [] + + // Return empty array if path does not exist + if (!existsSync(path)) return manifest + + // Find meta files and flatten into collection + const list: MetaFileTraverse[] = FindMetaFiles(path) + + // Parse each directory + list.forEach(file => { + const item = createManifestItem(file) + if (item) manifest.push(item) + }) return manifest } +/** + * Gathers the template's content and metadata based on the metadata file location + */ +const createManifestItem = (file: any): MetaFile | null => { + const { path } = file // Path to meta file + const rootPath = dirname(path) // Folder path + const htmlPath = join(rootPath, 'content.html') // HTML path + const textPath = join(rootPath, 'content.txt') // Text path + + // Check if meta file exists + if (existsSync(path)) { + const metaFile: MetaFile = readJsonSync(path) + const htmlFile: string = existsSync(htmlPath) + ? readFileSync(htmlPath, 'utf-8') + : '' + const textFile: string = existsSync(textPath) + ? readFileSync(textPath, 'utf-8') + : '' + + return { + HtmlBody: htmlFile, + TextBody: textFile, + ...metaFile, + } + } + + return null +} + +/** + * Searches for all metadata files and flattens into a collection + */ +const FindMetaFiles = (path: string): MetaFileTraverse[] => + traverse(dirTree(path)).reduce((acc, file) => { + if (file.name === 'meta.json') acc.push(file) + return acc + }, []) + /** * Show which templates will change after the publish */ const printReview = (review: TemplatePushReview) => { - const { files, added, modified } = review - const head = [chalk.gray('Type'), chalk.gray('Name'), chalk.gray('Alias')] + const { templates, layouts } = review - log(table([head, ...files], { border: getBorderCharacters('norc') })) + // Table headers + const header = [chalk.gray('Change'), chalk.gray('Name'), chalk.gray('Alias')] + const templatesHeader = [...header, chalk.gray('Layout used')] - if (added > 0) { + // Labels + const templatesLabel = + templates.length > 0 + ? `${templates.length} ${pluralize( + templates.length, + 'template', + 'templates' + )}` + : '' + const layoutsLabel = + layouts.length > 0 + ? `${layouts.length} ${pluralize(layouts.length, 'layout', 'layouts')}` + : '' + + // Log template and layout files + if (templates.length > 0) { + log(`\n${templatesLabel}`) log( - `${added} ${pluralize(added, 'template', 'templates')} will be added.`, - { color: 'green' } + table([templatesHeader, ...templates], { + border: getBorderCharacters('norc'), + }) ) } + if (layouts.length > 0) { + log(`\n${layoutsLabel}`) + log(table([header, ...layouts], { border: getBorderCharacters('norc') })) + } - if (modified > 0) { - log( - `${modified} ${pluralize( - modified, - 'template', - 'templates' - )} will be modified.`, - { color: 'yellow' } + // Log summary + log( + chalk.yellow( + `${templatesLabel}${ + templates.length > 0 && layouts.length > 0 ? ' and ' : '' + }${layoutsLabel} will be pushed to Postmark.` ) - } + ) } /** @@ -200,9 +304,9 @@ const pushTemplates = ( client: any, templates: TemplateManifest[] ) => { - templates.forEach(template => { + templates.forEach(template => pushTemplate(spinner, client, template, templates.length) - }) + ) } /** @@ -213,9 +317,9 @@ const pushTemplate = ( client: any, template: TemplateManifest, total: number -) => { +): void => { if (template.New) { - client + return client .createTemplate(template) .then((response: object) => pushComplete(true, response, template, spinner, total) @@ -223,16 +327,16 @@ const pushTemplate = ( .catch((response: object) => pushComplete(false, response, template, spinner, total) ) - } else { - client - .editTemplate(template.Alias, template) - .then((response: object) => - pushComplete(true, response, template, spinner, total) - ) - .catch((response: object) => - pushComplete(false, response, template, spinner, total) - ) } + + return client + .editTemplate(template.Alias, template) + .then((response: object) => + pushComplete(true, response, template, spinner, total) + ) + .catch((response: object) => + pushComplete(false, response, template, spinner, total) + ) } /** @@ -252,26 +356,19 @@ const pushComplete = ( // Log any errors to the console if (!success) { spinner.stop() - log(`\n${template.Name}: ${response.toString()}`, { error: true }) + log(`\n${template.Alias}: ${response.toString()}`, { error: true }) spinner.start() } if (completed === total) { spinner.stop() - log( - `All finished! ${results.success} ${pluralize( - results.success, - 'template was', - 'templates were' - )} pushed to Postmark.`, - { color: 'green' } - ) + log('✅ All finished!', { color: 'green' }) // Show failures if (results.failed) { log( - `Failed to push ${results.failed} ${pluralize( + `⚠️ Failed to push ${results.failed} ${pluralize( results.failed, 'template', 'templates' @@ -288,7 +385,6 @@ let results: TemplatePushResults = { } let review: TemplatePushReview = { - files: [], - added: 0, - modified: 0, + layouts: [], + templates: [], } diff --git a/src/types/Template.ts b/src/types/Template.ts index 4b29638..579bce4 100644 --- a/src/types/Template.ts +++ b/src/types/Template.ts @@ -5,6 +5,8 @@ export interface TemplateManifest { TextBody?: string Alias?: string New?: boolean + TemplateType: string + LayoutTemplate?: string | null } export interface Template extends TemplateManifest { @@ -20,6 +22,8 @@ export interface ListTemplate { TemplateId: number Name: string Alias?: string | null + TemplateType: string + LayoutTemplate: string | null } export interface Templates { @@ -33,9 +37,8 @@ export interface TemplatePushResults { } export interface TemplatePushReview { - files: any[] - added: number - modified: number + layouts: any[] + templates: any[] } export interface ProcessTemplatesOptions { @@ -62,3 +65,21 @@ export interface TemplatePushArguments { templatesdirectory: string force: boolean } + +export interface MetaFile { + Name: string + Alias: string + TemplateType: string + Subject?: string + LayoutTemplate?: string | null + HtmlBody?: string + TextBody?: string +} + +export interface MetaFileTraverse { + path: string + name: string + size: number + extension: string + type: string +} diff --git a/src/utils.ts b/src/utils.ts index 95d771d..fcd832a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -24,7 +24,7 @@ export const pluralize = ( count: number, singular: string, plural: string -): string => (count > 1 ? plural : singular) +): string => (count > 1 || count === 0 ? plural : singular) /** * Log stuff to the console @@ -65,7 +65,7 @@ export const serverTokenPrompt = (account: boolean): Promise => message: `Please enter your ${tokenType} token`, mask: '•', }, - ]).then((answer: { token?: string }) => { + ]).then((answer: any) => { const { token } = answer if (!token) { diff --git a/test/integration/shared.ts b/test/integration/shared.ts index f5bce07..32ed261 100644 --- a/test/integration/shared.ts +++ b/test/integration/shared.ts @@ -1,4 +1,5 @@ import nconf from 'nconf' +import * as postmark from "postmark"; export const testingKeys = nconf .env() @@ -11,3 +12,43 @@ export const toAddress: string = testingKeys.get('TO_ADDRESS') export const CLICommand: string = './dist/index.js' export const TestDataFolder: string = './test/data' export const PackageJson: any = require('../../package.json') + +// In order to test template syncing, data needs to be created by postmark.js client + +const templatePrefix: string = "testing-template-cli"; + +function templateToCreate(templatePrefix: string) { + return new postmark.Models.CreateTemplateRequest( + `${templatePrefix}-${Date.now()}`, + "Subject", + "Html body", + "Text body", + null, + postmark.Models.TemplateTypes.Standard, + ); +} + +function templateLayoutToCreate(templatePrefix: string) { + return new postmark.Models.CreateTemplateRequest( + `${templatePrefix}-${Date.now()}`, undefined, + "Html body {{{@content}}}", "Text body {{{@content}}}", + null, postmark.Models.TemplateTypes.Layout, + ); +} + +export const createTemplateData = async () => { + const client = new postmark.ServerClient(serverToken); + await client.createTemplate(templateToCreate(templatePrefix)); + await client.createTemplate(templateLayoutToCreate(templatePrefix)); +} + +export const deleteTemplateData = async () => { + const client = new postmark.ServerClient(serverToken); + const templates = await client.getTemplates({count: 50}); + + for (const template of templates.Templates) { + if (template.Name.includes(templatePrefix)) { + await client.deleteTemplate(template.TemplateId); + } + } +} \ No newline at end of file diff --git a/test/integration/templates.pull.test.ts b/test/integration/templates.pull.test.ts index b07176c..9e0fe0e 100644 --- a/test/integration/templates.pull.test.ts +++ b/test/integration/templates.pull.test.ts @@ -2,9 +2,16 @@ import { expect } from 'chai' import 'mocha' import execa from 'execa' import * as fs from 'fs-extra' +import { join } from 'path' const dirTree = require('directory-tree') -import { serverToken, CLICommand, TestDataFolder } from './shared' +import { + serverToken, + CLICommand, + TestDataFolder, + createTemplateData, + deleteTemplateData, +} from './shared' describe('Templates command', () => { const options: execa.CommonOptions = { @@ -13,28 +20,47 @@ describe('Templates command', () => { const dataFolder: string = TestDataFolder const commandParameters: string[] = ['templates', 'pull', dataFolder] + before(async () => { + await deleteTemplateData() + return createTemplateData() + }) + + after(async () => { + await deleteTemplateData() + }) + afterEach(() => { fs.removeSync(dataFolder) }) describe('Pull', () => { + function retrieveFiles(path: string, excludeLayouts?: boolean) { + const folderTree = dirTree( + path, + excludeLayouts && { + exclude: /_layouts$/, + } + ) + return folderTree.children[0].children + } + it('console out', async () => { const { stdout } = await execa(CLICommand, commandParameters, options) expect(stdout).to.include('All finished') }) - describe('Folder', () => { + describe('Templates', () => { it('templates', async () => { await execa(CLICommand, commandParameters, options) - const templateFolders = dirTree(dataFolder) - expect(templateFolders.children.length).to.be.gt(0) + const folderTree = dirTree(dataFolder, { + exclude: /_layouts$/, + }) + expect(folderTree.children.length).to.be.gt(0) }) it('single template - file names', async () => { await execa(CLICommand, commandParameters, options) - const templateFolders = dirTree(dataFolder) - - const files = templateFolders.children[0].children + const files = retrieveFiles(dataFolder, true) const names: string[] = files.map((f: any) => { return f.name }) @@ -44,8 +70,38 @@ describe('Templates command', () => { it('single template files - none empty', async () => { await execa(CLICommand, commandParameters, options) - const templateFolders = dirTree(dataFolder) - const files = templateFolders.children[0].children + const files = retrieveFiles(dataFolder) + + let result = files.findIndex((f: any) => { + return f.size <= 0 + }) + expect(result).to.eq(-1) + }) + }) + + describe('Layouts', () => { + const filesPath = join(dataFolder, '_layouts') + + it('layouts', async () => { + await execa(CLICommand, commandParameters, options) + const folderTree = dirTree(filesPath) + expect(folderTree.children.length).to.be.gt(0) + }) + + it('single layout - file names', async () => { + await execa(CLICommand, commandParameters, options) + const files = retrieveFiles(filesPath) + + const names: string[] = files.map((f: any) => { + return f.name + }) + + expect(names).to.members(['content.txt', 'content.html', 'meta.json']) + }) + + it('single layout files - none empty', async () => { + await execa(CLICommand, commandParameters, options) + const files = retrieveFiles(filesPath) let result = files.findIndex((f: any) => { return f.size <= 0 diff --git a/test/integration/templates.push.test.ts b/test/integration/templates.push.test.ts index 1daa199..42740a2 100644 --- a/test/integration/templates.push.test.ts +++ b/test/integration/templates.push.test.ts @@ -3,9 +3,16 @@ import 'mocha' import execa from 'execa' import * as fs from 'fs-extra' import { DirectoryTree } from 'directory-tree' +import { join } from 'path' const dirTree = require('directory-tree') -import { serverToken, CLICommand, TestDataFolder } from './shared' +import { + serverToken, + CLICommand, + TestDataFolder, + createTemplateData, + deleteTemplateData, +} from './shared' describe('Templates command', () => { const options: execa.CommonOptions = { @@ -20,51 +27,120 @@ describe('Templates command', () => { ] const pullCommandParameters: string[] = ['templates', 'pull', dataFolder] + before(async () => { + await deleteTemplateData() + return createTemplateData() + }) + + after(async () => { + await deleteTemplateData() + }) + afterEach(() => { fs.removeSync(dataFolder) }) describe('Push', () => { + function retrieveFiles(path: string, excludeLayouts?: boolean) { + const folderTree = dirTree( + path, + excludeLayouts && { + exclude: /_layouts$/, + } + ) + return folderTree.children[0].children + } + beforeEach(async () => { await execa(CLICommand, pullCommandParameters, options) }) - it('console out', async () => { - const templateFolders = dirTree(dataFolder) - const files = templateFolders.children[0].children - const file: DirectoryTree = files.find((f: DirectoryTree) => { - return f.path.includes('txt') + describe('Templates', () => { + it('console out', async () => { + const files = retrieveFiles(dataFolder, true) + const file: DirectoryTree = files.find((f: DirectoryTree) => { + return f.path.includes('txt') + }) + + fs.writeFileSync( + file.path, + `test data ${Date.now().toString()}`, + 'utf-8' + ) + const { stdout } = await execa( + CLICommand, + pushCommandParameters, + options + ) + expect(stdout).to.include('All finished!') }) - fs.writeFileSync(file.path, `test data ${Date.now().toString()}`, 'utf-8') - const { stdout } = await execa(CLICommand, pushCommandParameters, options) - expect(stdout).to.include( - 'All finished! 1 template was pushed to Postmark.' - ) - }) + it('file content', async () => { + let files = retrieveFiles(dataFolder, true) + let file: DirectoryTree = files.find((f: DirectoryTree) => { + return f.path.includes('txt') + }) + const contentToPush: string = `test data ${Date.now().toString()}` + + fs.writeFileSync(file.path, contentToPush, 'utf-8') + await execa(CLICommand, pushCommandParameters, options) - it('file content', async () => { - let templateFolders = dirTree(dataFolder) - let files = templateFolders.children[0].children - let file: DirectoryTree = files.find((f: DirectoryTree) => { - return f.path.includes('txt') + fs.removeSync(dataFolder) + await execa(CLICommand, pullCommandParameters, options) + + files = retrieveFiles(dataFolder, true) + file = files.find((f: DirectoryTree) => { + return f.path.includes('txt') + }) + + const content: string = fs.readFileSync(file.path).toString('utf-8') + expect(content).to.equal(contentToPush) }) - const contentToPush: string = `test data ${Date.now().toString()}` + }) - fs.writeFileSync(file.path, contentToPush, 'utf-8') - await execa(CLICommand, pushCommandParameters, options) + describe('Layouts', () => { + const filesPath = join(dataFolder, '_layouts') - fs.removeSync(dataFolder) - await execa(CLICommand, pullCommandParameters, options) + it('console out', async () => { + const files = retrieveFiles(filesPath) + const file: DirectoryTree = files.find((f: DirectoryTree) => { + return f.path.includes('txt') + }) - templateFolders = dirTree(dataFolder) - files = templateFolders.children[0].children - file = files.find((f: DirectoryTree) => { - return f.path.includes('txt') + fs.writeFileSync( + file.path, + `test data ${Date.now().toString()} {{{@content}}}`, + 'utf-8' + ) + const { stdout } = await execa( + CLICommand, + pushCommandParameters, + options + ) + expect(stdout).to.include('All finished!') }) - const content: string = fs.readFileSync(file.path).toString('utf-8') - expect(content).to.equal(contentToPush) + it('file content', async () => { + let files = retrieveFiles(filesPath) + let file: DirectoryTree = files.find((f: DirectoryTree) => { + return f.path.includes('txt') + }) + const contentToPush: string = `test data ${Date.now().toString()} {{{@content}}}` + + fs.writeFileSync(file.path, contentToPush, 'utf-8') + await execa(CLICommand, pushCommandParameters, options) + + fs.removeSync(dataFolder) + await execa(CLICommand, pullCommandParameters, options) + + files = retrieveFiles(filesPath) + file = files.find((f: DirectoryTree) => { + return f.path.includes('txt') + }) + + const content: string = fs.readFileSync(file.path).toString('utf-8') + expect(content).to.equal(contentToPush) + }) }) }) }) diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index a091ddb..c1741ad 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -25,6 +25,7 @@ describe('Utilities', () => { const plural = 'templates' it('should return plural', () => { + expect(utils.pluralize(0, singular, plural)).to.eq(plural) expect(utils.pluralize(2, singular, plural)).to.eq(plural) expect(utils.pluralize(5, singular, plural)).to.eq(plural) expect(utils.pluralize(10, singular, plural)).to.eq(plural) @@ -33,7 +34,6 @@ describe('Utilities', () => { it('should return singular', () => { expect(utils.pluralize(1, singular, plural)).to.eq(singular) - expect(utils.pluralize(0, singular, plural)).to.eq(singular) expect(utils.pluralize(-1, singular, plural)).to.eq(singular) }) })