@travetto/email-compiler 8.0.0-alpha.9 → 8.0.1
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/README.md +17 -8
- package/__index__.ts +1 -1
- package/package.json +19 -20
- package/src/compiler.ts +19 -20
- package/src/util.ts +28 -31
- package/support/bin/config.ts +5 -5
- package/support/bin/editor.ts +28 -22
- package/support/bin/send.ts +11 -8
- package/support/bin/types.ts +9 -9
- package/support/cli.email_compile.ts +12 -7
- package/support/cli.email_editor.ts +9 -5
- package/support/cli.email_test.ts +7 -5
package/README.md
CHANGED
|
@@ -13,14 +13,14 @@ npm install @travetto/email-compiler
|
|
|
13
13
|
yarn add @travetto/email-compiler
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
This is primarily a set of command line tools for compiling and developing templates.
|
|
16
|
+
This is primarily a set of command line tools for compiling and developing templates. The inputs are compiled files, generally under the `support/` folder, that represents the necessary input for the email compilation. [Email Inky Templates](https://github.com/travetto/travetto/tree/main/module/email-inky#readme "Email Inky templating module") shows this pattern by leveraging [JSX](https://en.wikipedia.org/wiki/JSX_(JavaScript)) bindings for the [inky](https://github.com/zurb/inky) framework, allowing for compile-time checked templates.
|
|
17
17
|
|
|
18
18
|
## Asset Management
|
|
19
|
-
The templating process involves loading various assets (html, css, images), and so there is provision for asset management and loading.
|
|
19
|
+
The templating process involves loading various assets (html, css, images), and so there is provision for asset management and loading. The templating config allows for specifying asset paths, with the following paths (in order of precedence):
|
|
20
20
|
1. `%ROOT%/resources/email`
|
|
21
21
|
1. `@travetto/email-{engine}/resources/email`
|
|
22
22
|
|
|
23
|
-
When looking up a resources, every asset folder is consulted, in order, and the first to resolve an asset wins.
|
|
23
|
+
When looking up a resources, every asset folder is consulted, in order, and the first to resolve an asset wins. This allows for overriding of default templating resources, as needed. The compilation process will convert `.email.html` files into `.compiled.html`, `.compiled.text` and `.compiled.subject` suffixes to generate the outputs respectively.
|
|
24
24
|
|
|
25
25
|
## Template Extension
|
|
26
26
|
The template extension points are defined at:
|
|
@@ -40,10 +40,10 @@ When referencing an image from the `resources` folder in a template, e.g.
|
|
|
40
40
|
|
|
41
41
|
**Code: Sample Image Reference**
|
|
42
42
|
```html
|
|
43
|
-
<img src="/email/logo.png"
|
|
43
|
+
<img src="/email/logo.png" alt="logo" />
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
The image will be extracted out and embedded in the email as a multi part message.
|
|
46
|
+
The image will be extracted out and embedded in the email as a multi part message. This allows for compression and optimization of images as well as externalizing resources that may not be immediately public. The currently supported set of image types are:
|
|
47
47
|
* jpeg
|
|
48
48
|
* png
|
|
49
49
|
|
|
@@ -53,18 +53,27 @@ The module provides [Command Line Interface](https://github.com/travetto/travett
|
|
|
53
53
|
## CLI Compilation
|
|
54
54
|
The module provides [Command Line Interface](https://github.com/travetto/travetto/tree/main/module/cli#readme "CLI infrastructure for Travetto framework") support for email template compilation also. Running
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
## CLI - email:compile
|
|
57
|
+
|
|
58
|
+
**Terminal: Help for email:compile**
|
|
57
59
|
```bash
|
|
58
60
|
$ trv email:compile --help
|
|
59
61
|
|
|
60
62
|
Usage: email:compile [options]
|
|
61
63
|
|
|
64
|
+
Description:
|
|
65
|
+
Compile all email templates into generated runtime artifacts.
|
|
66
|
+
|
|
67
|
+
The command discovers templated inputs (for example, `.email.html`) and emits
|
|
68
|
+
compiled outputs used at runtime (html/text/subject variants). With watch
|
|
69
|
+
enabled, recompilation runs automatically on matching template changes.
|
|
70
|
+
|
|
62
71
|
Options:
|
|
63
|
-
-w, --watch
|
|
72
|
+
-w, --watch Recompile templates whenever source templates change.
|
|
64
73
|
--help display help for command
|
|
65
74
|
```
|
|
66
75
|
|
|
67
|
-
Will convert all `.email.html` files into the appropriate `.compiled.html`, `.compiled.text` and `.compiled.subject` files.
|
|
76
|
+
Will convert all `.email.html` files into the appropriate `.compiled.html`, `.compiled.text` and `.compiled.subject` files. These will be used during the running of the application. By default these files are added to the `.gitignore` as they are generally not intended to be saved but to be generated during the build process.
|
|
68
77
|
|
|
69
78
|
## VSCode Plugin
|
|
70
79
|
In addition to command line tools, the [VSCode plugin](https://marketplace.visualstudio.com/items?itemName=arcsine.travetto-plugin) also supports:
|
package/__index__.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from './src/compiler.ts';
|
|
2
|
-
export * from './src/util.ts';
|
|
2
|
+
export * from './src/util.ts';
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/email-compiler",
|
|
3
|
-
"version": "8.0.
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "8.0.1",
|
|
5
4
|
"description": "Email compiling module",
|
|
6
5
|
"keywords": [
|
|
7
6
|
"email",
|
|
@@ -12,8 +11,12 @@
|
|
|
12
11
|
"homepage": "https://travetto.io",
|
|
13
12
|
"license": "MIT",
|
|
14
13
|
"author": {
|
|
15
|
-
"
|
|
16
|
-
"
|
|
14
|
+
"name": "Travetto Framework",
|
|
15
|
+
"email": "travetto.framework@gmail.com"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"url": "git+https://github.com/travetto/travetto.git",
|
|
19
|
+
"directory": "module/email-compiler"
|
|
17
20
|
},
|
|
18
21
|
"files": [
|
|
19
22
|
"__index__.ts",
|
|
@@ -21,26 +24,25 @@
|
|
|
21
24
|
"!resources",
|
|
22
25
|
"support"
|
|
23
26
|
],
|
|
27
|
+
"type": "module",
|
|
24
28
|
"main": "__index__.ts",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"directory": "module/email-compiler"
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
28
31
|
},
|
|
29
32
|
"dependencies": {
|
|
30
|
-
"@
|
|
31
|
-
"@travetto/
|
|
32
|
-
"@travetto/
|
|
33
|
-
"@travetto/
|
|
34
|
-
"@travetto/
|
|
35
|
-
"@travetto/
|
|
36
|
-
"@
|
|
33
|
+
"@css-inline/css-inline": "^0.21.2",
|
|
34
|
+
"@travetto/config": "^8.0.1",
|
|
35
|
+
"@travetto/di": "^8.0.1",
|
|
36
|
+
"@travetto/email": "^8.0.1",
|
|
37
|
+
"@travetto/image": "^8.0.1",
|
|
38
|
+
"@travetto/runtime": "^8.0.1",
|
|
39
|
+
"@travetto/worker": "^8.0.1",
|
|
37
40
|
"html-entities": "^2.6.0",
|
|
38
|
-
"inline-css": "^4.0.3",
|
|
39
41
|
"purgecss": "^8.0.0",
|
|
40
|
-
"sass": "^1.
|
|
42
|
+
"sass": "^1.104.0"
|
|
41
43
|
},
|
|
42
44
|
"peerDependencies": {
|
|
43
|
-
"@travetto/cli": "^8.0.
|
|
45
|
+
"@travetto/cli": "^8.0.1"
|
|
44
46
|
},
|
|
45
47
|
"peerDependenciesMeta": {
|
|
46
48
|
"@travetto/cli": {
|
|
@@ -52,8 +54,5 @@
|
|
|
52
54
|
"roles": [
|
|
53
55
|
"build"
|
|
54
56
|
]
|
|
55
|
-
},
|
|
56
|
-
"publishConfig": {
|
|
57
|
-
"access": "public"
|
|
58
57
|
}
|
|
59
58
|
}
|
package/src/compiler.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { type EmailCompiled, MailUtil, type EmailTemplateImport, type EmailTemplateModule } from '@travetto/email';
|
|
4
|
+
import { type EmailCompiled, type EmailTemplateImport, type EmailTemplateModule, MailUtil } from '@travetto/email';
|
|
6
5
|
import { ManifestFileUtil } from '@travetto/manifest';
|
|
6
|
+
import { ExecUtil, Runtime, RuntimeIndex, TypedObject } from '@travetto/runtime';
|
|
7
7
|
|
|
8
8
|
import { EmailCompileUtil } from './util.ts';
|
|
9
9
|
|
|
@@ -11,7 +11,6 @@ import { EmailCompileUtil } from './util.ts';
|
|
|
11
11
|
* Email compilation support
|
|
12
12
|
*/
|
|
13
13
|
export class EmailCompiler {
|
|
14
|
-
|
|
15
14
|
/**
|
|
16
15
|
* Load Template
|
|
17
16
|
*/
|
|
@@ -25,13 +24,11 @@ export class EmailCompiler {
|
|
|
25
24
|
* Grab list of all available templates
|
|
26
25
|
*/
|
|
27
26
|
static findAllTemplates(moduleName?: string): string[] {
|
|
28
|
-
return RuntimeIndex
|
|
29
|
-
.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
})
|
|
34
|
-
.map(file => file.sourceFile);
|
|
27
|
+
return RuntimeIndex.find({
|
|
28
|
+
module: module => (!moduleName ? module.roles.includes('std') : moduleName === module.name) && module.production,
|
|
29
|
+
folder: folder => folder === 'support',
|
|
30
|
+
file: file => EmailCompileUtil.isTemplateFile(file.sourceFile)
|
|
31
|
+
}).map(file => file.sourceFile);
|
|
35
32
|
}
|
|
36
33
|
|
|
37
34
|
/**
|
|
@@ -55,14 +52,16 @@ export class EmailCompiler {
|
|
|
55
52
|
*/
|
|
56
53
|
static async writeTemplate(file: string, message: EmailCompiled): Promise<void> {
|
|
57
54
|
const outs = this.getOutputFiles(file);
|
|
58
|
-
await Promise.all(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
55
|
+
await Promise.all(
|
|
56
|
+
TypedObject.keys(outs).map(async key => {
|
|
57
|
+
if (message[key]) {
|
|
58
|
+
const content = MailUtil.buildBrand(file, message[key], 'trv email:compile');
|
|
59
|
+
await ManifestFileUtil.bufferedFileWrite(outs[key], content);
|
|
60
|
+
} else {
|
|
61
|
+
await fs.rm(outs[key], { force: true }); // Remove file if data not provided
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
);
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
/**
|
|
@@ -90,7 +89,7 @@ export class EmailCompiler {
|
|
|
90
89
|
static async spawnCompile(file: string): Promise<boolean> {
|
|
91
90
|
const child = ExecUtil.spawnPackageCommand('trv', ['email:compile', file], {
|
|
92
91
|
cwd: Runtime.mainSourcePath,
|
|
93
|
-
env: { ...process.env }
|
|
92
|
+
env: { ...process.env }
|
|
94
93
|
});
|
|
95
94
|
|
|
96
95
|
const result = await ExecUtil.getResult(child, { catch: true });
|
|
@@ -102,4 +101,4 @@ export class EmailCompiler {
|
|
|
102
101
|
}
|
|
103
102
|
return result.valid;
|
|
104
103
|
}
|
|
105
|
-
}
|
|
104
|
+
}
|
package/src/util.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
|
|
2
3
|
import type { CompileResult, Options } from 'sass';
|
|
3
4
|
|
|
4
5
|
import type { EmailCompiled, EmailTemplateModule, EmailTemplateResource } from '@travetto/email';
|
|
5
6
|
import { ImageUtil } from '@travetto/image';
|
|
6
|
-
import { BinaryUtil, CodecUtil, RuntimeIndex
|
|
7
|
+
import { type BinaryArray, BinaryUtil, CodecUtil, RuntimeIndex } from '@travetto/runtime';
|
|
7
8
|
|
|
8
9
|
type Tokenized = {
|
|
9
10
|
text: string;
|
|
@@ -24,7 +25,6 @@ const EXT = /[.]email[.]tsx$/;
|
|
|
24
25
|
* Email compile tools
|
|
25
26
|
*/
|
|
26
27
|
export class EmailCompileUtil {
|
|
27
|
-
|
|
28
28
|
/**
|
|
29
29
|
* Is file a template?
|
|
30
30
|
*/
|
|
@@ -47,7 +47,7 @@ export class EmailCompileUtil {
|
|
|
47
47
|
return {
|
|
48
48
|
html: this.buildOutputPath(file, '.compiled.html', prefix),
|
|
49
49
|
subject: this.buildOutputPath(file, '.compiled.subject', prefix),
|
|
50
|
-
text: this.buildOutputPath(file, '.compiled.text', prefix)
|
|
50
|
+
text: this.buildOutputPath(file, '.compiled.text', prefix)
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -62,11 +62,12 @@ export class EmailCompileUtil {
|
|
|
62
62
|
let id = 0;
|
|
63
63
|
const tokens = new Map();
|
|
64
64
|
for (const pattern of patterns) {
|
|
65
|
-
for (const {
|
|
66
|
-
if (source.includes('://')) {
|
|
65
|
+
for (const { 0: all, groups: { prefix, source } = { prefix: '', source: '' } } of text.matchAll(pattern)) {
|
|
66
|
+
if (source.includes('://')) {
|
|
67
|
+
// No urls
|
|
67
68
|
continue;
|
|
68
69
|
}
|
|
69
|
-
const token = `@@${id += 1}@@`;
|
|
70
|
+
const token = `@@${(id += 1)}@@`;
|
|
70
71
|
tokens.set(token, source);
|
|
71
72
|
text = text.replace(all, `${prefix}${token}`);
|
|
72
73
|
}
|
|
@@ -85,7 +86,7 @@ export class EmailCompileUtil {
|
|
|
85
86
|
const compilerOptions: Options<'async'> = {
|
|
86
87
|
sourceMap: false,
|
|
87
88
|
quietDeps: true,
|
|
88
|
-
loadPaths: options.loader.searchPaths.slice(0)
|
|
89
|
+
loadPaths: options.loader.searchPaths.slice(0)
|
|
89
90
|
};
|
|
90
91
|
|
|
91
92
|
let result: CompileResult;
|
|
@@ -105,7 +106,7 @@ export class EmailCompileUtil {
|
|
|
105
106
|
const purge = new PurgeCSS();
|
|
106
107
|
const [result] = await purge.purge({
|
|
107
108
|
content: [{ raw: html, extension: 'html' }],
|
|
108
|
-
css: [{ raw: css }]
|
|
109
|
+
css: [{ raw: css }]
|
|
109
110
|
});
|
|
110
111
|
return result.css;
|
|
111
112
|
}
|
|
@@ -115,17 +116,14 @@ export class EmailCompileUtil {
|
|
|
115
116
|
*/
|
|
116
117
|
static async inlineCss(html: string, css: string): Promise<string> {
|
|
117
118
|
// Inline css
|
|
118
|
-
const {
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
removeLinkTags: true,
|
|
127
|
-
applyStyleTags: true,
|
|
128
|
-
});
|
|
119
|
+
const { inline } = await import('@css-inline/css-inline');
|
|
120
|
+
return inline(html, {
|
|
121
|
+
extraCss: css,
|
|
122
|
+
keepAtRules: true,
|
|
123
|
+
keepStyleTags: false,
|
|
124
|
+
keepLinkTags: false,
|
|
125
|
+
loadRemoteStylesheets: false
|
|
126
|
+
});
|
|
129
127
|
}
|
|
130
128
|
|
|
131
129
|
/**
|
|
@@ -166,15 +164,17 @@ export class EmailCompileUtil {
|
|
|
166
164
|
*/
|
|
167
165
|
static handleHtmlEdgeCases(html: string): string {
|
|
168
166
|
return html
|
|
169
|
-
.replace(/\n{3,100}/
|
|
167
|
+
.replace(/\n{3,100}/gms, '\n\n')
|
|
170
168
|
.replace(/<(meta|img|link|hr|br)[^>]{0,200}>/g, a => a.replace(/>/g, '/>')) // Fix self closing
|
|
171
169
|
.replace(/'/g, ''') // Fix apostrophes, as outlook hates them
|
|
172
|
-
.replace(
|
|
173
|
-
(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
.replace(/<(
|
|
177
|
-
(
|
|
170
|
+
.replace(
|
|
171
|
+
/(background(?:-color)?:\s*)([#0-9a-f]{6,8})([^>.#,]+)>/gi,
|
|
172
|
+
(all, property, color, rest) => `${property}${color}${rest} bgcolor="${color}">`
|
|
173
|
+
) // Inline bg-color
|
|
174
|
+
.replace(/<([^>]+vertical-align:\s*(top|bottom|middle)[^>]+)>/g, (a, tag, valign) =>
|
|
175
|
+
tag.indexOf('valign') ? `<${tag}>` : `<${tag} valign="${valign}">`
|
|
176
|
+
) // Vertically align if it has the style
|
|
177
|
+
.replace(/<(table[^>]+expand[^>]+width:\s*)(100%\s+!important)([^>]+)>/g, (a, left, size, right) => `<${left}100%${right}>`) // Drop important as a fix for outlook
|
|
178
178
|
.trim()
|
|
179
179
|
.concat('\n');
|
|
180
180
|
}
|
|
@@ -183,10 +183,7 @@ export class EmailCompileUtil {
|
|
|
183
183
|
* Apply styles into a given html document
|
|
184
184
|
*/
|
|
185
185
|
static async applyStyles(html: string, options: EmailTemplateResource): Promise<string> {
|
|
186
|
-
const styles = [
|
|
187
|
-
options.globalStyles ?? '',
|
|
188
|
-
await options.loader.readUTF8('/email/main.scss').catch(() => '')
|
|
189
|
-
]
|
|
186
|
+
const styles = [options.globalStyles ?? '', await options.loader.readUTF8('/email/main.scss').catch(() => '')]
|
|
190
187
|
.filter(line => !!line)
|
|
191
188
|
.join('\n');
|
|
192
189
|
|
|
@@ -222,4 +219,4 @@ export class EmailCompileUtil {
|
|
|
222
219
|
|
|
223
220
|
return { html, subject, text };
|
|
224
221
|
}
|
|
225
|
-
}
|
|
222
|
+
}
|
package/support/bin/config.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
|
|
2
3
|
import { parse, stringify } from 'yaml';
|
|
3
4
|
|
|
4
|
-
import { Runtime } from '@travetto/runtime';
|
|
5
5
|
import { ManifestFileUtil } from '@travetto/manifest';
|
|
6
|
+
import { Runtime } from '@travetto/runtime';
|
|
6
7
|
|
|
7
8
|
import type { EditorConfigType } from './types.ts';
|
|
8
9
|
|
|
@@ -12,7 +13,6 @@ export const CONFIG_FILE = 'resources/email/local.yml';
|
|
|
12
13
|
* Configuration utils
|
|
13
14
|
*/
|
|
14
15
|
export class EditorConfig {
|
|
15
|
-
|
|
16
16
|
static DEFAULT_CONFIG = {
|
|
17
17
|
to: 'my-email@gmail.com',
|
|
18
18
|
from: 'from-email@gmail.com',
|
|
@@ -25,8 +25,8 @@ export class EditorConfig {
|
|
|
25
25
|
auth: {
|
|
26
26
|
user: 'email@blah.com',
|
|
27
27
|
pass: 'password'
|
|
28
|
-
}
|
|
29
|
-
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
/**
|
|
@@ -56,4 +56,4 @@ export class EditorConfig {
|
|
|
56
56
|
}
|
|
57
57
|
return resolved;
|
|
58
58
|
}
|
|
59
|
-
}
|
|
59
|
+
}
|
package/support/bin/editor.ts
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import { Inject, Injectable } from '@travetto/di';
|
|
2
|
-
import {
|
|
2
|
+
import { type EmailCompiled, type MailInterpolator, MailUtil } from '@travetto/email';
|
|
3
3
|
import { RuntimeError, TypedObject, WatchUtil } from '@travetto/runtime';
|
|
4
4
|
|
|
5
|
-
import type { EditorSendService } from './send.ts';
|
|
6
|
-
import { EditorConfig } from './config.ts';
|
|
7
|
-
import type { EditorRequest, EditorResponse } from './types.ts';
|
|
8
|
-
|
|
9
5
|
import { EmailCompiler } from '../../src/compiler.ts';
|
|
10
6
|
import { EmailCompileUtil } from '../../src/util.ts';
|
|
7
|
+
import { EditorConfig } from './config.ts';
|
|
8
|
+
import type { EditorSendService } from './send.ts';
|
|
9
|
+
import type { EditorRequest, EditorResponse } from './types.ts';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* Utils for interacting with editors
|
|
14
13
|
*/
|
|
15
14
|
@Injectable()
|
|
16
15
|
export class EditorService {
|
|
17
|
-
|
|
18
16
|
@Inject()
|
|
19
17
|
sender: EditorSendService;
|
|
20
18
|
|
|
@@ -28,12 +26,11 @@ export class EditorService {
|
|
|
28
26
|
async #renderTemplate(templateFile: string, context: Record<string, unknown>): Promise<EmailCompiled> {
|
|
29
27
|
const email = await EmailCompiler.compile(templateFile);
|
|
30
28
|
return TypedObject.fromEntries(
|
|
31
|
-
await Promise.all(TypedObject.entries(email).map(([key, value]) =>
|
|
32
|
-
this.#interpolate(value, context).then((result) => [key, result])))
|
|
29
|
+
await Promise.all(TypedObject.entries(email).map(([key, value]) => this.#interpolate(value, context).then(result => [key, result])))
|
|
33
30
|
);
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
async #renderFile(file: string): Promise<{ content: EmailCompiled
|
|
33
|
+
async #renderFile(file: string): Promise<{ content: EmailCompiled; file: string }> {
|
|
37
34
|
const content = await this.#renderTemplate(file, await EditorConfig.get('context'));
|
|
38
35
|
return { content, file };
|
|
39
36
|
}
|
|
@@ -41,7 +38,9 @@ export class EditorService {
|
|
|
41
38
|
async #response<T>(operation: Promise<T>, success: (value: T) => EditorResponse, fail?: (error: Error) => EditorResponse): Promise<void> {
|
|
42
39
|
try {
|
|
43
40
|
const response = await operation;
|
|
44
|
-
if (process.connected) {
|
|
41
|
+
if (process.connected) {
|
|
42
|
+
process.send?.(success(response));
|
|
43
|
+
}
|
|
45
44
|
} catch (error) {
|
|
46
45
|
if (fail && process.connected && error && error instanceof Error) {
|
|
47
46
|
process.send?.(fail(error));
|
|
@@ -51,11 +50,11 @@ export class EditorService {
|
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
async sendFile(file: string, to?: string): Promise<{ to: string
|
|
53
|
+
async sendFile(file: string, to?: string): Promise<{ to: string; file: string; url?: string | false | undefined }> {
|
|
55
54
|
const config = await EditorConfig.get();
|
|
56
55
|
to ||= config.to;
|
|
57
56
|
const content = await this.#renderTemplate(file, config.context ?? {});
|
|
58
|
-
return { to, file, ...await this.sender.send({ from: config.from, to, ...content
|
|
57
|
+
return { to, file, ...(await this.sender.send({ from: config.from, to, ...content })) };
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
/**
|
|
@@ -71,7 +70,8 @@ export class EditorService {
|
|
|
71
70
|
return await this.#response(EditorConfig.ensureConfig(), file => ({ type: 'configured', file }));
|
|
72
71
|
}
|
|
73
72
|
case 'compile': {
|
|
74
|
-
return await this.#response(
|
|
73
|
+
return await this.#response(
|
|
74
|
+
this.#renderFile(request.file),
|
|
75
75
|
result => ({ type: 'compiled', ...result }),
|
|
76
76
|
error => ({ type: 'compiled-failed', message: error.message, stack: error.stack, file: request.file })
|
|
77
77
|
);
|
|
@@ -88,13 +88,19 @@ export class EditorService {
|
|
|
88
88
|
|
|
89
89
|
process.send({ type: 'init' });
|
|
90
90
|
|
|
91
|
-
await WatchUtil.watchCompilerEvents(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
await WatchUtil.watchCompilerEvents(
|
|
92
|
+
'change',
|
|
93
|
+
({ file }) =>
|
|
94
|
+
EmailCompiler.spawnCompile(file).then(success =>
|
|
95
|
+
success
|
|
96
|
+
? this.#response(
|
|
97
|
+
this.#renderFile(file),
|
|
98
|
+
result => ({ type: 'compiled', ...result }),
|
|
99
|
+
error => ({ type: 'compiled-failed', message: error.message, stack: error.stack, file })
|
|
100
|
+
)
|
|
101
|
+
: undefined
|
|
102
|
+
),
|
|
103
|
+
({ file }) => EmailCompileUtil.isTemplateFile(file)
|
|
104
|
+
);
|
|
99
105
|
}
|
|
100
|
-
}
|
|
106
|
+
}
|
package/support/bin/send.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { MailService, type EmailOptions, type MailTransport } from '@travetto/email';
|
|
2
1
|
import { DependencyRegistryIndex, Injectable } from '@travetto/di';
|
|
3
|
-
import {
|
|
2
|
+
import { type EmailOptions, MailService, type MailTransport } from '@travetto/email';
|
|
4
3
|
import { Registry } from '@travetto/registry';
|
|
4
|
+
import { toConcrete } from '@travetto/runtime';
|
|
5
5
|
|
|
6
6
|
import { EditorConfig } from './config.ts';
|
|
7
7
|
|
|
@@ -10,7 +10,6 @@ import { EditorConfig } from './config.ts';
|
|
|
10
10
|
*/
|
|
11
11
|
@Injectable()
|
|
12
12
|
export class EditorSendService {
|
|
13
|
-
|
|
14
13
|
ethereal = false;
|
|
15
14
|
|
|
16
15
|
async service(): Promise<MailService> {
|
|
@@ -22,14 +21,14 @@ export class EditorSendService {
|
|
|
22
21
|
try {
|
|
23
22
|
const { NodemailerTransport } = await import('@travetto/email-nodemailer');
|
|
24
23
|
const senderConfig = await EditorConfig.get('sender');
|
|
25
|
-
const cls = class {
|
|
24
|
+
const cls = class {};
|
|
26
25
|
DependencyRegistryIndex.getForRegister(cls).register({
|
|
27
26
|
candidates: {
|
|
28
27
|
factory: {
|
|
29
28
|
candidateType: MailTransportTarget,
|
|
30
29
|
factory: () => new NodemailerTransport(senderConfig),
|
|
31
30
|
class: cls,
|
|
32
|
-
method: 'factory'
|
|
31
|
+
method: 'factory'
|
|
33
32
|
}
|
|
34
33
|
}
|
|
35
34
|
});
|
|
@@ -37,8 +36,12 @@ export class EditorSendService {
|
|
|
37
36
|
|
|
38
37
|
this.ethereal = !!senderConfig.host?.includes('ethereal.email');
|
|
39
38
|
} catch {
|
|
40
|
-
console.error(
|
|
41
|
-
|
|
39
|
+
console.error(
|
|
40
|
+
'A mail transport is currently needed to support sending emails. Please install @travetto/email-nodemailer or any other compatible transport'
|
|
41
|
+
);
|
|
42
|
+
throw new Error(
|
|
43
|
+
'A mail transport is currently needed to support sending emails. Please install @travetto/email-nodemailer or any other compatible transport'
|
|
44
|
+
);
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
const service = await DependencyRegistryIndex.getInstance(MailService);
|
|
@@ -72,4 +75,4 @@ export class EditorSendService {
|
|
|
72
75
|
throw error;
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
|
-
}
|
|
78
|
+
}
|
package/support/bin/types.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
export type EditorRequest =
|
|
2
|
-
{ type: 'configure'
|
|
3
|
-
{ type: 'compile'
|
|
4
|
-
{ type: 'send'
|
|
2
|
+
| { type: 'configure'; file: string }
|
|
3
|
+
| { type: 'compile'; file: string }
|
|
4
|
+
| { type: 'send'; file: string; from?: string; to?: string };
|
|
5
5
|
|
|
6
6
|
export type EditorResponse =
|
|
7
|
-
{ type: 'configured'
|
|
8
|
-
{ type: 'sent'
|
|
9
|
-
{ type: 'compiled'
|
|
10
|
-
{ type: 'sent-failed'
|
|
11
|
-
{ type: 'compiled-failed'
|
|
12
|
-
{ type: 'init' };
|
|
7
|
+
| { type: 'configured'; file: string }
|
|
8
|
+
| { type: 'sent'; to: string; file: string; url?: string | false }
|
|
9
|
+
| { type: 'compiled'; file: string; content: Record<'html' | 'subject' | 'text', string> }
|
|
10
|
+
| { type: 'sent-failed'; message: string; stack: Error['stack']; to: string; file: string }
|
|
11
|
+
| { type: 'compiled-failed'; message: string; stack: Error['stack']; file: string }
|
|
12
|
+
| { type: 'init' };
|
|
13
13
|
|
|
14
14
|
export type EditorSender = {
|
|
15
15
|
port?: number;
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
+
import { CliCommand, type CliCommandShape, cliTpl } from '@travetto/cli';
|
|
1
2
|
import { Registry } from '@travetto/registry';
|
|
2
|
-
import { type CliCommandShape, CliCommand, cliTpl } from '@travetto/cli';
|
|
3
3
|
import { Env, Runtime, WatchUtil } from '@travetto/runtime';
|
|
4
4
|
|
|
5
5
|
import { EmailCompiler } from '../src/compiler.ts';
|
|
6
6
|
import { EmailCompileUtil } from '../src/util.ts';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Compile all email templates into generated runtime artifacts.
|
|
10
|
+
*
|
|
11
|
+
* The command discovers templated inputs (for example, `.email.html`) and emits
|
|
12
|
+
* compiled outputs used at runtime (html/text/subject variants). With watch
|
|
13
|
+
* enabled, recompilation runs automatically on matching template changes.
|
|
10
14
|
*/
|
|
11
15
|
@CliCommand()
|
|
12
16
|
export class EmailCompileCommand implements CliCommandShape {
|
|
13
|
-
|
|
14
|
-
/** Compile in watch mode */
|
|
17
|
+
/** Recompile templates whenever source templates change. */
|
|
15
18
|
watch?: boolean;
|
|
16
19
|
|
|
17
20
|
finalize(): void {
|
|
@@ -30,9 +33,11 @@ export class EmailCompileCommand implements CliCommandShape {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
if (this.watch) {
|
|
33
|
-
await WatchUtil.watchCompilerEvents(
|
|
36
|
+
await WatchUtil.watchCompilerEvents(
|
|
37
|
+
'change',
|
|
34
38
|
({ file }) => EmailCompiler.spawnCompile(file),
|
|
35
|
-
({ file }) => EmailCompileUtil.isTemplateFile(file)
|
|
39
|
+
({ file }) => EmailCompileUtil.isTemplateFile(file)
|
|
40
|
+
);
|
|
36
41
|
}
|
|
37
42
|
}
|
|
38
|
-
}
|
|
43
|
+
}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { Env } from '@travetto/runtime';
|
|
2
1
|
import { CliCommand, CliProfilesFlag } from '@travetto/cli';
|
|
3
|
-
import { Registry } from '@travetto/registry';
|
|
4
2
|
import { DependencyRegistryIndex } from '@travetto/di';
|
|
3
|
+
import { Registry } from '@travetto/registry';
|
|
4
|
+
import { Env } from '@travetto/runtime';
|
|
5
5
|
|
|
6
6
|
import { EditorService } from './bin/editor.ts';
|
|
7
7
|
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Start the email template editor service for interactive preview and testing.
|
|
10
|
+
*
|
|
11
|
+
* The editor compiles templates on demand and hosts the preview/test workflow
|
|
12
|
+
* used during local template authoring.
|
|
13
|
+
*/
|
|
9
14
|
@CliCommand()
|
|
10
15
|
export class EmailEditorCommand {
|
|
11
|
-
|
|
12
16
|
@CliProfilesFlag()
|
|
13
17
|
profile: string[];
|
|
14
18
|
|
|
@@ -21,4 +25,4 @@ export class EmailEditorCommand {
|
|
|
21
25
|
const service = await DependencyRegistryIndex.getInstance(EditorService);
|
|
22
26
|
await service.listen();
|
|
23
27
|
}
|
|
24
|
-
}
|
|
28
|
+
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { type CliCommandShape, CliCommand, CliProfilesFlag } from '@travetto/cli';
|
|
3
|
+
import { CliCommand, type CliCommandShape, CliProfilesFlag } from '@travetto/cli';
|
|
5
4
|
import { DependencyRegistryIndex } from '@travetto/di';
|
|
5
|
+
import { Registry } from '@travetto/registry';
|
|
6
6
|
import { Env } from '@travetto/runtime';
|
|
7
7
|
|
|
8
8
|
import { EditorService } from './bin/editor.ts';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Render and send a template file to a target recipient for quick validation.
|
|
12
|
+
*
|
|
13
|
+
* This command is useful during template development to verify real delivery and
|
|
14
|
+
* formatting without running the full editor workflow.
|
|
12
15
|
*/
|
|
13
16
|
@CliCommand()
|
|
14
17
|
export class EmailTestCommand implements CliCommandShape {
|
|
15
|
-
|
|
16
18
|
@CliProfilesFlag()
|
|
17
19
|
profile: string[];
|
|
18
20
|
|
|
@@ -26,4 +28,4 @@ export class EmailTestCommand implements CliCommandShape {
|
|
|
26
28
|
const editor = await DependencyRegistryIndex.getInstance(EditorService);
|
|
27
29
|
await editor.sendFile(file, to);
|
|
28
30
|
}
|
|
29
|
-
}
|
|
31
|
+
}
|