@travetto/email-compiler 7.0.0-rc.0 → 7.0.0-rc.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/email-compiler",
3
- "version": "7.0.0-rc.0",
3
+ "version": "7.0.0-rc.2",
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.0",
30
- "@travetto/di": "^7.0.0-rc.0",
31
- "@travetto/email": "^7.0.0-rc.0",
32
- "@travetto/image": "^7.0.0-rc.0",
33
- "@travetto/runtime": "^7.0.0-rc.0",
34
- "@travetto/worker": "^7.0.0-rc.0",
29
+ "@travetto/config": "^7.0.0-rc.2",
30
+ "@travetto/di": "^7.0.0-rc.2",
31
+ "@travetto/email": "^7.0.0-rc.2",
32
+ "@travetto/image": "^7.0.0-rc.2",
33
+ "@travetto/runtime": "^7.0.0-rc.2",
34
+ "@travetto/worker": "^7.0.0-rc.2",
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.0"
42
+ "@travetto/cli": "^7.0.0-rc.2"
43
43
  },
44
44
  "peerDependenciesMeta": {
45
45
  "@travetto/cli": {
package/src/compiler.ts CHANGED
@@ -23,14 +23,14 @@ export class EmailCompiler {
23
23
  /**
24
24
  * Grab list of all available templates
25
25
  */
26
- static findAllTemplates(mod?: string): string[] {
26
+ static findAllTemplates(moduleName?: string): string[] {
27
27
  return RuntimeIndex
28
28
  .find({
29
- module: m => !mod ? m.roles.includes('std') : mod === m.name,
30
- folder: f => f === 'support',
31
- file: f => EmailCompileUtil.isTemplateFile(f.sourceFile)
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(x => x.sourceFile);
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, msg: EmailCompiled): Promise<void> {
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 k => {
58
- if (msg[k]) {
59
- const content = MailUtil.buildBrand(file, msg[k], 'trv email:compile');
60
- await BinaryUtil.bufferedFileWrite(outs[k], content);
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[k], { force: true }); // Remove file if data not provided
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 tpl = await this.loadTemplate(file);
72
- const compiled = await EmailCompileUtil.compile(tpl);
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,7 +79,7 @@ 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(src => this.compile(src)));
82
+ await Promise.all(keys.map(key => this.compile(key)));
83
83
  return keys;
84
84
  }
85
85
 
@@ -89,16 +89,16 @@ export class EmailCompiler {
89
89
  static async * watchCompile(signal?: AbortSignal): AsyncIterable<string> {
90
90
  // Watch template files
91
91
  for await (const { file, action } of watchCompiler({ signal })) {
92
- const src = RuntimeIndex.getEntry(file);
93
- if (!src || !EmailCompileUtil.isTemplateFile(src.sourceFile) || action === 'delete') {
92
+ const entry = RuntimeIndex.getEntry(file);
93
+ if (!entry || !EmailCompileUtil.isTemplateFile(entry.sourceFile) || action === 'delete') {
94
94
  continue;
95
95
  }
96
96
  try {
97
97
  await this.compile(file);
98
98
  console.log('Successfully compiled template', { changed: [file] });
99
99
  yield file;
100
- } catch (err) {
101
- console.error(`Error in compiling ${file}`, err && err instanceof Error ? err.message : `${err}`);
100
+ } catch (error) {
101
+ console.error(`Error in compiling ${file}`, error && error instanceof Error ? error.message : `${error}`);
102
102
  }
103
103
  }
104
104
  }
package/src/util.ts CHANGED
@@ -11,11 +11,11 @@ type Tokenized = {
11
11
  finalize: (onToken: (token: string) => string) => string;
12
12
  };
13
13
 
14
- const SUPPORT_SRC = /(?:support|src)\//;
14
+ const SUPPORT_SOURCE = /(?:support|src)\//;
15
15
 
16
16
  const HTML_CSS_IMAGE_URLS = [
17
- /(?<pre><img[^>]src=\s{0,10}["'])(?<src>[^"{}]{1,1000})/g,
18
- /(?<pre>background(?:-image)?:\s{0,10}url[(]['"]?)(?<src>[^"'){}]{1,1000})/g
17
+ /(?<prefix><img[^>]src=\s{0,10}["'])(?<source>[^"{}]{1,1000})/g,
18
+ /(?<prefix>background(?:-image)?:\s{0,10}url[(]['"]?)(?<source>[^"'){}]{1,1000})/g
19
19
  ];
20
20
 
21
21
  const EXT = /[.]email[.]tsx$/;
@@ -36,8 +36,8 @@ export class EmailCompileUtil {
36
36
  * Generate singular output path given a file
37
37
  */
38
38
  static buildOutputPath(file: string, suffix: string, prefix?: string): string {
39
- const res = (SUPPORT_SRC.test(file) ? file.split(SUPPORT_SRC)[1] : file).replace(EXT, suffix);
40
- return prefix ? path.join(prefix, res) : res;
39
+ const location = (SUPPORT_SOURCE.test(file) ? file.split(SUPPORT_SOURCE)[1] : file).replace(EXT, suffix);
40
+ return prefix ? path.join(prefix, location) : location;
41
41
  }
42
42
 
43
43
  /**
@@ -62,16 +62,16 @@ export class EmailCompileUtil {
62
62
  let id = 0;
63
63
  const tokens = new Map();
64
64
  for (const pattern of patterns) {
65
- for (const { [0]: all, groups: { pre, src } = { pre: '', src: '' } } of text.matchAll(pattern)) {
66
- if (src.includes('://')) { // No urls
65
+ for (const { [0]: all, groups: { prefix, source } = { prefix: '', source: '' } } of text.matchAll(pattern)) {
66
+ if (source.includes('://')) { // No urls
67
67
  continue;
68
68
  }
69
69
  const token = `@@${id += 1}@@`;
70
- tokens.set(token, src);
71
- text = text.replace(all, `${pre}${token}`);
70
+ tokens.set(token, source);
71
+ text = text.replace(all, `${prefix}${token}`);
72
72
  }
73
73
  }
74
- const finalize = (onToken: (token: string) => string): string => text.replace(/@@[^@]{1,100}@@/g, t => onToken(t));
74
+ const finalize = (onToken: (token: string) => string): string => text.replace(/@@[^@]{1,100}@@/g, token => onToken(token));
75
75
 
76
76
  return { text, tokens, finalize };
77
77
  }
@@ -79,12 +79,12 @@ export class EmailCompileUtil {
79
79
  /**
80
80
  * Compile SCSS content with roots as search paths for additional assets
81
81
  */
82
- static async compileSass(src: { data: string } | { file: string }, opts: EmailTemplateResource): Promise<string> {
82
+ static async compileSass(input: { data: string } | { file: string }, options: EmailTemplateResource): Promise<string> {
83
83
  const sass = await import('sass');
84
84
  const result = await util.promisify(sass.render)({
85
- ...src,
85
+ ...input,
86
86
  sourceMap: false,
87
- includePaths: opts.loader.searchPaths.slice(0)
87
+ includePaths: options.loader.searchPaths.slice(0)
88
88
  });
89
89
  return result!.css.toString();
90
90
  }
@@ -133,21 +133,21 @@ export class EmailCompileUtil {
133
133
  /**
134
134
  * Inline image sources
135
135
  */
136
- static async inlineImages(html: string, opts: EmailTemplateResource): Promise<string> {
136
+ static async inlineImages(html: string, options: EmailTemplateResource): Promise<string> {
137
137
  const { tokens, finalize } = await this.tokenizeResources(html, HTML_CSS_IMAGE_URLS);
138
138
  const pendingImages: [token: string, ext: string, stream: Buffer | Promise<Buffer>][] = [];
139
139
 
140
- for (const [token, src] of tokens) {
141
- const ext = path.extname(src);
140
+ for (const [token, source] of tokens) {
141
+ const ext = path.extname(source);
142
142
  if (/^[.](jpe?g|png)$/.test(ext)) {
143
143
  const output = await ImageUtil.convert(
144
- await opts.loader.readStream(src),
144
+ await options.loader.readStream(source),
145
145
  { format: ext === '.png' ? 'png' : 'jpeg' }
146
146
  );
147
147
  const buffer = await toBuffer(output);
148
148
  pendingImages.push([token, ext, buffer]);
149
149
  } else {
150
- pendingImages.push([token, ext, opts.loader.read(src, true)]);
150
+ pendingImages.push([token, ext, options.loader.read(source, true)]);
151
151
  }
152
152
  }
153
153
 
@@ -167,7 +167,7 @@ export class EmailCompileUtil {
167
167
  .replace(/<(meta|img|link|hr|br)[^>]{0,200}>/g, a => a.replace(/>/g, '/>')) // Fix self closing
168
168
  .replace(/&apos;/g, '&#39;') // Fix apostrophes, as outlook hates them
169
169
  .replace(/(background(?:-color)?:\s*)([#0-9a-f]{6,8})([^>.#,]+)>/ig,
170
- (all, p, col, rest) => `${p}${col}${rest} bgcolor="${col}">`) // Inline bg-color
170
+ (all, property, color, rest) => `${property}${color}${rest} bgcolor="${color}">`) // Inline bg-color
171
171
  .replace(/<([^>]+vertical-align:\s*(top|bottom|middle)[^>]+)>/g,
172
172
  (a, tag, valign) => tag.indexOf('valign') ? `<${tag}>` : `<${tag} valign="${valign}">`) // Vertically align if it has the style
173
173
  .replace(/<(table[^>]+expand[^>]+width:\s*)(100%\s+!important)([^>]+)>/g,
@@ -179,14 +179,16 @@ export class EmailCompileUtil {
179
179
  /**
180
180
  * Apply styles into a given html document
181
181
  */
182
- static async applyStyles(html: string, opts: EmailTemplateResource): Promise<string> {
182
+ static async applyStyles(html: string, options: EmailTemplateResource): Promise<string> {
183
183
  const styles = [
184
- opts.globalStyles ?? '',
185
- await opts.loader.read('/email/main.scss').catch(() => '')
186
- ].filter(x => !!x).join('\n');
184
+ options.globalStyles ?? '',
185
+ await options.loader.read('/email/main.scss').catch(() => '')
186
+ ]
187
+ .filter(line => !!line)
188
+ .join('\n');
187
189
 
188
190
  if (styles.length) {
189
- const compiled = await this.compileSass({ data: styles }, opts);
191
+ const compiled = await this.compileSass({ data: styles }, options);
190
192
 
191
193
  // Remove all unused styles
192
194
  const finalStyles = await this.pruneCss(html, compiled);
@@ -198,21 +200,21 @@ export class EmailCompileUtil {
198
200
  return html;
199
201
  }
200
202
 
201
- static async compile(src: EmailTemplateModule): Promise<EmailCompiled> {
202
- const subject = await this.simplifiedText(await src.subject());
203
- const text = await this.simplifiedText(await src.text());
203
+ static async compile(input: EmailTemplateModule): Promise<EmailCompiled> {
204
+ const subject = await this.simplifiedText(await input.subject());
205
+ const text = await this.simplifiedText(await input.text());
204
206
 
205
- let html = await src.html();
207
+ let html = await input.html();
206
208
 
207
- if (src.inlineStyle !== false) {
208
- html = await this.applyStyles(html, src);
209
+ if (input.inlineStyle !== false) {
210
+ html = await this.applyStyles(html, input);
209
211
  }
210
212
 
211
213
  // Fix up html edge cases
212
214
  html = this.handleHtmlEdgeCases(html);
213
215
 
214
- if (src.inlineImages !== false) {
215
- html = await this.inlineImages(html, src);
216
+ if (input.inlineImages !== false) {
217
+ html = await this.inlineImages(html, input);
216
218
  }
217
219
 
218
220
  return { html, subject, text };
@@ -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(rel: string, context: Record<string, unknown>): Promise<EmailCompiled> {
28
- const p = await EmailCompiler.compile(rel);
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(p).map(([k, v]) => this.#interpolate(v, context).then((t) => [k, t])))
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>(op: Promise<T>, success: (v: T) => EditorResponse, fail?: (err: Error) => EditorResponse): Promise<void> {
40
+ async #response<T>(operation: Promise<T>, success: (value: T) => EditorResponse, fail?: (error: Error) => EditorResponse): Promise<void> {
40
41
  try {
41
- const res = await op;
42
- if (process.connected) { process.send?.(success(res)); }
43
- } catch (err) {
44
- if (fail && process.connected && err && err instanceof Error) {
45
- process.send?.(fail(err));
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(err);
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 cfg = await EditorConfig.get();
54
- to ||= cfg.to;
55
- const content = await this.#renderTemplate(file, cfg.context ?? {});
56
- return { to, file, ...await this.sender.send({ from: cfg.from, to, ...content, }) };
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 (msg: EditorRequest) => {
67
- switch (msg.type) {
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(msg.file),
73
- res => ({ type: 'compiled', ...res }),
74
- err => ({ type: 'compiled-failed', message: err.message, stack: err.stack, file: msg.file })
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(msg.file, msg.to),
80
- res => ({ type: 'sent', ...res }),
81
- err => ({ type: 'sent-failed', message: err.message, stack: err.stack, to: msg.to!, file: msg.file })
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
  }
@@ -88,8 +89,8 @@ export class EditorService {
88
89
 
89
90
  for await (const file of EmailCompiler.watchCompile()) {
90
91
  await this.#response(this.#renderFile(file),
91
- res => ({ type: 'compiled', ...res }),
92
- err => ({ type: 'compiled-failed', message: err.message, stack: err.stack, file })
92
+ result => ({ type: 'compiled', ...result }),
93
+ error => ({ type: 'compiled-failed', message: error.message, stack: error.stack, file })
93
94
  );
94
95
  }
95
96
  }
@@ -33,7 +33,7 @@ export class EditorSendService {
33
33
  }
34
34
  }
35
35
  });
36
- Registry.process([{ type: 'added', curr: cls }]);
36
+ Registry.process([{ type: 'added', current: cls }]);
37
37
 
38
38
  this.ethereal = !!senderConfig.host?.includes('ethereal.email');
39
39
  } catch {
@@ -51,23 +51,23 @@ export class EditorSendService {
51
51
  const to = message.to!;
52
52
  try {
53
53
  console.log('Sending email', { to });
54
- const svc = await this.service();
54
+ const service = await this.service();
55
55
  if (this.ethereal) {
56
56
  const { getTestMessageUrl } = await import('nodemailer');
57
57
  const { default: _smtp } = await import('nodemailer/lib/smtp-transport/index');
58
58
  type SendMessage = Parameters<Parameters<(typeof _smtp)['prototype']['send']>[1]>[1];
59
- const info = await svc.send<SendMessage>(message);
59
+ const info = await service.send<SendMessage>(message);
60
60
  const url = getTestMessageUrl(info);
61
61
  console.log('Sent email', { to, url });
62
62
  return { url };
63
63
  } else {
64
- await svc.send(message);
64
+ await service.send(message);
65
65
  console.log('Sent email', { to });
66
66
  return {};
67
67
  }
68
- } catch (err) {
69
- console.warn('Failed to send email', { to, error: err });
70
- throw err;
68
+ } catch (error) {
69
+ console.warn('Failed to send email', { to, error });
70
+ throw error;
71
71
  }
72
72
  }
73
73
  }
@@ -23,10 +23,10 @@ export class EmailCompileCommand implements CliCommandShape {
23
23
  await Registry.init();
24
24
 
25
25
  // Let the engine template
26
- const all = await EmailCompiler.compileAll();
27
- console!.log(cliTpl`Successfully compiled ${{ param: `${all.length}` }} templates`);
28
- for (const el of all) {
29
- console!.log(cliTpl` * ${{ param: Runtime.stripWorkspacePath(el) }}`);
26
+ const locations = await EmailCompiler.compileAll();
27
+ console!.log(cliTpl`Successfully compiled ${{ param: `${locations.length}` }} templates`);
28
+ for (const location of locations) {
29
+ console!.log(cliTpl` * ${{ param: Runtime.stripWorkspacePath(location) }}`);
30
30
  }
31
31
 
32
32
  if (this.watch) {