@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/email-compiler",
3
- "version": "7.0.0-rc.1",
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.1",
30
- "@travetto/di": "^7.0.0-rc.1",
31
- "@travetto/email": "^7.0.0-rc.1",
32
- "@travetto/image": "^7.0.0-rc.1",
33
- "@travetto/runtime": "^7.0.0-rc.1",
34
- "@travetto/worker": "^7.0.0-rc.1",
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.1"
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, watchCompiler, Runtime, BinaryUtil } from '@travetto/runtime';
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(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,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(src => this.compile(src)));
82
+ await Promise.all(keys.map(key => this.compile(key)));
83
83
  return keys;
84
84
  }
85
85
 
86
86
  /**
87
- * Watch compilation
87
+ * Spawn the compiler for a given file
88
88
  */
89
- static async * watchCompile(signal?: AbortSignal): AsyncIterable<string> {
90
- // Watch template files
91
- for await (const { file, action } of watchCompiler({ signal })) {
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 SUPPORT_SRC = /(?:support|src)\//;
15
+ const SUPPORT_SOURCE = /(?:support|src)\//;
15
16
 
16
17
  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
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 res = (SUPPORT_SRC.test(file) ? file.split(SUPPORT_SRC)[1] : file).replace(EXT, suffix);
40
- return prefix ? path.join(prefix, res) : res;
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: { pre, src } = { pre: '', src: '' } } of text.matchAll(pattern)) {
66
- if (src.includes('://')) { // No urls
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, src);
71
- text = text.replace(all, `${pre}${token}`);
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, t => onToken(t));
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(src: { data: string } | { file: string }, opts: EmailTemplateResource): Promise<string> {
83
- const sass = await import('sass');
84
- const result = await util.promisify(sass.render)({
85
- ...src,
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
- includePaths: opts.loader.searchPaths.slice(0)
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, opts: EmailTemplateResource): Promise<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, src] of tokens) {
141
- const ext = path.extname(src);
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 opts.loader.readStream(src),
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, opts.loader.read(src, true)]);
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(/&apos;/g, '&#39;') // Fix apostrophes, as outlook hates them
169
178
  .replace(/(background(?:-color)?:\s*)([#0-9a-f]{6,8})([^>.#,]+)>/ig,
170
- (all, p, col, rest) => `${p}${col}${rest} bgcolor="${col}">`) // Inline bg-color
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, opts: EmailTemplateResource): Promise<string> {
191
+ static async applyStyles(html: string, options: EmailTemplateResource): Promise<string> {
183
192
  const styles = [
184
- opts.globalStyles ?? '',
185
- await opts.loader.read('/email/main.scss').catch(() => '')
186
- ].filter(x => !!x).join('\n');
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 }, opts);
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(src: EmailTemplateModule): Promise<EmailCompiled> {
202
- const subject = await this.simplifiedText(await src.subject());
203
- const text = await this.simplifiedText(await src.text());
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 src.html();
216
+ let html = await input.html();
206
217
 
207
- if (src.inlineStyle !== false) {
208
- html = await this.applyStyles(html, src);
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 (src.inlineImages !== false) {
215
- html = await this.inlineImages(html, src);
225
+ if (input.inlineImages !== false) {
226
+ html = await this.inlineImages(html, input);
216
227
  }
217
228
 
218
229
  return { html, subject, text };
@@ -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(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
  }
@@ -86,11 +87,14 @@ export class EditorService {
86
87
 
87
88
  process.send({ type: 'init' });
88
89
 
89
- for await (const file of EmailCompiler.watchCompile()) {
90
- await this.#response(this.#renderFile(file),
91
- res => ({ type: 'compiled', ...res }),
92
- err => ({ type: 'compiled-failed', message: err.message, stack: err.stack, file })
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
  }
@@ -33,7 +33,7 @@ export class EditorSendService {
33
33
  }
34
34
  }
35
35
  });
36
- Registry.process([{ type: 'added', curr: cls }]);
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
- return await DependencyRegistryIndex.getInstance(MailService);
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 svc = await this.service();
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 svc.send<SendMessage>(message);
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 svc.send(message);
66
+ await service.send(message);
65
67
  console.log('Sent email', { to });
66
68
  return {};
67
69
  }
68
- } catch (err) {
69
- console.warn('Failed to send email', { to, error: err });
70
- throw err;
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 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) }}`);
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 _ of EmailCompiler.watchCompile()) {
34
- // Iterate until done
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, CliUtil } from '@travetto/cli';
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();