universal-email-templates-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # Universal Email Templates CLI
2
+
3
+ CLI tools for building, translating, and exporting professional MJML email templates.
4
+
5
+ ## Features
6
+
7
+ - Build MJML templates to HTML
8
+ - Multi-language support (FR, EN, ES, DE, IT, PT, NL, AR)
9
+ - Export to Mailchimp, Klaviyo, and HubSpot formats
10
+ - WCAG 2.1 AAA accessibility compliant
11
+ - Dark mode support
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install -g universal-email-templates-cli
17
+ ```
18
+
19
+ Or as a project dependency:
20
+
21
+ ```bash
22
+ npm install universal-email-templates-cli
23
+ ```
24
+
25
+ ## CLI Usage
26
+
27
+ ### Build Templates
28
+
29
+ ```bash
30
+ # Build all MJML templates to HTML
31
+ email-build --src ./src/templates --dist ./dist/html
32
+
33
+ # Build with options
34
+ email-build --src ./src/templates --dist ./dist/html --minify
35
+ ```
36
+
37
+ ### Internationalization
38
+
39
+ ```bash
40
+ # Build all languages
41
+ email-i18n --all --src ./src/templates --dist ./dist/i18n
42
+
43
+ # Build specific language
44
+ email-i18n --lang fr --src ./src/templates --dist ./dist/i18n
45
+
46
+ # Build with themes
47
+ email-i18n --lang en --themed --src ./src/templates --dist ./dist/i18n
48
+ ```
49
+
50
+ ### Export to Platforms
51
+
52
+ ```bash
53
+ # Export to Mailchimp
54
+ email-export --platform mailchimp --src ./dist/html --dist ./dist/mailchimp
55
+
56
+ # Export to Klaviyo
57
+ email-export --platform klaviyo --src ./dist/html --dist ./dist/klaviyo
58
+
59
+ # Export to HubSpot
60
+ email-export --platform hubspot --src ./dist/html --dist ./dist/hubspot
61
+ ```
62
+
63
+ ## Programmatic Usage
64
+
65
+ ```javascript
66
+ const { build, translate, exportTo } = require('universal-email-templates-cli');
67
+
68
+ // Build templates
69
+ await build({
70
+ src: './src/templates',
71
+ dist: './dist/html',
72
+ minify: true
73
+ });
74
+
75
+ // Translate templates
76
+ await translate({
77
+ src: './src/templates',
78
+ dist: './dist/i18n',
79
+ languages: ['fr', 'en', 'es'],
80
+ translations: require('./config/translations')
81
+ });
82
+
83
+ // Export to platform
84
+ await exportTo({
85
+ platform: 'mailchimp',
86
+ src: './dist/html',
87
+ dist: './dist/mailchimp'
88
+ });
89
+ ```
90
+
91
+ ## Configuration
92
+
93
+ ### Translations
94
+
95
+ Create a `translations.js` file:
96
+
97
+ ```javascript
98
+ module.exports = {
99
+ en: {
100
+ greeting_hello: 'Hello',
101
+ btn_get_started: 'Get Started',
102
+ unsubscribe: 'Unsubscribe'
103
+ },
104
+ fr: {
105
+ greeting_hello: 'Bonjour',
106
+ btn_get_started: 'Commencer',
107
+ unsubscribe: 'Se desabonner'
108
+ }
109
+ };
110
+ ```
111
+
112
+ ### Platform Variable Mappings
113
+
114
+ | Our Variable | Mailchimp | Klaviyo | HubSpot |
115
+ |--------------|-----------|---------|---------|
116
+ | `{{first_name}}` | `*\|FNAME\|*` | `{{ first_name }}` | `{{ contact.firstname }}` |
117
+ | `{{email}}` | `*\|EMAIL\|*` | `{{ email }}` | `{{ contact.email }}` |
118
+ | `{{unsubscribe_url}}` | `*\|UNSUB\|*` | `{% unsubscribe_url %}` | `{{ unsubscribe_link }}` |
119
+
120
+ ## Supported Languages
121
+
122
+ | Code | Language |
123
+ |------|----------|
124
+ | en | English |
125
+ | fr | French |
126
+ | es | Spanish |
127
+ | de | German |
128
+ | it | Italian |
129
+ | pt | Portuguese |
130
+ | nl | Dutch |
131
+ | ar | Arabic |
132
+
133
+ ## License
134
+
135
+ MIT
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI: Build templates with internationalization
5
+ * Usage: email-i18n --lang fr --src ./src --dist ./dist
6
+ */
7
+
8
+ const path = require('path');
9
+ const fs = require('fs');
10
+ const { translate, DEFAULT_LANGUAGES } = require('../lib/translator');
11
+
12
+ // Parse command line arguments
13
+ const args = process.argv.slice(2);
14
+
15
+ function getArg(name, defaultValue) {
16
+ const index = args.indexOf(`--${name}`);
17
+ if (index !== -1 && args[index + 1]) {
18
+ return args[index + 1];
19
+ }
20
+ return defaultValue;
21
+ }
22
+
23
+ function hasFlag(name) {
24
+ return args.includes(`--${name}`) || args.includes(`-${name[0]}`);
25
+ }
26
+
27
+ // Show help
28
+ if (hasFlag('help') || hasFlag('h')) {
29
+ console.log(`
30
+ 🌍 Universal Email Templates - i18n Build CLI
31
+
32
+ Usage:
33
+ email-i18n [options]
34
+
35
+ Options:
36
+ --all Build all supported languages
37
+ --lang <code> Build specific language (en, fr, es, de, it, pt, nl, ar)
38
+ --src <path> Source directory with MJML files (default: ./src/templates)
39
+ --dist <path> Destination directory (default: ./dist/i18n)
40
+ --config <path> Path to translations.js config file
41
+ --silent Suppress console output
42
+ --help, -h Show this help message
43
+
44
+ Supported Languages:
45
+ ${DEFAULT_LANGUAGES.join(', ')}
46
+
47
+ Examples:
48
+ email-i18n --all
49
+ email-i18n --lang fr
50
+ email-i18n --lang en --src ./templates --dist ./output
51
+ email-i18n --all --config ./my-translations.js
52
+ `);
53
+ process.exit(0);
54
+ }
55
+
56
+ // Load translations config
57
+ let translations = {};
58
+ const configPath = getArg('config', null);
59
+
60
+ if (configPath) {
61
+ const fullConfigPath = path.resolve(configPath);
62
+ if (fs.existsSync(fullConfigPath)) {
63
+ translations = require(fullConfigPath);
64
+ console.log(`📖 Loaded translations from: ${fullConfigPath}\n`);
65
+ } else {
66
+ console.error(`❌ Config file not found: ${fullConfigPath}`);
67
+ process.exit(1);
68
+ }
69
+ } else {
70
+ // Try to find translations.js in common locations
71
+ const possiblePaths = [
72
+ './config/translations.js',
73
+ './translations.js',
74
+ '../config/translations.js'
75
+ ];
76
+
77
+ for (const p of possiblePaths) {
78
+ const fullPath = path.resolve(p);
79
+ if (fs.existsSync(fullPath)) {
80
+ translations = require(fullPath);
81
+ console.log(`📖 Auto-loaded translations from: ${fullPath}\n`);
82
+ break;
83
+ }
84
+ }
85
+ }
86
+
87
+ // Determine languages to build
88
+ let languages = DEFAULT_LANGUAGES;
89
+ const specificLang = getArg('lang', null);
90
+
91
+ if (specificLang) {
92
+ if (!DEFAULT_LANGUAGES.includes(specificLang)) {
93
+ console.error(`❌ Unsupported language: ${specificLang}`);
94
+ console.log(`Supported: ${DEFAULT_LANGUAGES.join(', ')}`);
95
+ process.exit(1);
96
+ }
97
+ languages = [specificLang];
98
+ } else if (!hasFlag('all')) {
99
+ // Default to just English if no flag specified
100
+ languages = ['en'];
101
+ }
102
+
103
+ // Run translation build
104
+ const options = {
105
+ src: getArg('src', './src/templates'),
106
+ dist: getArg('dist', './dist/i18n'),
107
+ languages,
108
+ translations,
109
+ silent: hasFlag('silent')
110
+ };
111
+
112
+ translate(options)
113
+ .then(results => {
114
+ const hasErrors = Object.values(results).some(r => r.errors > 0);
115
+ if (hasErrors) {
116
+ process.exit(1);
117
+ }
118
+ })
119
+ .catch(error => {
120
+ console.error('❌ i18n build failed:', error.message);
121
+ process.exit(1);
122
+ });
package/bin/build.js ADDED
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI: Build MJML templates to HTML
5
+ * Usage: email-build --src ./src --dist ./dist
6
+ */
7
+
8
+ const { build } = require('../lib/builder');
9
+
10
+ // Parse command line arguments
11
+ const args = process.argv.slice(2);
12
+
13
+ function getArg(name, defaultValue) {
14
+ const index = args.indexOf(`--${name}`);
15
+ if (index !== -1 && args[index + 1]) {
16
+ return args[index + 1];
17
+ }
18
+ return defaultValue;
19
+ }
20
+
21
+ function hasFlag(name) {
22
+ return args.includes(`--${name}`) || args.includes(`-${name[0]}`);
23
+ }
24
+
25
+ // Show help
26
+ if (hasFlag('help') || hasFlag('h')) {
27
+ console.log(`
28
+ 📧 Universal Email Templates - Build CLI
29
+
30
+ Usage:
31
+ email-build [options]
32
+
33
+ Options:
34
+ --src <path> Source directory with MJML files (default: ./src/templates)
35
+ --dist <path> Destination directory for HTML (default: ./dist/html)
36
+ --minify Minify output HTML
37
+ --silent Suppress console output
38
+ --help, -h Show this help message
39
+
40
+ Examples:
41
+ email-build
42
+ email-build --src ./templates --dist ./output
43
+ email-build --minify
44
+ `);
45
+ process.exit(0);
46
+ }
47
+
48
+ // Run build
49
+ const options = {
50
+ src: getArg('src', './src/templates'),
51
+ dist: getArg('dist', './dist/html'),
52
+ minify: hasFlag('minify'),
53
+ silent: hasFlag('silent')
54
+ };
55
+
56
+ build(options)
57
+ .then(result => {
58
+ if (result.errors > 0) {
59
+ process.exit(1);
60
+ }
61
+ })
62
+ .catch(error => {
63
+ console.error('❌ Build failed:', error.message);
64
+ process.exit(1);
65
+ });
package/bin/export.js ADDED
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI: Export templates to email platforms
5
+ * Usage: email-export --platform mailchimp --src ./dist/html --dist ./dist/mailchimp
6
+ */
7
+
8
+ const { exportTo, exportAll, platforms } = require('../lib/exporter');
9
+
10
+ // Parse command line arguments
11
+ const args = process.argv.slice(2);
12
+
13
+ function getArg(name, defaultValue) {
14
+ const index = args.indexOf(`--${name}`);
15
+ if (index !== -1 && args[index + 1]) {
16
+ return args[index + 1];
17
+ }
18
+ return defaultValue;
19
+ }
20
+
21
+ function hasFlag(name) {
22
+ return args.includes(`--${name}`) || args.includes(`-${name[0]}`);
23
+ }
24
+
25
+ const supportedPlatforms = Object.keys(platforms);
26
+
27
+ // Show help
28
+ if (hasFlag('help') || hasFlag('h')) {
29
+ console.log(`
30
+ 📤 Universal Email Templates - Export CLI
31
+
32
+ Usage:
33
+ email-export --platform <name> [options]
34
+
35
+ Options:
36
+ --platform <name> Target platform: ${supportedPlatforms.join(', ')}
37
+ --all Export to all platforms
38
+ --src <path> Source directory with HTML files (default: ./dist/html)
39
+ --dist <path> Destination directory (default: ./dist/<platform>)
40
+ --silent Suppress console output
41
+ --help, -h Show this help message
42
+
43
+ Supported Platforms:
44
+ mailchimp - Mailchimp merge tags (*|FNAME|*, etc.)
45
+ klaviyo - Klaviyo template tags ({{ first_name }}, etc.)
46
+ hubspot - HubSpot personalization tokens ({{ contact.firstname }}, etc.)
47
+
48
+ Examples:
49
+ email-export --platform mailchimp
50
+ email-export --platform klaviyo --src ./html --dist ./klaviyo
51
+ email-export --all
52
+ `);
53
+ process.exit(0);
54
+ }
55
+
56
+ // Run export
57
+ async function run() {
58
+ const src = getArg('src', './dist/html');
59
+ const silent = hasFlag('silent');
60
+
61
+ if (hasFlag('all')) {
62
+ // Export to all platforms
63
+ console.log('🚀 Exporting to all platforms...\n');
64
+
65
+ const results = await exportAll({ src, silent });
66
+
67
+ console.log('\n' + '='.repeat(50));
68
+ console.log('📊 Export Summary:');
69
+ console.log('='.repeat(50));
70
+
71
+ let totalSuccess = 0;
72
+ let totalErrors = 0;
73
+
74
+ for (const [platform, result] of Object.entries(results)) {
75
+ console.log(` ${platforms[platform].name}: ${result.success} success, ${result.errors} errors`);
76
+ totalSuccess += result.success;
77
+ totalErrors += result.errors;
78
+ }
79
+
80
+ console.log('='.repeat(50));
81
+ console.log(` TOTAL: ${totalSuccess} success, ${totalErrors} errors\n`);
82
+
83
+ if (totalErrors > 0) {
84
+ process.exit(1);
85
+ }
86
+ } else {
87
+ // Export to specific platform
88
+ const platform = getArg('platform', null);
89
+
90
+ if (!platform) {
91
+ console.error('❌ Please specify a platform with --platform <name>');
92
+ console.log(`Supported: ${supportedPlatforms.join(', ')}`);
93
+ console.log('Or use --all to export to all platforms');
94
+ process.exit(1);
95
+ }
96
+
97
+ if (!supportedPlatforms.includes(platform)) {
98
+ console.error(`❌ Unsupported platform: ${platform}`);
99
+ console.log(`Supported: ${supportedPlatforms.join(', ')}`);
100
+ process.exit(1);
101
+ }
102
+
103
+ const dist = getArg('dist', `./dist/${platform}`);
104
+
105
+ const result = await exportTo({ platform, src, dist, silent });
106
+
107
+ if (result.errors > 0) {
108
+ process.exit(1);
109
+ }
110
+ }
111
+ }
112
+
113
+ run().catch(error => {
114
+ console.error('❌ Export failed:', error.message);
115
+ process.exit(1);
116
+ });
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Example translations configuration
3
+ * Copy this file to translations.js and customize
4
+ */
5
+
6
+ module.exports = {
7
+ en: {
8
+ // Greetings
9
+ greeting_hello: 'Hello',
10
+ greeting_hi: 'Hi',
11
+ greeting_dear: 'Dear',
12
+ greeting_welcome: 'Welcome',
13
+ welcome_aboard: 'Welcome aboard!',
14
+
15
+ // Buttons
16
+ btn_get_started: 'Get Started',
17
+ btn_learn_more: 'Learn More',
18
+ btn_shop_now: 'Shop Now',
19
+ btn_view_details: 'View Details',
20
+ btn_subscribe: 'Subscribe',
21
+ btn_confirm: 'Confirm',
22
+ btn_reset_password: 'Reset Password',
23
+ btn_verify_email: 'Verify Email',
24
+ btn_view_order: 'View Order',
25
+ btn_track_order: 'Track Order',
26
+
27
+ // Common UI
28
+ view_in_browser: 'View in browser',
29
+ unsubscribe: 'Unsubscribe',
30
+ privacy_policy: 'Privacy Policy',
31
+ terms_of_service: 'Terms of Service',
32
+ contact_us: 'Contact Us',
33
+ all_rights_reserved: 'All rights reserved',
34
+
35
+ // Order related
36
+ order_number: 'Order Number',
37
+ order_date: 'Order Date',
38
+ order_total: 'Total',
39
+ order_subtotal: 'Subtotal',
40
+ order_shipping: 'Shipping',
41
+ order_tax: 'Tax',
42
+ shipping_address: 'Shipping Address',
43
+ billing_address: 'Billing Address',
44
+
45
+ // Common phrases
46
+ thank_you: 'Thank you',
47
+ questions_reply: 'Questions? Just reply to this email.'
48
+ },
49
+
50
+ fr: {
51
+ // Greetings
52
+ greeting_hello: 'Bonjour',
53
+ greeting_hi: 'Salut',
54
+ greeting_dear: 'Cher',
55
+ greeting_welcome: 'Bienvenue',
56
+ welcome_aboard: 'Bienvenue parmi nous !',
57
+
58
+ // Buttons
59
+ btn_get_started: 'Commencer',
60
+ btn_learn_more: 'En savoir plus',
61
+ btn_shop_now: 'Acheter maintenant',
62
+ btn_view_details: 'Voir les details',
63
+ btn_subscribe: "S'abonner",
64
+ btn_confirm: 'Confirmer',
65
+ btn_reset_password: 'Reinitialiser le mot de passe',
66
+ btn_verify_email: "Verifier l'email",
67
+ btn_view_order: 'Voir la commande',
68
+ btn_track_order: 'Suivre la commande',
69
+
70
+ // Common UI
71
+ view_in_browser: 'Voir dans le navigateur',
72
+ unsubscribe: 'Se desabonner',
73
+ privacy_policy: 'Politique de confidentialite',
74
+ terms_of_service: "Conditions d'utilisation",
75
+ contact_us: 'Nous contacter',
76
+ all_rights_reserved: 'Tous droits reserves',
77
+
78
+ // Order related
79
+ order_number: 'Numero de commande',
80
+ order_date: 'Date de commande',
81
+ order_total: 'Total',
82
+ order_subtotal: 'Sous-total',
83
+ order_shipping: 'Livraison',
84
+ order_tax: 'TVA',
85
+ shipping_address: 'Adresse de livraison',
86
+ billing_address: 'Adresse de facturation',
87
+
88
+ // Common phrases
89
+ thank_you: 'Merci',
90
+ questions_reply: 'Des questions ? Repondez simplement a cet email.'
91
+ },
92
+
93
+ es: {
94
+ greeting_hello: 'Hola',
95
+ greeting_welcome: 'Bienvenido',
96
+ btn_get_started: 'Comenzar',
97
+ btn_learn_more: 'Saber mas',
98
+ unsubscribe: 'Darse de baja',
99
+ thank_you: 'Gracias'
100
+ },
101
+
102
+ de: {
103
+ greeting_hello: 'Hallo',
104
+ greeting_welcome: 'Willkommen',
105
+ btn_get_started: 'Loslegen',
106
+ btn_learn_more: 'Mehr erfahren',
107
+ unsubscribe: 'Abmelden',
108
+ thank_you: 'Danke'
109
+ }
110
+ };
package/index.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Universal Email Templates CLI
3
+ * Build, translate, and export MJML email templates
4
+ */
5
+
6
+ const { build } = require('./lib/builder');
7
+ const { translate, applyTranslations } = require('./lib/translator');
8
+ const { exportTo, platforms } = require('./lib/exporter');
9
+
10
+ module.exports = {
11
+ // Main functions
12
+ build,
13
+ translate,
14
+ exportTo,
15
+
16
+ // Utilities
17
+ applyTranslations,
18
+ platforms,
19
+
20
+ // Constants
21
+ supportedLanguages: ['en', 'fr', 'es', 'de', 'it', 'pt', 'nl', 'ar'],
22
+ supportedPlatforms: ['mailchimp', 'klaviyo', 'hubspot']
23
+ };
package/lib/builder.js ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * MJML Build Functions
3
+ * Compiles MJML templates to HTML
4
+ */
5
+
6
+ const { execSync } = require('child_process');
7
+ const fs = require('fs-extra');
8
+ const path = require('path');
9
+ const glob = require('glob');
10
+
11
+ /**
12
+ * Build MJML templates to HTML
13
+ * @param {Object} options - Build options
14
+ * @param {string} options.src - Source directory with MJML files
15
+ * @param {string} options.dist - Destination directory for HTML files
16
+ * @param {boolean} [options.minify=false] - Minify output HTML
17
+ * @param {boolean} [options.silent=false] - Suppress console output
18
+ * @returns {Promise<{success: number, errors: number, files: string[]}>}
19
+ */
20
+ async function build(options = {}) {
21
+ const {
22
+ src = './src/templates',
23
+ dist = './dist/html',
24
+ minify = false,
25
+ silent = false
26
+ } = options;
27
+
28
+ const srcDir = path.resolve(src);
29
+ const distDir = path.resolve(dist);
30
+
31
+ // Ensure dist directory exists
32
+ await fs.ensureDir(distDir);
33
+
34
+ // Find all MJML files
35
+ const mjmlFiles = glob.sync('**/*.mjml', { cwd: srcDir });
36
+
37
+ if (!silent) {
38
+ console.log(`\n🚀 Building ${mjmlFiles.length} MJML templates...\n`);
39
+ }
40
+
41
+ let successCount = 0;
42
+ let errorCount = 0;
43
+ const processedFiles = [];
44
+
45
+ for (const file of mjmlFiles) {
46
+ const inputPath = path.join(srcDir, file);
47
+ const outputPath = path.join(distDir, file.replace('.mjml', '.html'));
48
+ const outputDir = path.dirname(outputPath);
49
+
50
+ // Ensure output subdirectory exists
51
+ await fs.ensureDir(outputDir);
52
+
53
+ try {
54
+ const minifyFlag = minify ? '--config.minify=true' : '';
55
+ execSync(`npx mjml "${inputPath}" -o "${outputPath}" ${minifyFlag}`, {
56
+ stdio: 'pipe'
57
+ });
58
+
59
+ if (!silent) {
60
+ console.log(`✅ ${file}`);
61
+ }
62
+
63
+ successCount++;
64
+ processedFiles.push(outputPath);
65
+ } catch (error) {
66
+ if (!silent) {
67
+ console.log(`❌ ${file}`);
68
+ console.log(` Error: ${error.message}`);
69
+ }
70
+ errorCount++;
71
+ }
72
+ }
73
+
74
+ if (!silent) {
75
+ console.log(`\n📊 Build complete: ${successCount} success, ${errorCount} errors`);
76
+ console.log(`📁 Output: ${distDir}\n`);
77
+ }
78
+
79
+ return {
80
+ success: successCount,
81
+ errors: errorCount,
82
+ files: processedFiles
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Build a single MJML file
88
+ * @param {string} inputPath - Path to MJML file
89
+ * @param {string} outputPath - Path for output HTML
90
+ * @param {Object} [options] - Build options
91
+ * @returns {Promise<string>} - Output HTML content
92
+ */
93
+ async function buildSingle(inputPath, outputPath, options = {}) {
94
+ const { minify = false } = options;
95
+
96
+ await fs.ensureDir(path.dirname(outputPath));
97
+
98
+ const minifyFlag = minify ? '--config.minify=true' : '';
99
+ execSync(`npx mjml "${inputPath}" -o "${outputPath}" ${minifyFlag}`, {
100
+ stdio: 'pipe'
101
+ });
102
+
103
+ return fs.readFile(outputPath, 'utf8');
104
+ }
105
+
106
+ module.exports = {
107
+ build,
108
+ buildSingle
109
+ };
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Platform Export Functions
3
+ * Convert templates to Mailchimp, Klaviyo, HubSpot formats
4
+ */
5
+
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+ const glob = require('glob');
9
+ const cheerio = require('cheerio');
10
+
11
+ /**
12
+ * Platform-specific variable mappings
13
+ */
14
+ const platforms = {
15
+ mailchimp: {
16
+ name: 'Mailchimp',
17
+ variables: {
18
+ '{{first_name}}': '*|FNAME|*',
19
+ '{{last_name}}': '*|LNAME|*',
20
+ '{{email}}': '*|EMAIL|*',
21
+ '{{company_name}}': '*|LIST:COMPANY|*',
22
+ '{{company_address}}': '*|LIST:ADDRESS|*',
23
+ '{{unsubscribe_url}}': '*|UNSUB|*',
24
+ '{{preferences_url}}': '*|UPDATE_PROFILE|*',
25
+ '{{view_in_browser}}': '*|ARCHIVE|*',
26
+ '{{current_year}}': '*|CURRENT_YEAR|*',
27
+ '{{forward_url}}': '*|FORWARD|*'
28
+ },
29
+ trackingPixel: '<img src="*|OPEN_TRACKING_URL|*" width="1" height="1" style="display:none;">'
30
+ },
31
+
32
+ klaviyo: {
33
+ name: 'Klaviyo',
34
+ variables: {
35
+ '{{first_name}}': '{{ first_name|default:"" }}',
36
+ '{{last_name}}': '{{ last_name|default:"" }}',
37
+ '{{email}}': '{{ email }}',
38
+ '{{company_name}}': '{{ organization.name }}',
39
+ '{{company_address}}': '{{ organization.address }}',
40
+ '{{unsubscribe_url}}': '{% unsubscribe_url %}',
41
+ '{{preferences_url}}': '{% manage_preferences_url %}',
42
+ '{{view_in_browser}}': '{% view_in_browser_url %}',
43
+ '{{current_year}}': '{{ "now"|date:"Y" }}'
44
+ },
45
+ trackingPixel: null
46
+ },
47
+
48
+ hubspot: {
49
+ name: 'HubSpot',
50
+ variables: {
51
+ '{{first_name}}': '{{ contact.firstname }}',
52
+ '{{last_name}}': '{{ contact.lastname }}',
53
+ '{{email}}': '{{ contact.email }}',
54
+ '{{company_name}}': '{{ site_settings.company_name }}',
55
+ '{{company_address}}': '{{ site_settings.company_street_address_1 }}',
56
+ '{{unsubscribe_url}}': '{{ unsubscribe_link }}',
57
+ '{{preferences_url}}': '{{ unsubscribe_link }}',
58
+ '{{view_in_browser}}': '{{ view_as_page_url }}',
59
+ '{{current_year}}': '{{ year }}'
60
+ },
61
+ trackingPixel: null
62
+ }
63
+ };
64
+
65
+ /**
66
+ * Escape regex special characters
67
+ */
68
+ function escapeRegex(string) {
69
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
70
+ }
71
+
72
+ /**
73
+ * Export templates to a specific platform format
74
+ * @param {Object} options - Export options
75
+ * @param {string} options.platform - Target platform (mailchimp, klaviyo, hubspot)
76
+ * @param {string} options.src - Source directory with HTML files
77
+ * @param {string} options.dist - Destination directory
78
+ * @param {boolean} [options.silent=false] - Suppress console output
79
+ * @returns {Promise<{success: number, errors: number, files: string[]}>}
80
+ */
81
+ async function exportTo(options = {}) {
82
+ const {
83
+ platform,
84
+ src = './dist/html',
85
+ dist,
86
+ silent = false
87
+ } = options;
88
+
89
+ if (!platform || !platforms[platform]) {
90
+ throw new Error(`Invalid platform. Supported: ${Object.keys(platforms).join(', ')}`);
91
+ }
92
+
93
+ const platformConfig = platforms[platform];
94
+ const srcDir = path.resolve(src);
95
+ const distDir = path.resolve(dist || `./dist/${platform}`);
96
+
97
+ if (!silent) {
98
+ console.log(`🚀 Starting ${platformConfig.name} export...\n`);
99
+ }
100
+
101
+ // Ensure destination directory exists
102
+ await fs.ensureDir(distDir);
103
+
104
+ // Get all HTML files
105
+ const htmlFiles = glob.sync('**/*.html', { cwd: srcDir });
106
+
107
+ if (htmlFiles.length === 0) {
108
+ if (!silent) {
109
+ console.log('⚠️ No HTML files found. Run build first.\n');
110
+ }
111
+ return { success: 0, errors: 0, files: [] };
112
+ }
113
+
114
+ if (!silent) {
115
+ console.log(`📁 Found ${htmlFiles.length} templates to convert\n`);
116
+ }
117
+
118
+ let successCount = 0;
119
+ let errorCount = 0;
120
+ const processedFiles = [];
121
+
122
+ for (const file of htmlFiles) {
123
+ const sourcePath = path.join(srcDir, file);
124
+ const destPath = path.join(distDir, file);
125
+
126
+ try {
127
+ // Read HTML content
128
+ let html = await fs.readFile(sourcePath, 'utf8');
129
+
130
+ // Replace variables with platform-specific tags
131
+ for (const [ourVar, platformVar] of Object.entries(platformConfig.variables)) {
132
+ html = html.replace(new RegExp(escapeRegex(ourVar), 'g'), platformVar);
133
+ }
134
+
135
+ // Add tracking pixel if needed (Mailchimp)
136
+ if (platformConfig.trackingPixel) {
137
+ const $ = cheerio.load(html);
138
+ if (!$('img[src*="TRACKING"]').length && !$('img[src*="tracking"]').length) {
139
+ $('body').append(platformConfig.trackingPixel);
140
+ }
141
+ html = $.html();
142
+ }
143
+
144
+ // Ensure destination subdirectory exists
145
+ await fs.ensureDir(path.dirname(destPath));
146
+
147
+ // Write converted file
148
+ await fs.writeFile(destPath, html);
149
+
150
+ if (!silent) {
151
+ console.log(`✅ ${file}`);
152
+ }
153
+
154
+ successCount++;
155
+ processedFiles.push(destPath);
156
+ } catch (error) {
157
+ if (!silent) {
158
+ console.log(`❌ ${file}`);
159
+ console.log(` Error: ${error.message}`);
160
+ }
161
+ errorCount++;
162
+ }
163
+ }
164
+
165
+ if (!silent) {
166
+ console.log(`\n🎉 ${platformConfig.name} export complete!`);
167
+ console.log(`📁 Files saved to: ${distDir}\n`);
168
+ }
169
+
170
+ return {
171
+ success: successCount,
172
+ errors: errorCount,
173
+ files: processedFiles
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Export to all platforms at once
179
+ */
180
+ async function exportAll(options = {}) {
181
+ const { src, distBase = './dist', silent = false } = options;
182
+ const results = {};
183
+
184
+ for (const platform of Object.keys(platforms)) {
185
+ results[platform] = await exportTo({
186
+ platform,
187
+ src,
188
+ dist: path.join(distBase, platform),
189
+ silent
190
+ });
191
+ }
192
+
193
+ return results;
194
+ }
195
+
196
+ module.exports = {
197
+ exportTo,
198
+ exportAll,
199
+ platforms,
200
+ escapeRegex
201
+ };
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Translation Functions
3
+ * Apply translations to MJML/HTML templates
4
+ */
5
+
6
+ const { execSync } = require('child_process');
7
+ const fs = require('fs-extra');
8
+ const path = require('path');
9
+ const glob = require('glob');
10
+
11
+ // Default supported languages
12
+ const DEFAULT_LANGUAGES = ['en', 'fr', 'es', 'de', 'it', 'pt', 'nl', 'ar'];
13
+
14
+ /**
15
+ * Get translation value
16
+ * @param {Object} translations - Translation object
17
+ * @param {string} key - Translation key
18
+ * @param {string} lang - Language code
19
+ * @returns {string} - Translated text
20
+ */
21
+ function t(translations, key, lang) {
22
+ if (translations[lang] && translations[lang][key]) {
23
+ return translations[lang][key];
24
+ }
25
+ if (translations.en && translations.en[key]) {
26
+ return translations.en[key];
27
+ }
28
+ return `{{t.${key}}}`;
29
+ }
30
+
31
+ /**
32
+ * Apply translations to content
33
+ * @param {string} content - MJML or HTML content
34
+ * @param {string} lang - Target language
35
+ * @param {Object} translations - Translation dictionary
36
+ * @returns {string} - Translated content
37
+ */
38
+ function applyTranslations(content, lang, translations) {
39
+ let result = content;
40
+
41
+ // Replace {{t:key}} and {{t.key}} patterns
42
+ const translationPatternColon = /\{\{t:(\w+)\}\}/g;
43
+ const translationPatternDot = /\{\{t\.(\w+)\}\}/g;
44
+
45
+ result = result.replace(translationPatternColon, (match, key) => {
46
+ return t(translations, key, lang);
47
+ });
48
+
49
+ result = result.replace(translationPatternDot, (match, key) => {
50
+ return t(translations, key, lang);
51
+ });
52
+
53
+ return result;
54
+ }
55
+
56
+ /**
57
+ * Translate templates to multiple languages
58
+ * @param {Object} options - Translation options
59
+ * @param {string} options.src - Source directory with MJML files
60
+ * @param {string} options.dist - Destination directory
61
+ * @param {string[]} [options.languages] - Languages to build
62
+ * @param {Object} options.translations - Translation dictionary
63
+ * @param {Object} [options.themes] - Theme configurations
64
+ * @param {boolean} [options.silent=false] - Suppress console output
65
+ * @returns {Promise<Object>} - Results by language
66
+ */
67
+ async function translate(options = {}) {
68
+ const {
69
+ src = './src/templates',
70
+ dist = './dist/i18n',
71
+ languages = DEFAULT_LANGUAGES,
72
+ translations = {},
73
+ themes = null,
74
+ silent = false
75
+ } = options;
76
+
77
+ const srcDir = path.resolve(src);
78
+ const distDir = path.resolve(dist);
79
+ const results = {};
80
+
81
+ if (!silent) {
82
+ console.log(`🚀 Building templates in ${languages.length} languages: ${languages.join(', ')}...\n`);
83
+ }
84
+
85
+ for (const lang of languages) {
86
+ results[lang] = await buildLanguage({
87
+ lang,
88
+ srcDir,
89
+ distDir,
90
+ translations,
91
+ themes,
92
+ silent
93
+ });
94
+ }
95
+
96
+ if (!silent) {
97
+ console.log('\n' + '='.repeat(50));
98
+ console.log('📊 All languages build summary:');
99
+ console.log('='.repeat(50));
100
+
101
+ let totalSuccess = 0;
102
+ let totalErrors = 0;
103
+
104
+ for (const [lang, result] of Object.entries(results)) {
105
+ console.log(` ${lang.toUpperCase()}: ${result.success} success, ${result.errors} errors`);
106
+ totalSuccess += result.success;
107
+ totalErrors += result.errors;
108
+ }
109
+
110
+ console.log('='.repeat(50));
111
+ console.log(` TOTAL: ${totalSuccess} success, ${totalErrors} errors\n`);
112
+ }
113
+
114
+ return results;
115
+ }
116
+
117
+ /**
118
+ * Build templates for a specific language
119
+ */
120
+ async function buildLanguage(options) {
121
+ const { lang, srcDir, distDir, translations, themes, silent } = options;
122
+
123
+ const langDir = path.join(distDir, lang);
124
+ const tempDir = path.join(distDir, '.temp-i18n');
125
+
126
+ // Ensure directories exist
127
+ await fs.ensureDir(langDir);
128
+ await fs.remove(tempDir);
129
+ await fs.ensureDir(tempDir);
130
+
131
+ // Find all MJML files
132
+ const mjmlFiles = glob.sync('**/*.mjml', { cwd: srcDir });
133
+
134
+ if (!silent) {
135
+ console.log(`\n🌍 Building ${mjmlFiles.length} templates in ${lang.toUpperCase()}...\n`);
136
+ }
137
+
138
+ let successCount = 0;
139
+ let errorCount = 0;
140
+
141
+ for (const file of mjmlFiles) {
142
+ const inputPath = path.join(srcDir, file);
143
+ const tempPath = path.join(tempDir, file);
144
+ const outputPath = path.join(langDir, file.replace('.mjml', '.html'));
145
+
146
+ await fs.ensureDir(path.dirname(tempPath));
147
+ await fs.ensureDir(path.dirname(outputPath));
148
+
149
+ try {
150
+ // Read and translate MJML
151
+ let mjmlContent = await fs.readFile(inputPath, 'utf-8');
152
+ mjmlContent = applyTranslations(mjmlContent, lang, translations);
153
+
154
+ // Apply theme if provided
155
+ if (themes) {
156
+ mjmlContent = applyTheme(mjmlContent, themes);
157
+ }
158
+
159
+ // Write temp file
160
+ await fs.writeFile(tempPath, mjmlContent);
161
+
162
+ // Build MJML to HTML
163
+ execSync(`npx mjml "${tempPath}" -o "${outputPath}"`, { stdio: 'pipe' });
164
+
165
+ if (!silent) {
166
+ console.log(`✅ ${file}`);
167
+ }
168
+ successCount++;
169
+ } catch (error) {
170
+ if (!silent) {
171
+ console.log(`❌ ${file}`);
172
+ console.log(` Error: ${error.message}`);
173
+ }
174
+ errorCount++;
175
+ }
176
+ }
177
+
178
+ // Clean up temp directory
179
+ await fs.remove(tempDir);
180
+
181
+ if (!silent) {
182
+ console.log(`\n📊 Build complete: ${successCount} success, ${errorCount} errors`);
183
+ }
184
+
185
+ return { success: successCount, errors: errorCount };
186
+ }
187
+
188
+ /**
189
+ * Apply theme colors to content
190
+ */
191
+ function applyTheme(content, themeColors) {
192
+ let result = content;
193
+
194
+ const colorMappings = {
195
+ '#3B82F6': themeColors.primary,
196
+ '#2563EB': themeColors.primary_dark,
197
+ '#DBEAFE': themeColors.primary_light,
198
+ '#1F2937': themeColors.text_primary,
199
+ '#4B5563': themeColors.text_secondary,
200
+ '#FFFFFF': themeColors.surface,
201
+ '#F9FAFB': themeColors.background
202
+ };
203
+
204
+ for (const [original, replacement] of Object.entries(colorMappings)) {
205
+ if (replacement) {
206
+ const regex = new RegExp(original.replace('#', '\\#'), 'gi');
207
+ result = result.replace(regex, replacement);
208
+ }
209
+ }
210
+
211
+ return result;
212
+ }
213
+
214
+ module.exports = {
215
+ translate,
216
+ applyTranslations,
217
+ buildLanguage,
218
+ applyTheme,
219
+ DEFAULT_LANGUAGES
220
+ };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "universal-email-templates-cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI tools for building, translating and exporting professional MJML email templates to Mailchimp, Klaviyo, and HubSpot",
5
+ "author": "Atsé Fabrice Igor KOMAN <your.email@example.com>",
6
+ "license": "MIT",
7
+ "main": "index.js",
8
+ "bin": {
9
+ "email-build": "./bin/build.js",
10
+ "email-i18n": "./bin/build-i18n.js",
11
+ "email-export": "./bin/export.js"
12
+ },
13
+ "files": [
14
+ "bin/",
15
+ "lib/",
16
+ "config/",
17
+ "index.js",
18
+ "README.md"
19
+ ],
20
+ "keywords": [
21
+ "email-templates",
22
+ "mjml",
23
+ "newsletter",
24
+ "mailchimp",
25
+ "klaviyo",
26
+ "hubspot",
27
+ "email-marketing",
28
+ "responsive-email",
29
+ "i18n",
30
+ "internationalization",
31
+ "cli"
32
+ ],
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/NumeriKStudio/universal-email-templates-cli.git"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/NumeriKStudio/universal-email-templates-cli/issues"
39
+ },
40
+ "homepage": "https://github.com/NumeriKStudio/universal-email-templates-cli#readme",
41
+ "engines": {
42
+ "node": ">=16.0.0"
43
+ },
44
+ "scripts": {
45
+ "build": "node bin/build.js",
46
+ "build:i18n": "node bin/build-i18n.js --all",
47
+ "build:lang": "node bin/build-i18n.js",
48
+ "export:mailchimp": "node bin/export.js --platform mailchimp",
49
+ "export:klaviyo": "node bin/export.js --platform klaviyo",
50
+ "export:hubspot": "node bin/export.js --platform hubspot",
51
+ "test": "node test/index.js",
52
+ "prepublishOnly": "npm test"
53
+ },
54
+ "dependencies": {
55
+ "cheerio": "^1.0.0-rc.12",
56
+ "fs-extra": "^11.2.0",
57
+ "glob": "^10.3.10",
58
+ "mjml": "^4.15.3"
59
+ },
60
+ "devDependencies": {
61
+ "jest": "^29.7.0"
62
+ }
63
+ }