@travetto/email-compiler 7.0.0-rc.1 → 7.0.0-rc.3
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 +8 -8
- package/src/compiler.ts +34 -30
- package/src/util.ts +48 -37
- package/support/bin/editor.ts +32 -28
- package/support/bin/send.ts +10 -8
- package/support/cli.email_compile.ts +7 -8
- package/support/cli.email_editor.ts +1 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/email-compiler",
|
|
3
|
-
"version": "7.0.0-rc.
|
|
3
|
+
"version": "7.0.0-rc.3",
|
|
4
4
|
"description": "Email compiling module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"directory": "module/email-compiler"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@travetto/config": "^7.0.0-rc.
|
|
30
|
-
"@travetto/di": "^7.0.0-rc.
|
|
31
|
-
"@travetto/email": "^7.0.0-rc.
|
|
32
|
-
"@travetto/image": "^7.0.0-rc.
|
|
33
|
-
"@travetto/runtime": "^7.0.0-rc.
|
|
34
|
-
"@travetto/worker": "^7.0.0-rc.
|
|
29
|
+
"@travetto/config": "^7.0.0-rc.3",
|
|
30
|
+
"@travetto/di": "^7.0.0-rc.3",
|
|
31
|
+
"@travetto/email": "^7.0.0-rc.3",
|
|
32
|
+
"@travetto/image": "^7.0.0-rc.3",
|
|
33
|
+
"@travetto/runtime": "^7.0.0-rc.3",
|
|
34
|
+
"@travetto/worker": "^7.0.0-rc.3",
|
|
35
35
|
"@types/inline-css": "^3.0.4",
|
|
36
36
|
"html-entities": "^2.6.0",
|
|
37
37
|
"inline-css": "^4.0.3",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"sass": "^1.94.2"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@travetto/cli": "^7.0.0-rc.
|
|
42
|
+
"@travetto/cli": "^7.0.0-rc.3"
|
|
43
43
|
},
|
|
44
44
|
"peerDependenciesMeta": {
|
|
45
45
|
"@travetto/cli": {
|
package/src/compiler.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import { TypedObject, RuntimeIndex,
|
|
4
|
+
import { TypedObject, RuntimeIndex, Runtime, BinaryUtil, ExecUtil } from '@travetto/runtime';
|
|
5
5
|
import { EmailCompiled, MailUtil, EmailTemplateImport, EmailTemplateModule } from '@travetto/email';
|
|
6
6
|
|
|
7
7
|
import { EmailCompileUtil } from './util.ts';
|
|
@@ -23,14 +23,14 @@ export class EmailCompiler {
|
|
|
23
23
|
/**
|
|
24
24
|
* Grab list of all available templates
|
|
25
25
|
*/
|
|
26
|
-
static findAllTemplates(
|
|
26
|
+
static findAllTemplates(moduleName?: string): string[] {
|
|
27
27
|
return RuntimeIndex
|
|
28
28
|
.find({
|
|
29
|
-
module:
|
|
30
|
-
folder:
|
|
31
|
-
file:
|
|
29
|
+
module: mod => !moduleName ? mod.roles.includes('std') : moduleName === mod.name,
|
|
30
|
+
folder: folder => folder === 'support',
|
|
31
|
+
file: file => EmailCompileUtil.isTemplateFile(file.sourceFile)
|
|
32
32
|
})
|
|
33
|
-
.map(
|
|
33
|
+
.map(file => file.sourceFile);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -52,14 +52,14 @@ export class EmailCompiler {
|
|
|
52
52
|
/**
|
|
53
53
|
* Write template to file
|
|
54
54
|
*/
|
|
55
|
-
static async writeTemplate(file: string,
|
|
55
|
+
static async writeTemplate(file: string, message: EmailCompiled): Promise<void> {
|
|
56
56
|
const outs = this.getOutputFiles(file);
|
|
57
|
-
await Promise.all(TypedObject.keys(outs).map(async
|
|
58
|
-
if (
|
|
59
|
-
const content = MailUtil.buildBrand(file,
|
|
60
|
-
await BinaryUtil.bufferedFileWrite(outs[
|
|
57
|
+
await Promise.all(TypedObject.keys(outs).map(async key => {
|
|
58
|
+
if (message[key]) {
|
|
59
|
+
const content = MailUtil.buildBrand(file, message[key], 'trv email:compile');
|
|
60
|
+
await BinaryUtil.bufferedFileWrite(outs[key], content);
|
|
61
61
|
} else {
|
|
62
|
-
await fs.rm(outs[
|
|
62
|
+
await fs.rm(outs[key], { force: true }); // Remove file if data not provided
|
|
63
63
|
}
|
|
64
64
|
}));
|
|
65
65
|
}
|
|
@@ -68,8 +68,8 @@ export class EmailCompiler {
|
|
|
68
68
|
* Compile a file given a resource provider
|
|
69
69
|
*/
|
|
70
70
|
static async compile(file: string): Promise<EmailCompiled> {
|
|
71
|
-
const
|
|
72
|
-
const compiled = await EmailCompileUtil.compile(
|
|
71
|
+
const template = await this.loadTemplate(file);
|
|
72
|
+
const compiled = await EmailCompileUtil.compile(template);
|
|
73
73
|
await this.writeTemplate(file, compiled);
|
|
74
74
|
return compiled;
|
|
75
75
|
}
|
|
@@ -79,27 +79,31 @@ export class EmailCompiler {
|
|
|
79
79
|
*/
|
|
80
80
|
static async compileAll(mod?: string): Promise<string[]> {
|
|
81
81
|
const keys = this.findAllTemplates(mod);
|
|
82
|
-
await Promise.all(keys.map(
|
|
82
|
+
await Promise.all(keys.map(key => this.compile(key)));
|
|
83
83
|
return keys;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
87
|
+
* Spawn the compiler for a given file
|
|
88
88
|
*/
|
|
89
|
-
static async
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const src = RuntimeIndex.getEntry(file);
|
|
93
|
-
if (!src || !EmailCompileUtil.isTemplateFile(src.sourceFile) || action === 'delete') {
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
await this.compile(file);
|
|
98
|
-
console.log('Successfully compiled template', { changed: [file] });
|
|
99
|
-
yield file;
|
|
100
|
-
} catch (err) {
|
|
101
|
-
console.error(`Error in compiling ${file}`, err && err instanceof Error ? err.message : `${err}`);
|
|
102
|
-
}
|
|
89
|
+
static async spawnCompile(file: string): Promise<boolean> {
|
|
90
|
+
if (!EmailCompileUtil.isTemplateFile(file)) {
|
|
91
|
+
return false;
|
|
103
92
|
}
|
|
93
|
+
|
|
94
|
+
const child = ExecUtil.spawnTrv('email:compile', [file], {
|
|
95
|
+
cwd: Runtime.mainSourcePath,
|
|
96
|
+
env: { ...process.env },
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const result = await ExecUtil.getResult(child, { catch: true });
|
|
100
|
+
|
|
101
|
+
if (!result.valid) {
|
|
102
|
+
console.error('Error compiling template', { file, stderr: result.stderr });
|
|
103
|
+
} else {
|
|
104
|
+
console.log('Successfully compiled template', { changed: [file] });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return result.valid;
|
|
104
108
|
}
|
|
105
109
|
}
|
package/src/util.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import util from 'node:util';
|
|
2
1
|
import { buffer as toBuffer } from 'node:stream/consumers';
|
|
3
2
|
import path from 'node:path';
|
|
3
|
+
import type { CompileResult, Options } from 'sass';
|
|
4
4
|
|
|
5
5
|
import { EmailCompiled, EmailTemplateModule, EmailTemplateResource } from '@travetto/email';
|
|
6
6
|
import { ImageUtil } from '@travetto/image';
|
|
7
|
+
import { RuntimeIndex } from '@travetto/runtime';
|
|
7
8
|
|
|
8
9
|
type Tokenized = {
|
|
9
10
|
text: string;
|
|
@@ -11,11 +12,11 @@ type Tokenized = {
|
|
|
11
12
|
finalize: (onToken: (token: string) => string) => string;
|
|
12
13
|
};
|
|
13
14
|
|
|
14
|
-
const
|
|
15
|
+
const SUPPORT_SOURCE = /(?:support|src)\//;
|
|
15
16
|
|
|
16
17
|
const HTML_CSS_IMAGE_URLS = [
|
|
17
|
-
/(?<
|
|
18
|
-
/(?<
|
|
18
|
+
/(?<prefix><img[^>]src=\s{0,10}["'])(?<source>[^"{}]{1,1000})/g,
|
|
19
|
+
/(?<prefix>background(?:-image)?:\s{0,10}url[(]['"]?)(?<source>[^"'){}]{1,1000})/g
|
|
19
20
|
];
|
|
20
21
|
|
|
21
22
|
const EXT = /[.]email[.]tsx$/;
|
|
@@ -29,15 +30,15 @@ export class EmailCompileUtil {
|
|
|
29
30
|
* Is file a template?
|
|
30
31
|
*/
|
|
31
32
|
static isTemplateFile(file: string): boolean {
|
|
32
|
-
return EXT.test(file);
|
|
33
|
+
return EXT.test(file) && RuntimeIndex.findModuleForArbitraryFile(file) !== undefined;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* Generate singular output path given a file
|
|
37
38
|
*/
|
|
38
39
|
static buildOutputPath(file: string, suffix: string, prefix?: string): string {
|
|
39
|
-
const
|
|
40
|
-
return prefix ? path.join(prefix,
|
|
40
|
+
const location = (SUPPORT_SOURCE.test(file) ? file.split(SUPPORT_SOURCE)[1] : file).replace(EXT, suffix);
|
|
41
|
+
return prefix ? path.join(prefix, location) : location;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/**
|
|
@@ -62,16 +63,16 @@ export class EmailCompileUtil {
|
|
|
62
63
|
let id = 0;
|
|
63
64
|
const tokens = new Map();
|
|
64
65
|
for (const pattern of patterns) {
|
|
65
|
-
for (const { [0]: all, groups: {
|
|
66
|
-
if (
|
|
66
|
+
for (const { [0]: all, groups: { prefix, source } = { prefix: '', source: '' } } of text.matchAll(pattern)) {
|
|
67
|
+
if (source.includes('://')) { // No urls
|
|
67
68
|
continue;
|
|
68
69
|
}
|
|
69
70
|
const token = `@@${id += 1}@@`;
|
|
70
|
-
tokens.set(token,
|
|
71
|
-
text = text.replace(all, `${
|
|
71
|
+
tokens.set(token, source);
|
|
72
|
+
text = text.replace(all, `${prefix}${token}`);
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
|
-
const finalize = (onToken: (token: string) => string): string => text.replace(/@@[^@]{1,100}@@/g,
|
|
75
|
+
const finalize = (onToken: (token: string) => string): string => text.replace(/@@[^@]{1,100}@@/g, token => onToken(token));
|
|
75
76
|
|
|
76
77
|
return { text, tokens, finalize };
|
|
77
78
|
}
|
|
@@ -79,13 +80,21 @@ export class EmailCompileUtil {
|
|
|
79
80
|
/**
|
|
80
81
|
* Compile SCSS content with roots as search paths for additional assets
|
|
81
82
|
*/
|
|
82
|
-
static async compileSass(
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
83
|
+
static async compileSass(input: { data: string } | { file: string }, options: EmailTemplateResource): Promise<string> {
|
|
84
|
+
const { initAsyncCompiler } = await import('sass');
|
|
85
|
+
const compiler = await initAsyncCompiler();
|
|
86
|
+
const compilerOptions: Options<'async'> = {
|
|
86
87
|
sourceMap: false,
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
quietDeps: true,
|
|
89
|
+
loadPaths: options.loader.searchPaths.slice(0),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
let result: CompileResult;
|
|
93
|
+
if ('data' in input) {
|
|
94
|
+
result = await compiler.compileStringAsync(input.data, compilerOptions);
|
|
95
|
+
} else {
|
|
96
|
+
result = await compiler.compileAsync(input.file, compilerOptions);
|
|
97
|
+
}
|
|
89
98
|
return result!.css.toString();
|
|
90
99
|
}
|
|
91
100
|
|
|
@@ -133,21 +142,21 @@ export class EmailCompileUtil {
|
|
|
133
142
|
/**
|
|
134
143
|
* Inline image sources
|
|
135
144
|
*/
|
|
136
|
-
static async inlineImages(html: string,
|
|
145
|
+
static async inlineImages(html: string, options: EmailTemplateResource): Promise<string> {
|
|
137
146
|
const { tokens, finalize } = await this.tokenizeResources(html, HTML_CSS_IMAGE_URLS);
|
|
138
147
|
const pendingImages: [token: string, ext: string, stream: Buffer | Promise<Buffer>][] = [];
|
|
139
148
|
|
|
140
|
-
for (const [token,
|
|
141
|
-
const ext = path.extname(
|
|
149
|
+
for (const [token, source] of tokens) {
|
|
150
|
+
const ext = path.extname(source);
|
|
142
151
|
if (/^[.](jpe?g|png)$/.test(ext)) {
|
|
143
152
|
const output = await ImageUtil.convert(
|
|
144
|
-
await
|
|
153
|
+
await options.loader.readStream(source),
|
|
145
154
|
{ format: ext === '.png' ? 'png' : 'jpeg' }
|
|
146
155
|
);
|
|
147
156
|
const buffer = await toBuffer(output);
|
|
148
157
|
pendingImages.push([token, ext, buffer]);
|
|
149
158
|
} else {
|
|
150
|
-
pendingImages.push([token, ext,
|
|
159
|
+
pendingImages.push([token, ext, options.loader.read(source, true)]);
|
|
151
160
|
}
|
|
152
161
|
}
|
|
153
162
|
|
|
@@ -167,7 +176,7 @@ export class EmailCompileUtil {
|
|
|
167
176
|
.replace(/<(meta|img|link|hr|br)[^>]{0,200}>/g, a => a.replace(/>/g, '/>')) // Fix self closing
|
|
168
177
|
.replace(/'/g, ''') // Fix apostrophes, as outlook hates them
|
|
169
178
|
.replace(/(background(?:-color)?:\s*)([#0-9a-f]{6,8})([^>.#,]+)>/ig,
|
|
170
|
-
(all,
|
|
179
|
+
(all, property, color, rest) => `${property}${color}${rest} bgcolor="${color}">`) // Inline bg-color
|
|
171
180
|
.replace(/<([^>]+vertical-align:\s*(top|bottom|middle)[^>]+)>/g,
|
|
172
181
|
(a, tag, valign) => tag.indexOf('valign') ? `<${tag}>` : `<${tag} valign="${valign}">`) // Vertically align if it has the style
|
|
173
182
|
.replace(/<(table[^>]+expand[^>]+width:\s*)(100%\s+!important)([^>]+)>/g,
|
|
@@ -179,14 +188,16 @@ export class EmailCompileUtil {
|
|
|
179
188
|
/**
|
|
180
189
|
* Apply styles into a given html document
|
|
181
190
|
*/
|
|
182
|
-
static async applyStyles(html: string,
|
|
191
|
+
static async applyStyles(html: string, options: EmailTemplateResource): Promise<string> {
|
|
183
192
|
const styles = [
|
|
184
|
-
|
|
185
|
-
await
|
|
186
|
-
]
|
|
193
|
+
options.globalStyles ?? '',
|
|
194
|
+
await options.loader.read('/email/main.scss').catch(() => '')
|
|
195
|
+
]
|
|
196
|
+
.filter(line => !!line)
|
|
197
|
+
.join('\n');
|
|
187
198
|
|
|
188
199
|
if (styles.length) {
|
|
189
|
-
const compiled = await this.compileSass({ data: styles },
|
|
200
|
+
const compiled = await this.compileSass({ data: styles }, options);
|
|
190
201
|
|
|
191
202
|
// Remove all unused styles
|
|
192
203
|
const finalStyles = await this.pruneCss(html, compiled);
|
|
@@ -198,21 +209,21 @@ export class EmailCompileUtil {
|
|
|
198
209
|
return html;
|
|
199
210
|
}
|
|
200
211
|
|
|
201
|
-
static async compile(
|
|
202
|
-
const subject = await this.simplifiedText(await
|
|
203
|
-
const text = await this.simplifiedText(await
|
|
212
|
+
static async compile(input: EmailTemplateModule): Promise<EmailCompiled> {
|
|
213
|
+
const subject = await this.simplifiedText(await input.subject());
|
|
214
|
+
const text = await this.simplifiedText(await input.text());
|
|
204
215
|
|
|
205
|
-
let html = await
|
|
216
|
+
let html = await input.html();
|
|
206
217
|
|
|
207
|
-
if (
|
|
208
|
-
html = await this.applyStyles(html,
|
|
218
|
+
if (input.inlineStyle !== false) {
|
|
219
|
+
html = await this.applyStyles(html, input);
|
|
209
220
|
}
|
|
210
221
|
|
|
211
222
|
// Fix up html edge cases
|
|
212
223
|
html = this.handleHtmlEdgeCases(html);
|
|
213
224
|
|
|
214
|
-
if (
|
|
215
|
-
html = await this.inlineImages(html,
|
|
225
|
+
if (input.inlineImages !== false) {
|
|
226
|
+
html = await this.inlineImages(html, input);
|
|
216
227
|
}
|
|
217
228
|
|
|
218
229
|
return { html, subject, text };
|
package/support/bin/editor.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Inject, Injectable } from '@travetto/di';
|
|
2
2
|
import { MailUtil, EmailCompiled, MailInterpolator } from '@travetto/email';
|
|
3
|
-
import { AppError, TypedObject } from '@travetto/runtime';
|
|
3
|
+
import { AppError, TypedObject, watchCompiler } from '@travetto/runtime';
|
|
4
4
|
|
|
5
5
|
import { EditorSendService } from './send.ts';
|
|
6
6
|
import { EditorConfig } from './config.ts';
|
|
@@ -24,10 +24,11 @@ export class EditorService {
|
|
|
24
24
|
return Promise.resolve(this.engine.render(text, context)).then(MailUtil.purgeBrand);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
async #renderTemplate(
|
|
28
|
-
const
|
|
27
|
+
async #renderTemplate(templateFile: string, context: Record<string, unknown>): Promise<EmailCompiled> {
|
|
28
|
+
const email = await EmailCompiler.compile(templateFile);
|
|
29
29
|
return TypedObject.fromEntries(
|
|
30
|
-
await Promise.all(TypedObject.entries(
|
|
30
|
+
await Promise.all(TypedObject.entries(email).map(([key, value]) =>
|
|
31
|
+
this.#interpolate(value, context).then((result) => [key, result])))
|
|
31
32
|
);
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -36,24 +37,24 @@ export class EditorService {
|
|
|
36
37
|
return { content, file };
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
async #response<T>(
|
|
40
|
+
async #response<T>(operation: Promise<T>, success: (value: T) => EditorResponse, fail?: (error: Error) => EditorResponse): Promise<void> {
|
|
40
41
|
try {
|
|
41
|
-
const
|
|
42
|
-
if (process.connected) { process.send?.(success(
|
|
43
|
-
} catch (
|
|
44
|
-
if (fail && process.connected &&
|
|
45
|
-
process.send?.(fail(
|
|
42
|
+
const response = await operation;
|
|
43
|
+
if (process.connected) { process.send?.(success(response)); }
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (fail && process.connected && error && error instanceof Error) {
|
|
46
|
+
process.send?.(fail(error));
|
|
46
47
|
} else {
|
|
47
|
-
console.error(
|
|
48
|
+
console.error(error);
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
async sendFile(file: string, to?: string): Promise<{ to: string, file: string, url?: string | false | undefined }> {
|
|
53
|
-
const
|
|
54
|
-
to ||=
|
|
55
|
-
const content = await this.#renderTemplate(file,
|
|
56
|
-
return { to, file, ...await this.sender.send({ from:
|
|
54
|
+
const config = await EditorConfig.get();
|
|
55
|
+
to ||= config.to;
|
|
56
|
+
const content = await this.#renderTemplate(file, config.context ?? {});
|
|
57
|
+
return { to, file, ...await this.sender.send({ from: config.from, to, ...content, }) };
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
/**
|
|
@@ -63,22 +64,22 @@ export class EditorService {
|
|
|
63
64
|
if (!process.connected || !process.send) {
|
|
64
65
|
throw new AppError('Unable to run email editor, missing ipc channel');
|
|
65
66
|
}
|
|
66
|
-
process.on('message', async (
|
|
67
|
-
switch (
|
|
67
|
+
process.on('message', async (request: EditorRequest) => {
|
|
68
|
+
switch (request.type) {
|
|
68
69
|
case 'configure': {
|
|
69
70
|
return await this.#response(EditorConfig.ensureConfig(), file => ({ type: 'configured', file }));
|
|
70
71
|
}
|
|
71
72
|
case 'compile': {
|
|
72
|
-
return await this.#response(this.#renderFile(
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
return await this.#response(this.#renderFile(request.file),
|
|
74
|
+
result => ({ type: 'compiled', ...result }),
|
|
75
|
+
error => ({ type: 'compiled-failed', message: error.message, stack: error.stack, file: request.file })
|
|
75
76
|
);
|
|
76
77
|
}
|
|
77
78
|
case 'send': {
|
|
78
79
|
return await this.#response(
|
|
79
|
-
this.sendFile(
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
this.sendFile(request.file, request.to),
|
|
81
|
+
result => ({ type: 'sent', ...result }),
|
|
82
|
+
error => ({ type: 'sent-failed', message: error.message, stack: error.stack, to: request.to!, file: request.file })
|
|
82
83
|
);
|
|
83
84
|
}
|
|
84
85
|
}
|
|
@@ -86,11 +87,14 @@ export class EditorService {
|
|
|
86
87
|
|
|
87
88
|
process.send({ type: 'init' });
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
// Watch template files
|
|
91
|
+
for await (const { file } of watchCompiler({ restartOnCompilerExit: true })) {
|
|
92
|
+
if (await EmailCompiler.spawnCompile(file)) {
|
|
93
|
+
await this.#response(this.#renderFile(file),
|
|
94
|
+
result => ({ type: 'compiled', ...result }),
|
|
95
|
+
error => ({ type: 'compiled-failed', message: error.message, stack: error.stack, file })
|
|
96
|
+
);
|
|
97
|
+
}
|
|
94
98
|
}
|
|
95
99
|
}
|
|
96
100
|
}
|
package/support/bin/send.ts
CHANGED
|
@@ -33,7 +33,7 @@ export class EditorSendService {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
|
-
Registry.process([
|
|
36
|
+
Registry.process([cls]);
|
|
37
37
|
|
|
38
38
|
this.ethereal = !!senderConfig.host?.includes('ethereal.email');
|
|
39
39
|
} catch {
|
|
@@ -41,7 +41,9 @@ export class EditorSendService {
|
|
|
41
41
|
throw new Error('A mail transport is currently needed to support sending emails. Please install @travetto/email-nodemailer or any other compatible transport');
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
const service = await DependencyRegistryIndex.getInstance(MailService);
|
|
45
|
+
service.setCacheState(false); // Ensure we don't cache locally
|
|
46
|
+
return service;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
/**
|
|
@@ -51,23 +53,23 @@ export class EditorSendService {
|
|
|
51
53
|
const to = message.to!;
|
|
52
54
|
try {
|
|
53
55
|
console.log('Sending email', { to });
|
|
54
|
-
const
|
|
56
|
+
const service = await this.service();
|
|
55
57
|
if (this.ethereal) {
|
|
56
58
|
const { getTestMessageUrl } = await import('nodemailer');
|
|
57
59
|
const { default: _smtp } = await import('nodemailer/lib/smtp-transport/index');
|
|
58
60
|
type SendMessage = Parameters<Parameters<(typeof _smtp)['prototype']['send']>[1]>[1];
|
|
59
|
-
const info = await
|
|
61
|
+
const info = await service.send<SendMessage>(message);
|
|
60
62
|
const url = getTestMessageUrl(info);
|
|
61
63
|
console.log('Sent email', { to, url });
|
|
62
64
|
return { url };
|
|
63
65
|
} else {
|
|
64
|
-
await
|
|
66
|
+
await service.send(message);
|
|
65
67
|
console.log('Sent email', { to });
|
|
66
68
|
return {};
|
|
67
69
|
}
|
|
68
|
-
} catch (
|
|
69
|
-
console.warn('Failed to send email', { to, error
|
|
70
|
-
throw
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.warn('Failed to send email', { to, error });
|
|
72
|
+
throw error;
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Registry } from '@travetto/registry';
|
|
2
2
|
import { CliCommandShape, CliCommand, cliTpl } from '@travetto/cli';
|
|
3
|
-
import { Env, Runtime } from '@travetto/runtime';
|
|
3
|
+
import { Env, Runtime, watchCompiler } from '@travetto/runtime';
|
|
4
4
|
|
|
5
5
|
import { EmailCompiler } from '../src/compiler.ts';
|
|
6
6
|
|
|
@@ -16,22 +16,21 @@ export class EmailCompileCommand implements CliCommandShape {
|
|
|
16
16
|
preMain(): void {
|
|
17
17
|
Env.DEBUG.set(false);
|
|
18
18
|
Env.TRV_ROLE.set('build');
|
|
19
|
-
Env.TRV_DYNAMIC.set(this.watch);
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
async main(): Promise<void> {
|
|
23
22
|
await Registry.init();
|
|
24
23
|
|
|
25
24
|
// Let the engine template
|
|
26
|
-
const
|
|
27
|
-
console!.log(cliTpl`Successfully compiled ${{ param: `${
|
|
28
|
-
for (const
|
|
29
|
-
console!.log(cliTpl` * ${{ param: Runtime.stripWorkspacePath(
|
|
25
|
+
const locations = await EmailCompiler.compileAll();
|
|
26
|
+
console!.log(cliTpl`Successfully compiled ${{ param: `${locations.length}` }} templates`);
|
|
27
|
+
for (const location of locations) {
|
|
28
|
+
console!.log(cliTpl` * ${{ param: Runtime.stripWorkspacePath(location) }}`);
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
if (this.watch) {
|
|
33
|
-
for await (const
|
|
34
|
-
|
|
32
|
+
for await (const { file } of watchCompiler({ restartOnCompilerExit: true })) {
|
|
33
|
+
await EmailCompiler.spawnCompile(file);
|
|
35
34
|
}
|
|
36
35
|
}
|
|
37
36
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Env } from '@travetto/runtime';
|
|
2
|
-
import { CliCommand
|
|
2
|
+
import { CliCommand } from '@travetto/cli';
|
|
3
3
|
import { Registry } from '@travetto/registry';
|
|
4
4
|
import { DependencyRegistryIndex } from '@travetto/di';
|
|
5
5
|
|
|
@@ -10,15 +10,10 @@ import { EditorService } from './bin/editor.ts';
|
|
|
10
10
|
export class EmailEditorCommand {
|
|
11
11
|
|
|
12
12
|
preMain(): void {
|
|
13
|
-
Env.TRV_DYNAMIC.set(true);
|
|
14
13
|
Env.TRV_ROLE.set('build');
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
async main(): Promise<void> {
|
|
18
|
-
if (await CliUtil.runWithRestart(this, true)) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
await Registry.init();
|
|
23
18
|
const service = await DependencyRegistryIndex.getInstance(EditorService);
|
|
24
19
|
await service.listen();
|