@travetto/email-compiler 3.1.22 → 3.1.23
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/package.json +4 -4
- package/src/compiler.ts +13 -7
- package/src/util.ts +16 -10
- package/support/bin/manager.ts +13 -14
- package/support/bin/send.ts +3 -3
- package/support/cli.email_compile.ts +2 -1
- package/support/cli.email_editor.ts +5 -1
- package/support/cli.email_test.ts +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/email-compiler",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.23",
|
|
4
4
|
"description": "Email compiling module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@travetto/base": "^3.1.3",
|
|
30
|
-
"@travetto/config": "^3.1.
|
|
30
|
+
"@travetto/config": "^3.1.8",
|
|
31
31
|
"@travetto/di": "^3.1.4",
|
|
32
|
-
"@travetto/email": "^3.1.
|
|
32
|
+
"@travetto/email": "^3.1.14",
|
|
33
33
|
"@travetto/image": "^3.1.4",
|
|
34
34
|
"@types/inline-css": "^3.0.1",
|
|
35
35
|
"html-entities": "^2.3.3",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"purgecss": "^5.0.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@travetto/cli": "^3.1.
|
|
41
|
+
"@travetto/cli": "^3.1.9"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"@travetto/cli": {
|
package/src/compiler.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
|
|
3
3
|
import { FileQueryProvider, TypedObject } from '@travetto/base';
|
|
4
|
-
import {
|
|
4
|
+
import { EmailCompileSource, EmailCompiled, EmailCompileContext, MailUtil } from '@travetto/email';
|
|
5
5
|
import { RootIndex, path } from '@travetto/manifest';
|
|
6
6
|
import { DynamicFileLoader } from '@travetto/base/src/internal/file-loader';
|
|
7
7
|
|
|
@@ -15,13 +15,19 @@ const VALID_FILE = (file: string): boolean => /[.](scss|css|png|jpe?g|gif|ya?ml)
|
|
|
15
15
|
export class EmailCompiler {
|
|
16
16
|
|
|
17
17
|
/** Load Template */
|
|
18
|
-
static async loadTemplate(file: string): Promise<
|
|
18
|
+
static async loadTemplate(file: string): Promise<EmailCompileContext> {
|
|
19
19
|
const entry = RootIndex.getEntry(file);
|
|
20
20
|
if (!entry) {
|
|
21
21
|
throw new Error(`Unable to find template for ${file}`);
|
|
22
22
|
}
|
|
23
23
|
const root = (await import(entry.outputFile)).default;
|
|
24
|
-
const
|
|
24
|
+
const og: EmailCompileSource = await root.wrap();
|
|
25
|
+
const res = {
|
|
26
|
+
file: entry.sourceFile,
|
|
27
|
+
module: entry.module,
|
|
28
|
+
...og
|
|
29
|
+
};
|
|
30
|
+
|
|
25
31
|
const mod = RootIndex.getModule(entry.module)!;
|
|
26
32
|
|
|
27
33
|
const resourcePaths = [
|
|
@@ -51,7 +57,7 @@ export class EmailCompiler {
|
|
|
51
57
|
/**
|
|
52
58
|
* Get output files
|
|
53
59
|
*/
|
|
54
|
-
static getOutputFiles(file: string):
|
|
60
|
+
static getOutputFiles(file: string): EmailCompiled {
|
|
55
61
|
const entry = RootIndex.getEntry(file)!;
|
|
56
62
|
const mod = RootIndex.getModule(entry.module)!;
|
|
57
63
|
return EmailCompileUtil.getOutputs(file, path.join(mod.sourcePath, 'resources'));
|
|
@@ -67,12 +73,12 @@ export class EmailCompiler {
|
|
|
67
73
|
/**
|
|
68
74
|
* Write template to file
|
|
69
75
|
*/
|
|
70
|
-
static async writeTemplate(file: string, msg:
|
|
76
|
+
static async writeTemplate(file: string, msg: EmailCompiled): Promise<void> {
|
|
71
77
|
const outs = this.getOutputFiles(file);
|
|
72
78
|
await Promise.all(TypedObject.keys(outs).map(async k => {
|
|
73
79
|
if (msg[k]) {
|
|
74
80
|
await fs.mkdir(path.dirname(outs[k]), { recursive: true });
|
|
75
|
-
await fs.writeFile(outs[k], msg[k], { encoding: 'utf8' });
|
|
81
|
+
await fs.writeFile(outs[k], MailUtil.buildBrand(file, msg[k], 'trv email:compile'), { encoding: 'utf8' });
|
|
76
82
|
} else {
|
|
77
83
|
await fs.unlink(outs[k]).catch(() => { }); // Remove file if data not provided
|
|
78
84
|
}
|
|
@@ -82,7 +88,7 @@ export class EmailCompiler {
|
|
|
82
88
|
/**
|
|
83
89
|
* Compile a file given a resource provider
|
|
84
90
|
*/
|
|
85
|
-
static async compile(file: string, persist: boolean = false): Promise<
|
|
91
|
+
static async compile(file: string, persist: boolean = false): Promise<EmailCompiled> {
|
|
86
92
|
const src = await this.loadTemplate(file);
|
|
87
93
|
const compiled = await EmailCompileUtil.compile(src);
|
|
88
94
|
if (persist) {
|
package/src/util.ts
CHANGED
|
@@ -2,11 +2,13 @@ import util from 'util';
|
|
|
2
2
|
import { Readable } from 'stream';
|
|
3
3
|
|
|
4
4
|
import { FileResourceProvider, StreamUtil } from '@travetto/base';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
EmailTemplateImageConfig, EmailTemplateStyleConfig,
|
|
7
|
+
EmailCompiled, EmailCompileContext
|
|
8
|
+
} from '@travetto/email';
|
|
6
9
|
import { ImageConverter } from '@travetto/image';
|
|
7
10
|
import { path } from '@travetto/manifest';
|
|
8
11
|
|
|
9
|
-
|
|
10
12
|
type Tokenized = {
|
|
11
13
|
text: string;
|
|
12
14
|
tokens: Map<string, string>;
|
|
@@ -42,7 +44,7 @@ export class EmailCompileUtil {
|
|
|
42
44
|
/**
|
|
43
45
|
* Get the different parts from the file name
|
|
44
46
|
*/
|
|
45
|
-
static getOutputs(file: string, prefix?: string):
|
|
47
|
+
static getOutputs(file: string, prefix?: string): EmailCompiled {
|
|
46
48
|
return {
|
|
47
49
|
html: this.buildOutputPath(file, '.compiled.html', prefix),
|
|
48
50
|
subject: this.buildOutputPath(file, '.compiled.subject', prefix),
|
|
@@ -132,7 +134,7 @@ export class EmailCompileUtil {
|
|
|
132
134
|
/**
|
|
133
135
|
* Inline image sources
|
|
134
136
|
*/
|
|
135
|
-
static async inlineImages(html: string, opts:
|
|
137
|
+
static async inlineImages(html: string, opts: EmailTemplateImageConfig): Promise<string> {
|
|
136
138
|
const { tokens, finalize } = await this.tokenizeResources(html, this.#HTML_CSS_IMAGE_URLS);
|
|
137
139
|
const pendingImages: [token: string, ext: string, stream: Readable | Promise<Readable>][] = [];
|
|
138
140
|
const resources = new FileResourceProvider(opts.search ?? []);
|
|
@@ -157,6 +159,7 @@ export class EmailCompileUtil {
|
|
|
157
159
|
*/
|
|
158
160
|
static handleHtmlEdgeCases(html: string): string {
|
|
159
161
|
return html
|
|
162
|
+
.replace(/\n{3,100}/msg, '\n\n')
|
|
160
163
|
.replace(/<(meta|img|link|hr|br)[^>]*>/g, a => a.replace('>', '/>')) // Fix self closing
|
|
161
164
|
.replace(/'/g, ''') // Fix apostrophes, as outlook hates them
|
|
162
165
|
.replace(/(background(?:-color)?:\s*)([#0-9a-f]{6,8})([^>.#,]+)>/ig,
|
|
@@ -164,14 +167,16 @@ export class EmailCompileUtil {
|
|
|
164
167
|
.replace(/<([^>]+vertical-align:\s*(top|bottom|middle)[^>]+)>/g,
|
|
165
168
|
(a, tag, valign) => tag.indexOf('valign') ? `<${tag}>` : `<${tag} valign="${valign}">`) // Vertically align if it has the style
|
|
166
169
|
.replace(/<(table[^>]+expand[^>]+width:\s*)(100%\s+!important)([^>]+)>/g,
|
|
167
|
-
(a, left, size, right) => `<${left}100%${right}>`)
|
|
170
|
+
(a, left, size, right) => `<${left}100%${right}>`) // Drop important as a fix for outlook
|
|
171
|
+
.trim()
|
|
172
|
+
.concat('\n');
|
|
168
173
|
}
|
|
169
174
|
|
|
170
175
|
|
|
171
176
|
/**
|
|
172
177
|
* Apply styles into a given html document
|
|
173
178
|
*/
|
|
174
|
-
static async applyStyles(html: string, opts:
|
|
179
|
+
static async applyStyles(html: string, opts: EmailTemplateStyleConfig): Promise<string> {
|
|
175
180
|
const styles: string[] = [];
|
|
176
181
|
|
|
177
182
|
if (opts.global) {
|
|
@@ -200,11 +205,11 @@ export class EmailCompileUtil {
|
|
|
200
205
|
return html;
|
|
201
206
|
}
|
|
202
207
|
|
|
203
|
-
static async compile(src:
|
|
204
|
-
const subject = await this.simplifiedText(await src.subject());
|
|
205
|
-
const text = await this.simplifiedText(await src.text());
|
|
208
|
+
static async compile(src: EmailCompileContext): Promise<EmailCompiled> {
|
|
209
|
+
const subject = await this.simplifiedText(await src.subject(src));
|
|
210
|
+
const text = await this.simplifiedText(await src.text(src));
|
|
206
211
|
|
|
207
|
-
let html = await src.html();
|
|
212
|
+
let html = await src.html(src);
|
|
208
213
|
|
|
209
214
|
if (src.styles?.inline !== false) {
|
|
210
215
|
html = await this.applyStyles(html, src.styles!);
|
|
@@ -216,6 +221,7 @@ export class EmailCompileUtil {
|
|
|
216
221
|
if (src.images?.inline !== false) {
|
|
217
222
|
html = await this.inlineImages(html, src.images!);
|
|
218
223
|
}
|
|
224
|
+
|
|
219
225
|
return { html, subject, text };
|
|
220
226
|
}
|
|
221
227
|
}
|
package/support/bin/manager.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
|
|
3
|
-
import type
|
|
3
|
+
import { type MailInterpolator, type EmailCompiled } from '@travetto/email';
|
|
4
4
|
import { DependencyRegistry } from '@travetto/di';
|
|
5
5
|
import { TypedObject } from '@travetto/base';
|
|
6
|
-
import {
|
|
6
|
+
import { MailInterpolatorTarget } from '@travetto/email/src/internal/types';
|
|
7
7
|
|
|
8
8
|
import { EmailCompiler } from '../../src/compiler';
|
|
9
9
|
|
|
@@ -14,20 +14,20 @@ export class EmailCompilationManager {
|
|
|
14
14
|
|
|
15
15
|
static async createInstance(): Promise<EmailCompilationManager> {
|
|
16
16
|
return new EmailCompilationManager(
|
|
17
|
-
await DependencyRegistry.getInstance<
|
|
17
|
+
await DependencyRegistry.getInstance<MailInterpolator>(MailInterpolatorTarget),
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
engine:
|
|
21
|
+
engine: MailInterpolator;
|
|
22
22
|
|
|
23
|
-
constructor(engine:
|
|
23
|
+
constructor(engine: MailInterpolator) {
|
|
24
24
|
this.engine = engine;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Resolve template
|
|
29
29
|
*/
|
|
30
|
-
async resolveTemplateParts(file: string): Promise<
|
|
30
|
+
async resolveTemplateParts(file: string): Promise<EmailCompiled> {
|
|
31
31
|
const files = EmailCompiler.getOutputFiles(file);
|
|
32
32
|
const missing = await Promise.all(Object.values(files).map(x => fs.stat(file).catch(() => { })));
|
|
33
33
|
|
|
@@ -41,21 +41,20 @@ export class EmailCompilationManager {
|
|
|
41
41
|
.then(content => [key, content] as const)
|
|
42
42
|
)
|
|
43
43
|
);
|
|
44
|
-
return TypedObject.fromEntries<keyof
|
|
44
|
+
return TypedObject.fromEntries<keyof EmailCompiled, string>(parts);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Render
|
|
49
49
|
* @param rel
|
|
50
50
|
*/
|
|
51
|
-
async resolveCompiledTemplate(rel: string, context: Record<string, unknown>): Promise<
|
|
51
|
+
async resolveCompiledTemplate(rel: string, context: Record<string, unknown>): Promise<EmailCompiled> {
|
|
52
|
+
const { MailUtil } = await import('@travetto/email');
|
|
52
53
|
const { html, text, subject } = await this.resolveTemplateParts(rel);
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
text: await this.engine.template(text, context),
|
|
57
|
-
subject: await this.engine.template(subject, context),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
55
|
+
const get = (input: string): Promise<string> =>
|
|
56
|
+
Promise.resolve(this.engine.render(input, context)).then(MailUtil.purgeBrand);
|
|
60
57
|
|
|
58
|
+
return { html: await get(html), text: await get(text), subject: await get(subject) };
|
|
59
|
+
}
|
|
61
60
|
}
|
package/support/bin/send.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MailService,
|
|
1
|
+
import { MailService, EmailOptions, SentEmail } from '@travetto/email';
|
|
2
2
|
import { MailTransportTarget } from '@travetto/email/src/internal/types';
|
|
3
3
|
import { DependencyRegistry } from '@travetto/di';
|
|
4
4
|
|
|
@@ -50,7 +50,7 @@ ${EditorConfig.getDefaultConfig()}`.trim();
|
|
|
50
50
|
/**
|
|
51
51
|
* Resolve template
|
|
52
52
|
*/
|
|
53
|
-
static async sendEmail(file: string, message:
|
|
53
|
+
static async sendEmail(file: string, message: EmailOptions): Promise<{
|
|
54
54
|
url?: string | false;
|
|
55
55
|
}> {
|
|
56
56
|
const to = message.to!;
|
|
@@ -62,7 +62,7 @@ ${EditorConfig.getDefaultConfig()}`.trim();
|
|
|
62
62
|
throw new Error('Node mailer support is missing');
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
const info = await svc.send<{ host?: string } &
|
|
65
|
+
const info = await svc.send<{ host?: string } & SentEmail>(message);
|
|
66
66
|
console.log('Sent email', { to });
|
|
67
67
|
|
|
68
68
|
const senderConfig = await EditorConfig.getSenderConfig(file);
|
|
@@ -10,7 +10,11 @@ import { EmailCompilationManager } from './bin/manager';
|
|
|
10
10
|
export class EmailEditorCommand {
|
|
11
11
|
|
|
12
12
|
envInit(): GlobalEnvConfig {
|
|
13
|
-
return {
|
|
13
|
+
return {
|
|
14
|
+
envName: 'dev',
|
|
15
|
+
dynamic: true,
|
|
16
|
+
profiles: ['email-dev']
|
|
17
|
+
};
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
async main(): Promise<void> {
|
|
@@ -16,7 +16,10 @@ import { EmailCompiler } from '../src/compiler';
|
|
|
16
16
|
export class EmailTestCommand implements CliCommandShape {
|
|
17
17
|
|
|
18
18
|
envInit(): GlobalEnvConfig {
|
|
19
|
-
return {
|
|
19
|
+
return {
|
|
20
|
+
envName: 'dev',
|
|
21
|
+
profiles: ['email-dev']
|
|
22
|
+
};
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
async main(file: string, to: string): Promise<void> {
|