@mailmodo/cli 0.0.55-beta.pr57.93 → 0.0.55
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/billing/index.d.ts +11 -1
- package/dist/commands/billing/index.js +184 -28
- package/dist/commands/contacts/index.d.ts +19 -1
- package/dist/commands/contacts/index.js +114 -21
- package/dist/commands/deploy/index.js +4 -4
- package/dist/commands/deployments/index.d.ts +4 -1
- package/dist/commands/deployments/index.js +52 -11
- package/dist/commands/domain/index.d.ts +14 -1
- package/dist/commands/domain/index.js +100 -19
- package/dist/commands/edit/index.d.ts +20 -2
- package/dist/commands/edit/index.js +258 -30
- package/dist/commands/emails/index.d.ts +2 -1
- package/dist/commands/emails/index.js +91 -26
- package/dist/commands/init/index.d.ts +3 -1
- package/dist/commands/init/index.js +199 -41
- package/dist/commands/login/index.d.ts +0 -2
- package/dist/commands/login/index.js +76 -32
- package/dist/commands/logs/index.d.ts +8 -1
- package/dist/commands/logs/index.js +55 -12
- package/dist/commands/preview/index.d.ts +19 -1
- package/dist/commands/preview/index.js +212 -30
- package/dist/commands/sdk/index.d.ts +3 -1
- package/dist/commands/sdk/index.js +46 -14
- package/dist/commands/settings/index.d.ts +22 -1
- package/dist/commands/settings/index.js +246 -34
- package/dist/commands/status/index.d.ts +0 -1
- package/dist/commands/status/index.js +39 -13
- package/dist/lib/{commands/deploy → deploy}/domain-setup.d.ts +1 -1
- package/dist/lib/{commands/deploy → deploy}/domain-setup.js +2 -2
- package/dist/lib/{commands/deploy → deploy}/output.d.ts +1 -1
- package/dist/lib/{commands/deploy → deploy}/output.js +2 -2
- package/dist/lib/{commands/deploy → deploy}/payload.d.ts +1 -1
- package/dist/lib/{commands/deploy → deploy}/payload.js +2 -2
- package/dist/lib/{commands/deploy → deploy}/sequence-status.js +2 -2
- package/dist/lib/{commands/deploy → deploy}/types.d.ts +4 -4
- package/dist/lib/templates/missing-templates.d.ts +1 -1
- package/dist/lib/templates/missing-templates.js +1 -1
- package/oclif.manifest.json +54 -54
- package/package.json +1 -1
- package/dist/lib/commands/billing/checkout-status.d.ts +0 -3
- package/dist/lib/commands/billing/checkout-status.js +0 -63
- package/dist/lib/commands/billing/format.d.ts +0 -7
- package/dist/lib/commands/billing/format.js +0 -63
- package/dist/lib/commands/billing/purchase-cap.d.ts +0 -7
- package/dist/lib/commands/billing/purchase-cap.js +0 -57
- package/dist/lib/commands/billing/types.d.ts +0 -72
- package/dist/lib/commands/contacts/actions.d.ts +0 -3
- package/dist/lib/commands/contacts/actions.js +0 -49
- package/dist/lib/commands/contacts/export-delete.d.ts +0 -9
- package/dist/lib/commands/contacts/export-delete.js +0 -51
- package/dist/lib/commands/contacts/types.d.ts +0 -35
- package/dist/lib/commands/contacts/types.js +0 -1
- package/dist/lib/commands/deploy/types.js +0 -1
- package/dist/lib/commands/deployments/output.d.ts +0 -2
- package/dist/lib/commands/deployments/output.js +0 -68
- package/dist/lib/commands/deployments/types.d.ts +0 -24
- package/dist/lib/commands/deployments/types.js +0 -1
- package/dist/lib/commands/domain/setup.d.ts +0 -8
- package/dist/lib/commands/domain/setup.js +0 -53
- package/dist/lib/commands/domain/types.d.ts +0 -56
- package/dist/lib/commands/domain/types.js +0 -1
- package/dist/lib/commands/domain/verify.d.ts +0 -5
- package/dist/lib/commands/domain/verify.js +0 -50
- package/dist/lib/commands/edit/diff.d.ts +0 -7
- package/dist/lib/commands/edit/diff.js +0 -65
- package/dist/lib/commands/edit/display.d.ts +0 -5
- package/dist/lib/commands/edit/display.js +0 -53
- package/dist/lib/commands/edit/flow.d.ts +0 -8
- package/dist/lib/commands/edit/flow.js +0 -70
- package/dist/lib/commands/edit/persist.d.ts +0 -5
- package/dist/lib/commands/edit/persist.js +0 -65
- package/dist/lib/commands/edit/types.d.ts +0 -37
- package/dist/lib/commands/edit/types.js +0 -1
- package/dist/lib/commands/emails/editor.d.ts +0 -2
- package/dist/lib/commands/emails/editor.js +0 -43
- package/dist/lib/commands/emails/output.d.ts +0 -4
- package/dist/lib/commands/emails/output.js +0 -36
- package/dist/lib/commands/emails/types.d.ts +0 -3
- package/dist/lib/commands/emails/types.js +0 -1
- package/dist/lib/commands/init/analysis.d.ts +0 -3
- package/dist/lib/commands/init/analysis.js +0 -69
- package/dist/lib/commands/init/output.d.ts +0 -12
- package/dist/lib/commands/init/output.js +0 -39
- package/dist/lib/commands/init/payload.d.ts +0 -8
- package/dist/lib/commands/init/payload.js +0 -78
- package/dist/lib/commands/init/types.d.ts +0 -57
- package/dist/lib/commands/init/types.js +0 -1
- package/dist/lib/commands/login/output.d.ts +0 -8
- package/dist/lib/commands/login/output.js +0 -53
- package/dist/lib/commands/login/types.d.ts +0 -19
- package/dist/lib/commands/login/types.js +0 -1
- package/dist/lib/commands/logs/output.d.ts +0 -2
- package/dist/lib/commands/logs/output.js +0 -52
- package/dist/lib/commands/logs/types.d.ts +0 -23
- package/dist/lib/commands/logs/types.js +0 -1
- package/dist/lib/commands/preview/actions.d.ts +0 -11
- package/dist/lib/commands/preview/actions.js +0 -43
- package/dist/lib/commands/preview/render.d.ts +0 -3
- package/dist/lib/commands/preview/render.js +0 -30
- package/dist/lib/commands/preview/server.d.ts +0 -8
- package/dist/lib/commands/preview/server.js +0 -63
- package/dist/lib/commands/preview/types.d.ts +0 -19
- package/dist/lib/commands/preview/types.js +0 -1
- package/dist/lib/commands/preview/wrapper-html.d.ts +0 -2
- package/dist/lib/commands/preview/wrapper-html.js +0 -35
- package/dist/lib/commands/sdk/output.d.ts +0 -2
- package/dist/lib/commands/sdk/output.js +0 -42
- package/dist/lib/commands/sdk/types.d.ts +0 -21
- package/dist/lib/commands/sdk/types.js +0 -1
- package/dist/lib/commands/settings/actions.d.ts +0 -10
- package/dist/lib/commands/settings/actions.js +0 -56
- package/dist/lib/commands/settings/display.d.ts +0 -15
- package/dist/lib/commands/settings/display.js +0 -69
- package/dist/lib/commands/settings/logo-domain.d.ts +0 -3
- package/dist/lib/commands/settings/logo-domain.js +0 -47
- package/dist/lib/commands/settings/prompt.d.ts +0 -2
- package/dist/lib/commands/settings/prompt.js +0 -82
- package/dist/lib/commands/settings/types.d.ts +0 -65
- package/dist/lib/commands/settings/types.js +0 -1
- package/dist/lib/commands/status/output.d.ts +0 -2
- package/dist/lib/commands/status/output.js +0 -49
- package/dist/lib/commands/status/types.d.ts +0 -28
- package/dist/lib/commands/status/types.js +0 -1
- /package/dist/lib/{commands/deploy → deploy}/sequence-status.d.ts +0 -0
- /package/dist/lib/{commands/billing → deploy}/types.js +0 -0
|
@@ -1,10 +1,57 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import open from 'open';
|
|
2
5
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
3
|
-
import {
|
|
6
|
+
import { API_ENDPOINTS, PREVIEW_PORT } from '../../lib/constants.js';
|
|
7
|
+
import { INFO } from '../../lib/messages.js';
|
|
4
8
|
import { loadTemplate, getEmailStyle, getTemplateFilename, } from '../../lib/yaml-config.js';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
import { handleMissingTemplates } from '../../lib/templates/missing-templates.js';
|
|
10
|
+
/* eslint-disable camelcase */
|
|
11
|
+
const SAMPLE_DATA = Object.freeze({
|
|
12
|
+
app_url: 'https://yourapp.com',
|
|
13
|
+
cta_url: 'https://yourapp.com/action',
|
|
14
|
+
first_name: 'Sarah',
|
|
15
|
+
product_name: 'YourApp',
|
|
16
|
+
unsubscribe_url: '#',
|
|
17
|
+
});
|
|
18
|
+
/* eslint-enable camelcase */
|
|
19
|
+
/**
|
|
20
|
+
* Replaces all {{variable}} template placeholders in the HTML string
|
|
21
|
+
* with corresponding values from the sample data map.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} html - Raw HTML template with {{variable}} placeholders.
|
|
24
|
+
* @param {Record<string, string>} data - Key-value map of template variable replacements.
|
|
25
|
+
* @returns {string} HTML with all recognized placeholders replaced.
|
|
26
|
+
*/
|
|
27
|
+
function renderTemplate(html, data) {
|
|
28
|
+
return html.replaceAll(/\{\{(\w+)\}\}/g, (match, key) => data[key] || match);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Strips HTML tags and decodes common HTML entities to produce
|
|
32
|
+
* a readable plain text representation of an email.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} html - The HTML content to convert.
|
|
35
|
+
* @returns {string} Plain text with tags removed and entities decoded.
|
|
36
|
+
*/
|
|
37
|
+
function htmlToText(html) {
|
|
38
|
+
return html
|
|
39
|
+
.replaceAll(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
|
40
|
+
.replaceAll(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
|
41
|
+
.replaceAll(/<br\s*\/?>/gi, '\n')
|
|
42
|
+
.replaceAll(/<\/p>/gi, '\n\n')
|
|
43
|
+
.replaceAll(/<\/div>/gi, '\n')
|
|
44
|
+
.replaceAll(/<\/tr>/gi, '\n')
|
|
45
|
+
.replaceAll(/<\/li>/gi, '\n')
|
|
46
|
+
.replaceAll(/<[^>]+>/g, '')
|
|
47
|
+
.replaceAll(' ', ' ')
|
|
48
|
+
.replaceAll('&', '&')
|
|
49
|
+
.replaceAll('<', '<')
|
|
50
|
+
.replaceAll('>', '>')
|
|
51
|
+
.replaceAll('"', '"')
|
|
52
|
+
.replaceAll(/\n{3,}/g, '\n\n')
|
|
53
|
+
.trim();
|
|
54
|
+
}
|
|
8
55
|
export default class Preview extends BaseCommand {
|
|
9
56
|
static args = {
|
|
10
57
|
id: Args.string({ description: 'Email template ID to preview' }),
|
|
@@ -34,29 +81,34 @@ export default class Preview extends BaseCommand {
|
|
|
34
81
|
if (!email) {
|
|
35
82
|
this.error(`Template '${templateId}' not found in mailmodo.yaml.`);
|
|
36
83
|
}
|
|
37
|
-
/* eslint-disable camelcase */
|
|
38
84
|
const sampleData = {
|
|
39
85
|
...SAMPLE_DATA,
|
|
40
|
-
app_url: yamlConfig.project?.url || 'https://yourapp.com',
|
|
41
|
-
product_name: yamlConfig.project?.name || 'YourApp',
|
|
86
|
+
app_url: yamlConfig.project?.url || 'https://yourapp.com', // eslint-disable-line camelcase
|
|
87
|
+
product_name: yamlConfig.project?.name || 'YourApp', // eslint-disable-line camelcase
|
|
42
88
|
};
|
|
43
|
-
/* eslint-enable camelcase */
|
|
44
89
|
const effectiveStyle = getEmailStyle(email.style, yamlConfig.project?.emailStyle);
|
|
45
90
|
const templateFilename = getTemplateFilename(email.id, email.style, yamlConfig.project?.emailStyle);
|
|
46
91
|
let templateHtml = await loadTemplate(templateFilename);
|
|
47
92
|
if (!templateHtml) {
|
|
48
93
|
await this.ensureAuth();
|
|
49
|
-
const
|
|
94
|
+
const regenCtx = {
|
|
95
|
+
error: (msg) => this.error(msg),
|
|
96
|
+
exit: (code) => this.exit(code),
|
|
97
|
+
log: (msg) => this.log(msg),
|
|
98
|
+
onApiError: (r) => this.handleApiError(r),
|
|
99
|
+
post: (path, body) => this.apiClient.post(path, body),
|
|
100
|
+
spinner: (text, json, work) => this.withApiSpinner({ json, text }, work),
|
|
101
|
+
syncYaml: () => this.syncYamlToServer(),
|
|
102
|
+
};
|
|
103
|
+
const regenerated = await handleMissingTemplates(regenCtx, yamlConfig, [email.id], { json: flags.json, yes: flags.yes });
|
|
50
104
|
if (!regenerated)
|
|
51
105
|
return;
|
|
52
106
|
templateHtml = await loadTemplate(templateFilename);
|
|
53
107
|
if (!templateHtml)
|
|
54
108
|
this.error('Template regeneration failed.');
|
|
55
109
|
}
|
|
56
|
-
const ctx = this.makeCtx();
|
|
57
110
|
if (flags.send) {
|
|
58
|
-
await this.
|
|
59
|
-
await sendTestEmail(ctx, email, renderTemplate(templateHtml, sampleData), {
|
|
111
|
+
await this.sendTestEmail(email, renderTemplate(templateHtml, sampleData), {
|
|
60
112
|
domain: yamlConfig.project?.domain,
|
|
61
113
|
jsonOutput: flags.json,
|
|
62
114
|
toAddress: flags.send,
|
|
@@ -64,29 +116,159 @@ export default class Preview extends BaseCommand {
|
|
|
64
116
|
return;
|
|
65
117
|
}
|
|
66
118
|
if (flags.text) {
|
|
67
|
-
await renderText(
|
|
68
|
-
jsonOutput: flags.json,
|
|
69
|
-
sampleData,
|
|
70
|
-
templateHtml,
|
|
71
|
-
});
|
|
119
|
+
await this.renderText(email, templateHtml, sampleData, flags.json);
|
|
72
120
|
return;
|
|
73
121
|
}
|
|
74
|
-
await startPreviewServer(
|
|
122
|
+
await this.startPreviewServer(email, templateHtml, sampleData, {
|
|
75
123
|
effectiveStyle,
|
|
76
124
|
jsonOutput: flags.json,
|
|
77
|
-
rendered: renderTemplate(templateHtml, sampleData),
|
|
78
|
-
sampleData,
|
|
79
125
|
});
|
|
80
126
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Renders a plain text version of the email to stdout. Used by AI agents
|
|
129
|
+
* and CI pipelines that cannot open a browser.
|
|
130
|
+
*/
|
|
131
|
+
async renderText(email, templateHtml, sampleData, jsonOutput) {
|
|
132
|
+
const plainText = htmlToText(renderTemplate(templateHtml, sampleData));
|
|
133
|
+
if (jsonOutput) {
|
|
134
|
+
this.log(JSON.stringify({
|
|
135
|
+
body: plainText,
|
|
136
|
+
id: email.id,
|
|
137
|
+
previewText: email.previewText,
|
|
138
|
+
subject: email.subject,
|
|
139
|
+
}, null, 2));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.log(`\n ${chalk.bold('SUBJECT:')} ${email.subject}`);
|
|
143
|
+
if (email.previewText) {
|
|
144
|
+
this.log(` ${chalk.bold('PREVIEW:')} ${email.previewText}`);
|
|
145
|
+
}
|
|
146
|
+
this.log(`\n${plainText}\n`);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Calls the API to send a test email to the specified address.
|
|
150
|
+
* Before domain verification, tests send via the mailmodo.com domain.
|
|
151
|
+
*/
|
|
152
|
+
async sendTestEmail(email, html, opts) {
|
|
153
|
+
const { domain, jsonOutput, toAddress } = opts;
|
|
154
|
+
await this.ensureAuth();
|
|
155
|
+
const response = await this.withApiSpinner({ json: jsonOutput, text: ' Sending test email...' }, () => this.apiClient.post(`${API_ENDPOINTS.PREVIEW}/send`, {
|
|
156
|
+
domain,
|
|
157
|
+
html,
|
|
158
|
+
subject: email.subject,
|
|
159
|
+
to: toAddress,
|
|
160
|
+
}));
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
this.handleApiError(response);
|
|
163
|
+
}
|
|
164
|
+
const { note, sentTo, sentVia, status } = response.data;
|
|
165
|
+
if (jsonOutput) {
|
|
166
|
+
this.log(JSON.stringify({ note, sentTo, sentVia, status }, null, 2));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.log(`\n ${chalk.green('✓')} Test email sent to ${chalk.cyan(sentTo)} via ${chalk.cyan(sentVia)}.`);
|
|
170
|
+
if (note) {
|
|
171
|
+
this.log(` ${chalk.dim(note)}`);
|
|
172
|
+
}
|
|
173
|
+
this.log('');
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Probes ports starting at startPort and returns the first one not in use.
|
|
177
|
+
*/
|
|
178
|
+
async findAvailablePort(startPort, endPort = startPort + 10) {
|
|
179
|
+
if (startPort > endPort) {
|
|
180
|
+
throw new Error(`No available port found starting from port ${endPort - 10}`);
|
|
181
|
+
}
|
|
182
|
+
const available = await new Promise((resolve) => {
|
|
183
|
+
const probe = createServer();
|
|
184
|
+
probe.once('error', () => resolve(false));
|
|
185
|
+
probe.once('listening', () => probe.close(() => resolve(true)));
|
|
186
|
+
probe.listen(startPort);
|
|
187
|
+
});
|
|
188
|
+
return available
|
|
189
|
+
? startPort
|
|
190
|
+
: this.findAvailablePort(startPort + 1, endPort);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Starts a local HTTP server to serve the rendered email template,
|
|
194
|
+
* then opens the user's default browser to view it.
|
|
195
|
+
*/
|
|
196
|
+
async startPreviewServer(email, templateHtml, sampleData, opts) {
|
|
197
|
+
const { effectiveStyle, jsonOutput } = opts;
|
|
198
|
+
const rendered = renderTemplate(templateHtml, sampleData);
|
|
199
|
+
const wrapperHtml = `<!DOCTYPE html>
|
|
200
|
+
<html>
|
|
201
|
+
<head>
|
|
202
|
+
<meta charset="utf-8">
|
|
203
|
+
<title>Preview: ${email.subject}</title>
|
|
204
|
+
<style>
|
|
205
|
+
body { margin: 0; padding: 2rem; background: #f5f5f5; font-family: system-ui; }
|
|
206
|
+
.preview-bar { background: #1a1a2e; color: #fff; padding: 0.75rem 1.5rem; border-radius: 0.5rem;
|
|
207
|
+
margin-bottom: 1.5rem; display: flex; justify-content: space-between; align-items: center; }
|
|
208
|
+
.preview-bar h3 { margin: 0; font-size: 0.875rem; }
|
|
209
|
+
.preview-bar span { font-size: 0.75rem; opacity: 0.7; }
|
|
210
|
+
.email-frame { background: #fff; max-width: 40rem; margin: 0 auto; border-radius: 0.5rem;
|
|
211
|
+
box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.1); overflow: hidden; }
|
|
212
|
+
.email-header { padding: 1rem 1.5rem; border-bottom: 1px solid #eee; }
|
|
213
|
+
.email-header .subject { font-weight: 600; font-size: 1rem; }
|
|
214
|
+
.email-header .meta { font-size: 0.75rem; color: #666; margin-top: 0.25rem; }
|
|
215
|
+
.email-body { padding: 1.5rem; }
|
|
216
|
+
</style>
|
|
217
|
+
</head>
|
|
218
|
+
<body>
|
|
219
|
+
<div class="preview-bar">
|
|
220
|
+
<h3>Mailmodo Preview — ${email.id}</h3>
|
|
221
|
+
<span>Style: ${effectiveStyle} · Press Ctrl+C in terminal to stop</span>
|
|
222
|
+
</div>
|
|
223
|
+
<div class="email-frame">
|
|
224
|
+
<div class="email-header">
|
|
225
|
+
<div class="subject">${email.subject}</div>
|
|
226
|
+
<div class="meta">To: ${sampleData.first_name} · From: ${sampleData.product_name}</div>
|
|
227
|
+
</div>
|
|
228
|
+
<div class="email-body">${rendered}</div>
|
|
229
|
+
</div>
|
|
230
|
+
</body>
|
|
231
|
+
</html>`;
|
|
232
|
+
const port = await this.findAvailablePort(PREVIEW_PORT);
|
|
233
|
+
if (!jsonOutput && port !== PREVIEW_PORT) {
|
|
234
|
+
this.log(`\n ${chalk.yellow('!')} Port ${PREVIEW_PORT} is already in use. Opening preview on port ${chalk.cyan(String(port))}.`);
|
|
235
|
+
}
|
|
236
|
+
if (jsonOutput) {
|
|
237
|
+
this.log(JSON.stringify({
|
|
238
|
+
id: email.id,
|
|
239
|
+
style: effectiveStyle,
|
|
240
|
+
url: `http://localhost:${port}`,
|
|
241
|
+
}, null, 2));
|
|
242
|
+
}
|
|
243
|
+
const server = createServer((_req, res) => {
|
|
244
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
245
|
+
res.end(wrapperHtml);
|
|
246
|
+
});
|
|
247
|
+
await new Promise((resolve) => {
|
|
248
|
+
server.listen(port, () => resolve());
|
|
249
|
+
});
|
|
250
|
+
const url = `http://localhost:${port}`;
|
|
251
|
+
if (!jsonOutput) {
|
|
252
|
+
this.log(`\n Style: ${chalk.cyan(effectiveStyle)}`);
|
|
253
|
+
this.log(` Preview server at ${chalk.cyan(url)}`);
|
|
254
|
+
this.log(` ${INFO.BROWSER_OPENING}\n`);
|
|
255
|
+
this.log(` ${chalk.dim('Press Ctrl+C to stop the preview server.')}\n`);
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
await open(url);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
if (!jsonOutput) {
|
|
262
|
+
this.log(` ${INFO.BROWSER_OPEN_FAILED}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
await new Promise((resolve) => {
|
|
266
|
+
const shutdown = () => {
|
|
267
|
+
server.closeAllConnections?.();
|
|
268
|
+
server.close(() => resolve());
|
|
269
|
+
};
|
|
270
|
+
process.once('SIGINT', shutdown);
|
|
271
|
+
process.once('SIGTERM', shutdown);
|
|
272
|
+
});
|
|
91
273
|
}
|
|
92
274
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
2
3
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
3
|
-
import { API_ENDPOINTS } from '../../lib/constants.js';
|
|
4
|
-
import {
|
|
4
|
+
import { API_ENDPOINTS, SDK_IMPORT_SNIPPET, SDK_INSTALL_COMMAND, } from '../../lib/constants.js';
|
|
5
|
+
import { SEPARATOR } from '../../lib/messages.js';
|
|
5
6
|
export default class Sdk extends BaseCommand {
|
|
6
7
|
static description = 'Show the SDK track() / identify() reference for deployed sequences';
|
|
7
8
|
static examples = [
|
|
@@ -18,25 +19,56 @@ export default class Sdk extends BaseCommand {
|
|
|
18
19
|
async run() {
|
|
19
20
|
const { flags } = await this.parse(Sdk);
|
|
20
21
|
await this.ensureAuth();
|
|
21
|
-
const ctx = this.makeCtx();
|
|
22
22
|
const params = flags['sequence-id']
|
|
23
23
|
? { sequenceId: flags['sequence-id'] }
|
|
24
24
|
: undefined;
|
|
25
|
-
const response = await
|
|
26
|
-
if (!response.ok)
|
|
27
|
-
|
|
25
|
+
const response = await this.withApiSpinner({ json: flags.json, text: ' Loading SDK reference...' }, () => this.apiClient.get(API_ENDPOINTS.SEQUENCES_SDK, params));
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
this.handleApiError(response);
|
|
28
|
+
}
|
|
28
29
|
if (flags.json) {
|
|
29
30
|
this.log(JSON.stringify(response.data, null, 2));
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
+
this.renderSnippets(response.data);
|
|
34
|
+
}
|
|
35
|
+
renderSnippets(data) {
|
|
36
|
+
const snippets = data.sdkSnippets ?? [];
|
|
37
|
+
if (snippets.length === 0) {
|
|
38
|
+
this.log(`\n ${chalk.dim('No active deployed sequences.')}`);
|
|
39
|
+
this.log(` Run ${chalk.cyan('mailmodo deploy')} to deploy one.\n`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
this.log(`\n ${chalk.bold(String(snippets.length))} active ${snippets.length === 1 ? 'sequence' : 'sequences'}:\n`);
|
|
43
|
+
this.log(` ${SEPARATOR}`);
|
|
44
|
+
this.log(` ${chalk.bold('SDK EVENT REFERENCE')}`);
|
|
45
|
+
this.log(` ${SEPARATOR}\n`);
|
|
46
|
+
this.log(` ${chalk.cyan(SDK_INSTALL_COMMAND)}\n`);
|
|
47
|
+
this.log(` ${chalk.dim(SDK_IMPORT_SNIPPET)}\n`);
|
|
48
|
+
for (const [index, snippet] of snippets.entries()) {
|
|
49
|
+
this.renderSequenceBlock(snippet);
|
|
50
|
+
if (index < snippets.length - 1)
|
|
51
|
+
this.log('');
|
|
52
|
+
}
|
|
53
|
+
this.log(` ${SEPARATOR}\n`);
|
|
33
54
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
renderSequenceBlock(snippet) {
|
|
56
|
+
const productName = snippet.productName || 'Unnamed sequence';
|
|
57
|
+
this.log(` ${chalk.bold(productName)} ${chalk.dim(`(${snippet.sequenceId})`)}`);
|
|
58
|
+
const trackCalls = [...new Set(snippet.sdkSnippet?.trackCalls ?? [])];
|
|
59
|
+
const identifyCalls = [...new Set(snippet.sdkSnippet?.identifyCalls ?? [])];
|
|
60
|
+
this.renderCallBlock('// track() calls', trackCalls);
|
|
61
|
+
this.renderCallBlock('// identify() calls', identifyCalls);
|
|
62
|
+
if (trackCalls.length === 0 && identifyCalls.length === 0) {
|
|
63
|
+
this.log(` ${chalk.dim('No track() or identify() calls available.')}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
renderCallBlock(label, calls) {
|
|
67
|
+
if (calls.length === 0)
|
|
68
|
+
return;
|
|
69
|
+
this.log(` ${chalk.dim(label)}`);
|
|
70
|
+
for (const call of calls) {
|
|
71
|
+
this.log(` ${chalk.dim(call)}`);
|
|
72
|
+
}
|
|
41
73
|
}
|
|
42
74
|
}
|
|
@@ -8,5 +8,26 @@ export default class Settings extends BaseCommand {
|
|
|
8
8
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
9
|
};
|
|
10
10
|
run(): Promise<void>;
|
|
11
|
-
private
|
|
11
|
+
private applySetFlag;
|
|
12
|
+
private applyMonthlyCapChange;
|
|
13
|
+
private displaySettingsGroup;
|
|
14
|
+
/**
|
|
15
|
+
* Prompts the user to pick a setting key to edit and dispatches
|
|
16
|
+
* to the appropriate handler for that key.
|
|
17
|
+
*/
|
|
18
|
+
private promptEditSetting;
|
|
19
|
+
/**
|
|
20
|
+
* Fetches the domain verification status from the API.
|
|
21
|
+
* Returns true/false for verified/unverified, or null if unavailable.
|
|
22
|
+
*/
|
|
23
|
+
private fetchDomainVerified;
|
|
24
|
+
private handleDomainChange;
|
|
25
|
+
/**
|
|
26
|
+
* Handles the logo file upload flow: validates the local file exists,
|
|
27
|
+
* reads it, uploads to Mailmodo CDN via API, and updates both logoFile
|
|
28
|
+
* and logoUrl in the project config.
|
|
29
|
+
*
|
|
30
|
+
* @param {import('../../lib/yaml-config.js').MailmodoYaml} yamlConfig - The full YAML config to update and save.
|
|
31
|
+
*/
|
|
32
|
+
private handleLogoUpload;
|
|
12
33
|
}
|