glasswork 0.10.0 → 0.11.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/dist/email/compiler/code-generator.d.ts +11 -0
- package/dist/email/compiler/code-generator.d.ts.map +1 -0
- package/dist/email/compiler/code-generator.js +319 -0
- package/dist/email/compiler/code-generator.js.map +1 -0
- package/dist/email/compiler/compile-templates.d.ts +68 -0
- package/dist/email/compiler/compile-templates.d.ts.map +1 -0
- package/dist/email/compiler/compile-templates.js +156 -0
- package/dist/email/compiler/compile-templates.js.map +1 -0
- package/dist/email/compiler/compiler.d.ts +30 -0
- package/dist/email/compiler/compiler.d.ts.map +1 -0
- package/dist/email/compiler/compiler.js +510 -0
- package/dist/email/compiler/compiler.js.map +1 -0
- package/dist/email/compiler/index.d.ts +4 -0
- package/dist/email/compiler/index.d.ts.map +1 -0
- package/dist/email/compiler/index.js +3 -0
- package/dist/email/compiler/index.js.map +1 -0
- package/dist/email/compiler/tokenizer.d.ts +7 -0
- package/dist/email/compiler/tokenizer.d.ts.map +1 -0
- package/dist/email/compiler/tokenizer.js +154 -0
- package/dist/email/compiler/tokenizer.js.map +1 -0
- package/dist/email/compiler/type-extractor.d.ts +11 -0
- package/dist/email/compiler/type-extractor.d.ts.map +1 -0
- package/dist/email/compiler/type-extractor.js +296 -0
- package/dist/email/compiler/type-extractor.js.map +1 -0
- package/dist/email/compiler/types.d.ts +108 -0
- package/dist/email/compiler/types.d.ts.map +1 -0
- package/dist/email/compiler/types.js +2 -0
- package/dist/email/compiler/types.js.map +1 -0
- package/dist/email/config.d.ts +89 -0
- package/dist/email/config.d.ts.map +1 -0
- package/dist/email/config.js +55 -0
- package/dist/email/config.js.map +1 -0
- package/dist/email/email-service.d.ts +54 -0
- package/dist/email/email-service.d.ts.map +1 -0
- package/dist/email/email-service.js +94 -0
- package/dist/email/email-service.js.map +1 -0
- package/dist/email/index.d.ts +12 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +23 -0
- package/dist/email/index.js.map +1 -0
- package/dist/email/template-registry.d.ts +83 -0
- package/dist/email/template-registry.d.ts.map +1 -0
- package/dist/email/template-registry.js +85 -0
- package/dist/email/template-registry.js.map +1 -0
- package/dist/email/templated-email-service.d.ts +59 -0
- package/dist/email/templated-email-service.d.ts.map +1 -0
- package/dist/email/templated-email-service.js +63 -0
- package/dist/email/templated-email-service.js.map +1 -0
- package/dist/email/transports/index.d.ts +3 -0
- package/dist/email/transports/index.d.ts.map +1 -0
- package/dist/email/transports/index.js +3 -0
- package/dist/email/transports/index.js.map +1 -0
- package/dist/email/transports/mock-transport.d.ts +74 -0
- package/dist/email/transports/mock-transport.d.ts.map +1 -0
- package/dist/email/transports/mock-transport.js +102 -0
- package/dist/email/transports/mock-transport.js.map +1 -0
- package/dist/email/transports/ses-transport.d.ts +65 -0
- package/dist/email/transports/ses-transport.d.ts.map +1 -0
- package/dist/email/transports/ses-transport.js +290 -0
- package/dist/email/transports/ses-transport.js.map +1 -0
- package/dist/email/types.d.ts +128 -0
- package/dist/email/types.d.ts.map +1 -0
- package/dist/email/types.js +2 -0
- package/dist/email/types.js.map +1 -0
- package/dist/email/webhooks/handler-factory.d.ts +41 -0
- package/dist/email/webhooks/handler-factory.d.ts.map +1 -0
- package/dist/email/webhooks/handler-factory.js +136 -0
- package/dist/email/webhooks/handler-factory.js.map +1 -0
- package/dist/email/webhooks/index.d.ts +6 -0
- package/dist/email/webhooks/index.d.ts.map +1 -0
- package/dist/email/webhooks/index.js +9 -0
- package/dist/email/webhooks/index.js.map +1 -0
- package/dist/email/webhooks/notification-parser.d.ts +45 -0
- package/dist/email/webhooks/notification-parser.d.ts.map +1 -0
- package/dist/email/webhooks/notification-parser.js +150 -0
- package/dist/email/webhooks/notification-parser.js.map +1 -0
- package/dist/email/webhooks/signature-verification.d.ts +38 -0
- package/dist/email/webhooks/signature-verification.d.ts.map +1 -0
- package/dist/email/webhooks/signature-verification.js +179 -0
- package/dist/email/webhooks/signature-verification.js.map +1 -0
- package/dist/email/webhooks/subscription-handler.d.ts +37 -0
- package/dist/email/webhooks/subscription-handler.d.ts.map +1 -0
- package/dist/email/webhooks/subscription-handler.js +99 -0
- package/dist/email/webhooks/subscription-handler.js.map +1 -0
- package/dist/email/webhooks/types.d.ts +187 -0
- package/dist/email/webhooks/types.d.ts.map +1 -0
- package/dist/email/webhooks/types.js +5 -0
- package/dist/email/webhooks/types.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -1
- package/package.json +10 -2
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { InferredType, Token } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates TypeScript code from tokenized template.
|
|
4
|
+
* Converts control flow markers and variables to JS template literals.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateCode(name: string, tokens: Token[], types: Map<string, InferredType>, html: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Alternative code generator that produces cleaner output for simple templates
|
|
9
|
+
*/
|
|
10
|
+
export declare function generateCodeWithContext(name: string, compiledHtml: string, tokens: Token[], types: Map<string, InferredType>): string;
|
|
11
|
+
//# sourceMappingURL=code-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-generator.d.ts","sourceRoot":"","sources":["../../../src/email/compiler/code-generator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEtD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,KAAK,EAAE,EACf,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAChC,IAAI,EAAE,MAAM,GACX,MAAM,CAgDR;AAoID;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,KAAK,EAAE,EACf,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAC/B,MAAM,CAgDR"}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { generateInterface } from './type-extractor.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates TypeScript code from tokenized template.
|
|
4
|
+
* Converts control flow markers and variables to JS template literals.
|
|
5
|
+
*/
|
|
6
|
+
export function generateCode(name, tokens, types, html) {
|
|
7
|
+
const interfaceName = toPascalCase(name) + 'Context';
|
|
8
|
+
const interfaceCode = generateInterface(interfaceName, types);
|
|
9
|
+
// Transform the HTML with control flow markers into template literal
|
|
10
|
+
const transformedHtml = transformToTemplateLiteral(tokens);
|
|
11
|
+
const code = `// This file is auto-generated by @glasswork/email. Do not edit manually.
|
|
12
|
+
|
|
13
|
+
${interfaceCode}
|
|
14
|
+
|
|
15
|
+
export function render(ctx: ${interfaceName}): { html: string; text: string } {
|
|
16
|
+
const html = \`${escapeBackticks(transformedHtml)}\`;
|
|
17
|
+
|
|
18
|
+
// Auto-generated plain text version
|
|
19
|
+
const text = htmlToText(html);
|
|
20
|
+
|
|
21
|
+
return { html, text };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function htmlToText(html: string): string {
|
|
25
|
+
return html
|
|
26
|
+
// Remove style and script tags with content
|
|
27
|
+
.replace(/<style[^>]*>[sS]*?</style>/gi, '')
|
|
28
|
+
.replace(/<script[^>]*>[sS]*?</script>/gi, '')
|
|
29
|
+
// Convert line breaks
|
|
30
|
+
.replace(/<br\\s*\\/?>/gi, '\\n')
|
|
31
|
+
// Convert block elements to newlines
|
|
32
|
+
.replace(/<\\/(p|div|tr|h[1-6])>/gi, '\\n')
|
|
33
|
+
// Convert list items
|
|
34
|
+
.replace(/<li[^>]*>/gi, '• ')
|
|
35
|
+
.replace(/<\\/li>/gi, '\\n')
|
|
36
|
+
// Remove remaining HTML tags
|
|
37
|
+
.replace(/<[^>]+>/g, '')
|
|
38
|
+
// Decode common HTML entities
|
|
39
|
+
.replace(/ /gi, ' ')
|
|
40
|
+
.replace(/&/gi, '&')
|
|
41
|
+
.replace(/</gi, '<')
|
|
42
|
+
.replace(/>/gi, '>')
|
|
43
|
+
.replace(/"/gi, '"')
|
|
44
|
+
.replace(/'/gi, "'")
|
|
45
|
+
// Clean up whitespace
|
|
46
|
+
.replace(/\\n\\s*\\n\\s*\\n/g, '\\n\\n')
|
|
47
|
+
.trim();
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
return code;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Transforms tokens back into a string with JS template literal expressions.
|
|
54
|
+
*/
|
|
55
|
+
function transformToTemplateLiteral(tokens) {
|
|
56
|
+
const parts = [];
|
|
57
|
+
const blockStack = [];
|
|
58
|
+
for (const token of tokens) {
|
|
59
|
+
switch (token.type) {
|
|
60
|
+
case 'text':
|
|
61
|
+
parts.push(token.content);
|
|
62
|
+
break;
|
|
63
|
+
case 'variable':
|
|
64
|
+
parts.push(transformVariable(token));
|
|
65
|
+
break;
|
|
66
|
+
case 'if':
|
|
67
|
+
blockStack.push('if');
|
|
68
|
+
parts.push(`\${${transformCondition(token.condition)} ? \``);
|
|
69
|
+
break;
|
|
70
|
+
case 'elseif':
|
|
71
|
+
// Close previous if/elseif block and start new conditional
|
|
72
|
+
parts.push(`\` : ${transformCondition(token.condition)} ? \``);
|
|
73
|
+
break;
|
|
74
|
+
case 'else':
|
|
75
|
+
// Close previous if/elseif block and start else
|
|
76
|
+
parts.push('` : `');
|
|
77
|
+
break;
|
|
78
|
+
case 'each':
|
|
79
|
+
blockStack.push('each');
|
|
80
|
+
parts.push(transformEachStart(token));
|
|
81
|
+
break;
|
|
82
|
+
case 'end': {
|
|
83
|
+
const blockType = blockStack.pop();
|
|
84
|
+
if (blockType === 'if') {
|
|
85
|
+
// Close conditional - if no else was provided, use empty string
|
|
86
|
+
parts.push("` : ''}");
|
|
87
|
+
}
|
|
88
|
+
else if (blockType === 'each') {
|
|
89
|
+
parts.push("`).join('')}");
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return parts.join('');
|
|
96
|
+
}
|
|
97
|
+
function transformVariable(token) {
|
|
98
|
+
const path = token.path.join('.');
|
|
99
|
+
// Handle loop context variables
|
|
100
|
+
if (token.path[0].startsWith('@')) {
|
|
101
|
+
const loopVar = token.path[0].slice(1); // Remove @ prefix
|
|
102
|
+
switch (loopVar) {
|
|
103
|
+
case 'index':
|
|
104
|
+
return '${__index}';
|
|
105
|
+
case 'first':
|
|
106
|
+
return '${__index === 0}';
|
|
107
|
+
case 'last':
|
|
108
|
+
return '${__index === __array.length - 1}';
|
|
109
|
+
case 'length':
|
|
110
|
+
return '${__array.length}';
|
|
111
|
+
default:
|
|
112
|
+
return `\${${loopVar}}`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Handle expressions with operators (like @index + 1)
|
|
116
|
+
if (token.expression.includes('+') || token.expression.includes('-')) {
|
|
117
|
+
const transformed = token.expression
|
|
118
|
+
.replace(/@index/g, '__index')
|
|
119
|
+
.replace(/@first/g, '(__index === 0)')
|
|
120
|
+
.replace(/@last/g, '(__index === __array.length - 1)')
|
|
121
|
+
.replace(/@length/g, '__array.length');
|
|
122
|
+
return `\${${transformed}}`;
|
|
123
|
+
}
|
|
124
|
+
if (token.defaultValue !== undefined) {
|
|
125
|
+
return `\${${path} ?? '${token.defaultValue}'}`;
|
|
126
|
+
}
|
|
127
|
+
return `\${${path}}`;
|
|
128
|
+
}
|
|
129
|
+
function transformCondition(condition) {
|
|
130
|
+
// Transform loop context variables in conditions
|
|
131
|
+
return condition
|
|
132
|
+
.replace(/@index/g, '__index')
|
|
133
|
+
.replace(/@first/g, '(__index === 0)')
|
|
134
|
+
.replace(/@last/g, '(__index === __array.length - 1)')
|
|
135
|
+
.replace(/@length/g, '__array.length');
|
|
136
|
+
}
|
|
137
|
+
function transformEachStart(token) {
|
|
138
|
+
const { arrayPath, itemName, indexName } = token;
|
|
139
|
+
// Build the map function with proper destructuring
|
|
140
|
+
const mapParams = indexName ? `(${itemName}, ${indexName})` : `(${itemName}, __index)`;
|
|
141
|
+
// Store array reference for @last, @length access
|
|
142
|
+
return `\${(((__array) => __array.map(${mapParams} => \``;
|
|
143
|
+
}
|
|
144
|
+
function toPascalCase(str) {
|
|
145
|
+
return str
|
|
146
|
+
.split(/[-_\s]+/)
|
|
147
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
148
|
+
.join('');
|
|
149
|
+
}
|
|
150
|
+
function escapeBackticks(str) {
|
|
151
|
+
// Escape backticks that aren't part of our template expressions
|
|
152
|
+
// We need to be careful not to escape the ones we're inserting
|
|
153
|
+
return str.replace(/`/g, '\\`');
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Alternative code generator that produces cleaner output for simple templates
|
|
157
|
+
*/
|
|
158
|
+
export function generateCodeWithContext(name, compiledHtml, tokens, types) {
|
|
159
|
+
const interfaceName = toPascalCase(name) + 'Context';
|
|
160
|
+
const interfaceCode = generateInterface(interfaceName, types);
|
|
161
|
+
// Replace control flow markers and variables in the compiled HTML
|
|
162
|
+
const transformedHtml = transformCompiledHtml(compiledHtml, tokens);
|
|
163
|
+
const code = `// This file is auto-generated by @glasswork/email. Do not edit manually.
|
|
164
|
+
|
|
165
|
+
${interfaceCode}
|
|
166
|
+
|
|
167
|
+
export function render(ctx: ${interfaceName}): { html: string; text: string } {
|
|
168
|
+
const html = \`${escapeBackticks(transformedHtml)}\`;
|
|
169
|
+
|
|
170
|
+
// Auto-generated plain text version
|
|
171
|
+
const text = htmlToText(html);
|
|
172
|
+
|
|
173
|
+
return { html, text };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function htmlToText(html: string): string {
|
|
177
|
+
return html
|
|
178
|
+
// Remove style and script tags with content
|
|
179
|
+
.replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, '')
|
|
180
|
+
.replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, '')
|
|
181
|
+
// Convert line breaks
|
|
182
|
+
.replace(/<br\\s*\\/?>/gi, '\\n')
|
|
183
|
+
// Convert block elements to newlines
|
|
184
|
+
.replace(/<\\/(p|div|tr|h[1-6])>/gi, '\\n')
|
|
185
|
+
// Convert list items
|
|
186
|
+
.replace(/<li[^>]*>/gi, '• ')
|
|
187
|
+
.replace(/<\\/li>/gi, '\\n')
|
|
188
|
+
// Remove remaining HTML tags
|
|
189
|
+
.replace(/<[^>]+>/g, '')
|
|
190
|
+
// Decode common HTML entities
|
|
191
|
+
.replace(/ /gi, ' ')
|
|
192
|
+
.replace(/&/gi, '&')
|
|
193
|
+
.replace(/</gi, '<')
|
|
194
|
+
.replace(/>/gi, '>')
|
|
195
|
+
.replace(/"/gi, '"')
|
|
196
|
+
.replace(/'/gi, "'")
|
|
197
|
+
// Clean up whitespace
|
|
198
|
+
.replace(/\\n\\s*\\n\\s*\\n/g, '\\n\\n')
|
|
199
|
+
.trim();
|
|
200
|
+
}
|
|
201
|
+
`;
|
|
202
|
+
return code;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Transforms compiled HTML (from MJML) by replacing markers with template expressions.
|
|
206
|
+
* This is the second pass of the two-pass compilation.
|
|
207
|
+
*/
|
|
208
|
+
function transformCompiledHtml(html, tokens) {
|
|
209
|
+
// Build a map of original positions to replacements
|
|
210
|
+
// Since MJML compilation may have moved things around, we use regex to find markers
|
|
211
|
+
let result = html;
|
|
212
|
+
// Transform control flow markers (they're preserved in HTML comments)
|
|
213
|
+
result = result.replace(/<!--\s*@if\s+([^-]*?)\s*-->/g, (_match, condition) => {
|
|
214
|
+
return `\${${transformCondition(condition.trim())} ? \``;
|
|
215
|
+
});
|
|
216
|
+
result = result.replace(/<!--\s*@elseif\s+([^-]*?)\s*-->/g, (_match, condition) => {
|
|
217
|
+
return `\` : ${transformCondition(condition.trim())} ? \``;
|
|
218
|
+
});
|
|
219
|
+
result = result.replace(/<!--\s*@else\s*-->/g, '` : `');
|
|
220
|
+
// Handle @each - need to track which @end belongs to which @each/@if
|
|
221
|
+
const eachPattern = /<!--\s*@each\s+(\S+)\s+as\s+(\w+)(?:\s*,\s*(\w+))?\s*-->/g;
|
|
222
|
+
result = result.replace(eachPattern, (_match, arrayPath, itemName, indexName) => {
|
|
223
|
+
const mapParams = indexName ? `(${itemName}, ${indexName})` : `(${itemName}, __index)`;
|
|
224
|
+
return `\${(((__array) => __array.map(${mapParams} => \``;
|
|
225
|
+
});
|
|
226
|
+
// Handle @end markers - need context to know if it's if or each
|
|
227
|
+
// For simplicity in spike, we'll use a simple approach
|
|
228
|
+
result = transformEndMarkers(result);
|
|
229
|
+
// Transform variables
|
|
230
|
+
result = result.replace(/\{\{([^}]+)\}\}/g, (_match, expression) => {
|
|
231
|
+
const trimmed = expression.trim();
|
|
232
|
+
// Check for default value
|
|
233
|
+
const defaultMatch = trimmed.match(/^(.+?)\s*\?\?\s*['"]([^'"]*)['"]\s*$/);
|
|
234
|
+
if (defaultMatch) {
|
|
235
|
+
return `\${${defaultMatch[1].trim()} ?? '${defaultMatch[2]}'}`;
|
|
236
|
+
}
|
|
237
|
+
// Handle loop context variables
|
|
238
|
+
if (trimmed.startsWith('@')) {
|
|
239
|
+
const loopVar = trimmed.slice(1);
|
|
240
|
+
switch (loopVar) {
|
|
241
|
+
case 'index':
|
|
242
|
+
return '${__index}';
|
|
243
|
+
case 'first':
|
|
244
|
+
return '${__index === 0}';
|
|
245
|
+
case 'last':
|
|
246
|
+
return '${__index === __array.length - 1}';
|
|
247
|
+
case 'length':
|
|
248
|
+
return '${__array.length}';
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Handle expressions with loop context
|
|
252
|
+
if (trimmed.includes('@')) {
|
|
253
|
+
const transformed = trimmed
|
|
254
|
+
.replace(/@index/g, '__index')
|
|
255
|
+
.replace(/@first/g, '(__index === 0)')
|
|
256
|
+
.replace(/@last/g, '(__index === __array.length - 1)')
|
|
257
|
+
.replace(/@length/g, '__array.length');
|
|
258
|
+
return `\${${transformed}}`;
|
|
259
|
+
}
|
|
260
|
+
return `\${ctx.${trimmed}}`;
|
|
261
|
+
});
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Transforms @end markers based on context.
|
|
266
|
+
* This is tricky because we need to know if it ends an @if or @each.
|
|
267
|
+
*/
|
|
268
|
+
function transformEndMarkers(html) {
|
|
269
|
+
// Track block types as we encounter them
|
|
270
|
+
const blockStack = [];
|
|
271
|
+
const markers = [];
|
|
272
|
+
// Find all markers
|
|
273
|
+
const markerPattern = /<!--\s*@(if|elseif|else|each|end)(?:\s+[^-]*?)?\s*-->/g;
|
|
274
|
+
let match;
|
|
275
|
+
while ((match = markerPattern.exec(html)) !== null) {
|
|
276
|
+
const type = match[1];
|
|
277
|
+
markers.push({ type, index: match.index, length: match[0].length });
|
|
278
|
+
}
|
|
279
|
+
// Process markers in order and build replacement map
|
|
280
|
+
const replacements = [];
|
|
281
|
+
for (const marker of markers) {
|
|
282
|
+
if (marker.type === 'if') {
|
|
283
|
+
blockStack.push('if');
|
|
284
|
+
}
|
|
285
|
+
else if (marker.type === 'each') {
|
|
286
|
+
blockStack.push('each');
|
|
287
|
+
}
|
|
288
|
+
else if (marker.type === 'end') {
|
|
289
|
+
const blockType = blockStack.pop();
|
|
290
|
+
if (blockType === 'if') {
|
|
291
|
+
replacements.push({
|
|
292
|
+
index: marker.index,
|
|
293
|
+
length: marker.length,
|
|
294
|
+
replacement: "` : ''}",
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
else if (blockType === 'each') {
|
|
298
|
+
replacements.push({
|
|
299
|
+
index: marker.index,
|
|
300
|
+
length: marker.length,
|
|
301
|
+
replacement: "`).join('')})(ctx." + findArrayPath(markers, replacements.length) + ')}', // This is simplified
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// Apply replacements in reverse order to preserve indices
|
|
307
|
+
let result = html;
|
|
308
|
+
for (let i = replacements.length - 1; i >= 0; i--) {
|
|
309
|
+
const { index, length, replacement } = replacements[i];
|
|
310
|
+
result = result.slice(0, index) + replacement + result.slice(index + length);
|
|
311
|
+
}
|
|
312
|
+
return result;
|
|
313
|
+
}
|
|
314
|
+
// Helper to find the array path for an @each block
|
|
315
|
+
function findArrayPath(markers, endIndex) {
|
|
316
|
+
// This is a simplified version - in a real implementation we'd track this properly
|
|
317
|
+
return 'items';
|
|
318
|
+
}
|
|
319
|
+
//# sourceMappingURL=code-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-generator.js","sourceRoot":"","sources":["../../../src/email/compiler/code-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for the template compiler
|
|
3
|
+
*/
|
|
4
|
+
export interface CompileOptions {
|
|
5
|
+
/** Source directory containing MJML templates */
|
|
6
|
+
sourceDir: string;
|
|
7
|
+
/** Output directory for compiled TypeScript files */
|
|
8
|
+
outputDir: string;
|
|
9
|
+
/** Base layout file to wrap templates (optional) */
|
|
10
|
+
layoutFile?: string;
|
|
11
|
+
/** Layout content marker to replace with template content */
|
|
12
|
+
layoutMarker?: string;
|
|
13
|
+
/** Whether to generate an index file */
|
|
14
|
+
generateIndex?: boolean;
|
|
15
|
+
/** File extension for templates (default: .mjml) */
|
|
16
|
+
templateExtension?: string;
|
|
17
|
+
/** Subdirectories to exclude (default: ['layouts']) */
|
|
18
|
+
excludeDirs?: string[];
|
|
19
|
+
/** MJML compiler function (must be provided) */
|
|
20
|
+
mjmlCompile: (mjml: string) => {
|
|
21
|
+
html: string;
|
|
22
|
+
errors: Array<{
|
|
23
|
+
message: string;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Compiled template info
|
|
29
|
+
*/
|
|
30
|
+
export interface CompiledTemplateInfo {
|
|
31
|
+
/** Template name */
|
|
32
|
+
name: string;
|
|
33
|
+
/** Subject line (if found in template) */
|
|
34
|
+
subject?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Result of compiling templates
|
|
38
|
+
*/
|
|
39
|
+
export interface CompileResult {
|
|
40
|
+
/** Number of templates compiled */
|
|
41
|
+
count: number;
|
|
42
|
+
/** List of compiled template info */
|
|
43
|
+
templates: CompiledTemplateInfo[];
|
|
44
|
+
/** Any errors encountered */
|
|
45
|
+
errors: Array<{
|
|
46
|
+
template: string;
|
|
47
|
+
error: string;
|
|
48
|
+
}>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Compiles all MJML templates in a directory
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* import mjml2html from 'mjml';
|
|
56
|
+
* import { compileTemplates } from '@glasswork/email/compiler';
|
|
57
|
+
*
|
|
58
|
+
* const result = await compileTemplates({
|
|
59
|
+
* sourceDir: './src/templates',
|
|
60
|
+
* outputDir: './src/templates.compiled',
|
|
61
|
+
* mjmlCompile: (source) => mjml2html(source, { validationLevel: 'soft' }),
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* console.log(`Compiled ${result.count} templates`);
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function compileTemplates(options: CompileOptions): CompileResult;
|
|
68
|
+
//# sourceMappingURL=compile-templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile-templates.d.ts","sourceRoot":"","sources":["../../../src/email/compiler/compile-templates.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oDAAoD;IACpD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,gDAAgD;IAChD,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,KAAK,CAAC;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;CACrF;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,SAAS,EAAE,oBAAoB,EAAE,CAAC;IAClC,6BAA6B;IAC7B,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,CA0EvE"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { createLogger } from '../../utils/logger.js';
|
|
4
|
+
import { compile } from './compiler.js';
|
|
5
|
+
const logger = createLogger('TemplateCompiler');
|
|
6
|
+
/**
|
|
7
|
+
* Compiles all MJML templates in a directory
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import mjml2html from 'mjml';
|
|
12
|
+
* import { compileTemplates } from '@glasswork/email/compiler';
|
|
13
|
+
*
|
|
14
|
+
* const result = await compileTemplates({
|
|
15
|
+
* sourceDir: './src/templates',
|
|
16
|
+
* outputDir: './src/templates.compiled',
|
|
17
|
+
* mjmlCompile: (source) => mjml2html(source, { validationLevel: 'soft' }),
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* console.log(`Compiled ${result.count} templates`);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function compileTemplates(options) {
|
|
24
|
+
const { sourceDir, outputDir, layoutFile, layoutMarker = '<!-- content -->', generateIndex = true, templateExtension = '.mjml', excludeDirs = ['layouts'], mjmlCompile, } = options;
|
|
25
|
+
// Ensure output directory exists
|
|
26
|
+
mkdirSync(outputDir, { recursive: true });
|
|
27
|
+
// Read layout if provided
|
|
28
|
+
let layout = null;
|
|
29
|
+
if (layoutFile && existsSync(layoutFile)) {
|
|
30
|
+
layout = readFileSync(layoutFile, 'utf-8');
|
|
31
|
+
}
|
|
32
|
+
// Find all template files
|
|
33
|
+
const templateFiles = findTemplateFiles(sourceDir, templateExtension, excludeDirs);
|
|
34
|
+
const result = {
|
|
35
|
+
count: 0,
|
|
36
|
+
templates: [],
|
|
37
|
+
errors: [],
|
|
38
|
+
};
|
|
39
|
+
// Compile each template
|
|
40
|
+
for (const templatePath of templateFiles) {
|
|
41
|
+
const templateName = basename(templatePath, templateExtension);
|
|
42
|
+
try {
|
|
43
|
+
// Read template content
|
|
44
|
+
let source = readFileSync(templatePath, 'utf-8');
|
|
45
|
+
// If layout is provided and template doesn't have full MJML structure, wrap it
|
|
46
|
+
if (layout && !source.trim().startsWith('<mjml')) {
|
|
47
|
+
source = layout.replace(layoutMarker, source);
|
|
48
|
+
}
|
|
49
|
+
// Compile the template
|
|
50
|
+
const compiled = compile(source, templateName, mjmlCompile);
|
|
51
|
+
// Write the compiled output
|
|
52
|
+
const outputPath = join(outputDir, `${templateName}.ts`);
|
|
53
|
+
writeFileSync(outputPath, compiled.source);
|
|
54
|
+
result.count++;
|
|
55
|
+
result.templates.push({
|
|
56
|
+
name: templateName,
|
|
57
|
+
subject: compiled.subject,
|
|
58
|
+
});
|
|
59
|
+
logger.info(`✓ Compiled ${templateName}${compiled.subject ? ` (subject: "${compiled.subject}")` : ''}`);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
63
|
+
result.errors.push({ template: templateName, error: errorMessage });
|
|
64
|
+
logger.error(`✗ Failed to compile ${templateName}: ${errorMessage}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Generate index file
|
|
68
|
+
if (generateIndex && result.templates.length > 0) {
|
|
69
|
+
const indexContent = generateIndexFile(result.templates);
|
|
70
|
+
writeFileSync(join(outputDir, 'index.ts'), indexContent);
|
|
71
|
+
logger.info('✓ Generated index.ts');
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Escapes a string for use in a JavaScript string literal
|
|
77
|
+
*/
|
|
78
|
+
function escapeString(str) {
|
|
79
|
+
return str.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Finds all template files in a directory recursively
|
|
83
|
+
*/
|
|
84
|
+
function findTemplateFiles(dir, extension, excludeDirs) {
|
|
85
|
+
const files = [];
|
|
86
|
+
if (!existsSync(dir)) {
|
|
87
|
+
return files;
|
|
88
|
+
}
|
|
89
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
const fullPath = join(dir, entry.name);
|
|
92
|
+
if (entry.isDirectory()) {
|
|
93
|
+
if (!excludeDirs.includes(entry.name)) {
|
|
94
|
+
files.push(...findTemplateFiles(fullPath, extension, excludeDirs));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (entry.isFile() && entry.name.endsWith(extension)) {
|
|
98
|
+
files.push(fullPath);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return files;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Generates an index file that re-exports all templates
|
|
105
|
+
*/
|
|
106
|
+
function generateIndexFile(templates) {
|
|
107
|
+
const imports = [];
|
|
108
|
+
const registrations = [];
|
|
109
|
+
for (const template of templates) {
|
|
110
|
+
const varName = toCamelCase(template.name);
|
|
111
|
+
imports.push(`import * as ${varName} from './${template.name}.js';`);
|
|
112
|
+
// Include subject if available
|
|
113
|
+
if (template.subject) {
|
|
114
|
+
registrations.push(` .register('${template.name}', ${varName}.render, { subject: '${escapeString(template.subject)}' })`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
registrations.push(` .register('${template.name}', ${varName}.render)`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return `// This file is auto-generated. Do not edit manually.
|
|
121
|
+
import {
|
|
122
|
+
createTemplateRegistry,
|
|
123
|
+
type TemplatedEmailService,
|
|
124
|
+
type TemplateRegistry,
|
|
125
|
+
} from 'glasswork';
|
|
126
|
+
|
|
127
|
+
${imports.join('\n')}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Registry of all compiled email templates
|
|
131
|
+
*/
|
|
132
|
+
export const templates = createTemplateRegistry()
|
|
133
|
+
${registrations.join('\n')};
|
|
134
|
+
|
|
135
|
+
// Re-export individual templates for direct access
|
|
136
|
+
${templates.map((t) => `export * as ${toCamelCase(t.name)} from './${t.name}.js';`).join('\n')}
|
|
137
|
+
|
|
138
|
+
// Type utilities for dependency injection
|
|
139
|
+
type InferTemplates<T> = T extends TemplateRegistry<infer U> ? U : never;
|
|
140
|
+
|
|
141
|
+
/** Template definitions for type-safe email sending */
|
|
142
|
+
export type Templates = InferTemplates<typeof templates>;
|
|
143
|
+
|
|
144
|
+
/** Typed email service for dependency injection */
|
|
145
|
+
export type EmailService = TemplatedEmailService<Templates>;
|
|
146
|
+
`;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Converts a string to camelCase
|
|
150
|
+
*/
|
|
151
|
+
function toCamelCase(str) {
|
|
152
|
+
return str
|
|
153
|
+
.replace(/[-_.]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ''))
|
|
154
|
+
.replace(/^[A-Z]/, (char) => char.toLowerCase());
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=compile-templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile-templates.js","sourceRoot":"","sources":["../../../src/email/compiler/compile-templates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CompiledTemplate } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Two-pass MJML template compiler.
|
|
4
|
+
*
|
|
5
|
+
* Pass 1: MJML → HTML (preserving control flow markers in comments)
|
|
6
|
+
* Pass 2: HTML with markers → TypeScript template literal function
|
|
7
|
+
*
|
|
8
|
+
* This approach allows us to:
|
|
9
|
+
* 1. Use the full power of MJML at build time
|
|
10
|
+
* 2. Generate zero-dependency TypeScript render functions
|
|
11
|
+
* 3. Infer TypeScript types from template usage
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Compiles an MJML template with control flow syntax to a TypeScript render function.
|
|
15
|
+
*
|
|
16
|
+
* @param source - MJML source with control flow markers
|
|
17
|
+
* @param name - Template name (used for interface naming)
|
|
18
|
+
* @param mjmlCompile - MJML compiler function (injected to avoid bundling MJML)
|
|
19
|
+
* @returns Compiled TypeScript source code
|
|
20
|
+
*/
|
|
21
|
+
export declare function compile(source: string, name: string, mjmlCompile: (mjml: string) => {
|
|
22
|
+
html: string;
|
|
23
|
+
errors: Array<{
|
|
24
|
+
message: string;
|
|
25
|
+
}>;
|
|
26
|
+
}): CompiledTemplate;
|
|
27
|
+
export { tokenize } from './tokenizer.js';
|
|
28
|
+
export { extractTypes, generateInterface } from './type-extractor.js';
|
|
29
|
+
export type { CompiledTemplate, CompilerOptions, Token } from './types.js';
|
|
30
|
+
//# sourceMappingURL=compiler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../../../src/email/compiler/compiler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAgB,MAAM,YAAY,CAAC;AAEjE;;;;;;;;;;GAUG;AAEH;;;;;;;GAOG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAClF,gBAAgB,CA2BlB;AAyjBD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACtE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC"}
|