@mailmodo/cli 0.0.30-beta.pr32.51 → 0.0.30-beta.pr32.53
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/dist/commands/edit/index.js +3 -3
- package/dist/commands/emails/index.js +9 -4
- package/dist/commands/preview/index.d.ts +6 -2
- package/dist/commands/preview/index.js +38 -12
- package/dist/lib/yaml-config.d.ts +2 -1
- package/dist/lib/yaml-config.js +5 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { confirm, input, select } from '@inquirer/prompts';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
5
5
|
import { API_ENDPOINTS } from '../../lib/constants.js';
|
|
6
|
-
import { loadTemplate,
|
|
6
|
+
import { loadTemplate, getTemplateFilename, saveTemplate, saveYaml, } from '../../lib/yaml-config.js';
|
|
7
7
|
export default class Edit extends BaseCommand {
|
|
8
8
|
static args = {
|
|
9
9
|
id: Args.string({
|
|
@@ -31,7 +31,7 @@ export default class Edit extends BaseCommand {
|
|
|
31
31
|
this.error(`Email '${args.id}' not found in mailmodo.yaml.`);
|
|
32
32
|
}
|
|
33
33
|
const email = yamlConfig.emails[emailIndex];
|
|
34
|
-
const templateFilename =
|
|
34
|
+
const templateFilename = getTemplateFilename(email.id, email.style, yamlConfig.project?.emailStyle);
|
|
35
35
|
const ctx = {
|
|
36
36
|
email,
|
|
37
37
|
emailIndex,
|
|
@@ -43,7 +43,7 @@ export default class Edit extends BaseCommand {
|
|
|
43
43
|
json: flags.json ?? false,
|
|
44
44
|
yes: flags.yes ?? false,
|
|
45
45
|
};
|
|
46
|
-
const initialChange = flags.change
|
|
46
|
+
const initialChange = flags.change?.trim() || (await this.askChangeDescription());
|
|
47
47
|
await this.runEditStep(ctx, initialChange, editFlags);
|
|
48
48
|
}
|
|
49
49
|
async runEditStep(ctx, changeDescription, flags) {
|
|
@@ -79,11 +79,16 @@ export default class Emails extends BaseCommand {
|
|
|
79
79
|
this.log(`\n Opening ${template}...\n`);
|
|
80
80
|
const editor = process.env.VISUAL || process.env.EDITOR;
|
|
81
81
|
if (editor) {
|
|
82
|
-
const
|
|
83
|
-
await new Promise((resolve) => {
|
|
84
|
-
child
|
|
82
|
+
const [cmd, ...editorArgs] = editor.trim().split(/\s+/);
|
|
83
|
+
const launched = await new Promise((resolve) => {
|
|
84
|
+
const child = spawn(cmd, [...editorArgs, templatePath], {
|
|
85
|
+
stdio: 'inherit',
|
|
86
|
+
});
|
|
87
|
+
child.on('error', () => resolve(false));
|
|
88
|
+
child.on('close', () => resolve(true));
|
|
85
89
|
});
|
|
86
|
-
|
|
90
|
+
if (launched)
|
|
91
|
+
return;
|
|
87
92
|
}
|
|
88
93
|
if (await this.trySpawnEditor('code', templatePath))
|
|
89
94
|
return;
|
|
@@ -23,8 +23,12 @@ export default class Preview extends BaseCommand {
|
|
|
23
23
|
*/
|
|
24
24
|
private sendTestEmail;
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
|
|
26
|
+
* Probes ports starting at startPort and returns the first one not in use.
|
|
27
|
+
*/
|
|
28
|
+
private findAvailablePort;
|
|
29
|
+
/**
|
|
30
|
+
* Starts a local HTTP server to serve the rendered email template,
|
|
31
|
+
* then opens the user's default browser to view it.
|
|
28
32
|
*/
|
|
29
33
|
private startPreviewServer;
|
|
30
34
|
}
|
|
@@ -4,7 +4,7 @@ import chalk from 'chalk';
|
|
|
4
4
|
import open from 'open';
|
|
5
5
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
6
6
|
import { API_ENDPOINTS, PREVIEW_PORT } from '../../lib/constants.js';
|
|
7
|
-
import { loadTemplate,
|
|
7
|
+
import { loadTemplate, getEmailStyle, getTemplateFilename, } from '../../lib/yaml-config.js';
|
|
8
8
|
/* eslint-disable camelcase */
|
|
9
9
|
const SAMPLE_DATA = Object.freeze({
|
|
10
10
|
app_url: 'https://yourapp.com',
|
|
@@ -84,7 +84,8 @@ export default class Preview extends BaseCommand {
|
|
|
84
84
|
app_url: yamlConfig.project?.url || 'https://yourapp.com', // eslint-disable-line camelcase
|
|
85
85
|
product_name: yamlConfig.project?.name || 'YourApp', // eslint-disable-line camelcase
|
|
86
86
|
};
|
|
87
|
-
const
|
|
87
|
+
const effectiveStyle = getEmailStyle(email.style, yamlConfig.project?.emailStyle);
|
|
88
|
+
const templateHtml = await loadTemplate(getTemplateFilename(email.id, email.style, yamlConfig.project?.emailStyle));
|
|
88
89
|
if (flags.send) {
|
|
89
90
|
const rendered = templateHtml
|
|
90
91
|
? renderTemplate(templateHtml, sampleData)
|
|
@@ -100,7 +101,10 @@ export default class Preview extends BaseCommand {
|
|
|
100
101
|
await this.renderText(email, templateHtml, sampleData, flags.json);
|
|
101
102
|
return;
|
|
102
103
|
}
|
|
103
|
-
await this.startPreviewServer(email, templateHtml, sampleData,
|
|
104
|
+
await this.startPreviewServer(email, templateHtml, sampleData, {
|
|
105
|
+
effectiveStyle,
|
|
106
|
+
jsonOutput: flags.json,
|
|
107
|
+
});
|
|
104
108
|
}
|
|
105
109
|
/**
|
|
106
110
|
* Renders a plain text version of the email to stdout. Used by AI agents
|
|
@@ -154,10 +158,28 @@ export default class Preview extends BaseCommand {
|
|
|
154
158
|
this.log('');
|
|
155
159
|
}
|
|
156
160
|
/**
|
|
157
|
-
*
|
|
158
|
-
* template, then opens the user's default browser to view it.
|
|
161
|
+
* Probes ports starting at startPort and returns the first one not in use.
|
|
159
162
|
*/
|
|
160
|
-
async
|
|
163
|
+
async findAvailablePort(startPort, endPort = startPort + 10) {
|
|
164
|
+
if (startPort > endPort) {
|
|
165
|
+
throw new Error(`No available port found starting from port ${endPort - 10}`);
|
|
166
|
+
}
|
|
167
|
+
const available = await new Promise((resolve) => {
|
|
168
|
+
const probe = createServer();
|
|
169
|
+
probe.once('error', () => resolve(false));
|
|
170
|
+
probe.once('listening', () => probe.close(() => resolve(true)));
|
|
171
|
+
probe.listen(startPort);
|
|
172
|
+
});
|
|
173
|
+
return available
|
|
174
|
+
? startPort
|
|
175
|
+
: this.findAvailablePort(startPort + 1, endPort);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Starts a local HTTP server to serve the rendered email template,
|
|
179
|
+
* then opens the user's default browser to view it.
|
|
180
|
+
*/
|
|
181
|
+
async startPreviewServer(email, templateHtml, sampleData, opts) {
|
|
182
|
+
const { effectiveStyle, jsonOutput } = opts;
|
|
161
183
|
const rendered = templateHtml
|
|
162
184
|
? renderTemplate(templateHtml, sampleData)
|
|
163
185
|
: '<p>No template found.</p>';
|
|
@@ -183,7 +205,7 @@ export default class Preview extends BaseCommand {
|
|
|
183
205
|
<body>
|
|
184
206
|
<div class="preview-bar">
|
|
185
207
|
<h3>Mailmodo Preview — ${email.id}</h3>
|
|
186
|
-
<span>Style: ${
|
|
208
|
+
<span>Style: ${effectiveStyle} · Press Ctrl+C in terminal to stop</span>
|
|
187
209
|
</div>
|
|
188
210
|
<div class="email-frame">
|
|
189
211
|
<div class="email-header">
|
|
@@ -194,11 +216,15 @@ export default class Preview extends BaseCommand {
|
|
|
194
216
|
</div>
|
|
195
217
|
</body>
|
|
196
218
|
</html>`;
|
|
219
|
+
const port = await this.findAvailablePort(PREVIEW_PORT);
|
|
220
|
+
if (!jsonOutput && port !== PREVIEW_PORT) {
|
|
221
|
+
this.log(`\n ${chalk.yellow('!')} Port ${PREVIEW_PORT} is already in use. Opening preview on port ${chalk.cyan(String(port))}.`);
|
|
222
|
+
}
|
|
197
223
|
if (jsonOutput) {
|
|
198
224
|
this.log(JSON.stringify({
|
|
199
225
|
id: email.id,
|
|
200
|
-
style:
|
|
201
|
-
url: `http://localhost:${
|
|
226
|
+
style: effectiveStyle,
|
|
227
|
+
url: `http://localhost:${port}`,
|
|
202
228
|
}, null, 2));
|
|
203
229
|
}
|
|
204
230
|
const server = createServer((_req, res) => {
|
|
@@ -206,11 +232,11 @@ export default class Preview extends BaseCommand {
|
|
|
206
232
|
res.end(wrapperHtml);
|
|
207
233
|
});
|
|
208
234
|
await new Promise((resolve) => {
|
|
209
|
-
server.listen(
|
|
235
|
+
server.listen(port, () => resolve());
|
|
210
236
|
});
|
|
211
|
-
const url = `http://localhost:${
|
|
237
|
+
const url = `http://localhost:${port}`;
|
|
212
238
|
if (!jsonOutput) {
|
|
213
|
-
this.log(`\n Style: ${chalk.cyan(
|
|
239
|
+
this.log(`\n Style: ${chalk.cyan(effectiveStyle)}`);
|
|
214
240
|
this.log(` Preview server at ${chalk.cyan(url)}`);
|
|
215
241
|
this.log(` Opening in browser...\n`);
|
|
216
242
|
this.log(` ${chalk.dim('Press Ctrl+C to stop the preview server.')}\n`);
|
|
@@ -68,4 +68,5 @@ export declare function saveTemplate(filename: string, html: string, cwd?: strin
|
|
|
68
68
|
* or null if the file doesn't exist or can't be read.
|
|
69
69
|
*/
|
|
70
70
|
export declare function loadTemplate(filename: string, cwd?: string): Promise<null | string>;
|
|
71
|
-
export declare function
|
|
71
|
+
export declare function getEmailStyle(emailStyle?: 'branded' | 'plain', projectStyle?: 'branded' | 'plain'): 'branded' | 'plain';
|
|
72
|
+
export declare function getTemplateFilename(emailId: string, emailStyle?: 'branded' | 'plain', projectStyle?: 'branded' | 'plain'): string;
|
package/dist/lib/yaml-config.js
CHANGED
|
@@ -72,7 +72,10 @@ export async function loadTemplate(filename, cwd) {
|
|
|
72
72
|
return null;
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
export function
|
|
76
|
-
|
|
75
|
+
export function getEmailStyle(emailStyle, projectStyle) {
|
|
76
|
+
return emailStyle ?? projectStyle ?? 'branded';
|
|
77
|
+
}
|
|
78
|
+
export function getTemplateFilename(emailId, emailStyle, projectStyle) {
|
|
79
|
+
const style = getEmailStyle(emailStyle, projectStyle);
|
|
77
80
|
return style === 'plain' ? `${emailId}_plain.html` : `${emailId}.html`;
|
|
78
81
|
}
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED