@mailmodo/cli 0.0.21-beta.pr23.39 → 0.0.21-beta.pr24.38
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/deploy/index.d.ts +2 -28
- package/dist/commands/deploy/index.js +50 -39
- package/dist/commands/domain/index.js +1 -1
- package/dist/commands/edit/index.js +2 -0
- package/dist/commands/init/index.js +6 -1
- package/dist/commands/logs/index.d.ts +0 -2
- package/dist/commands/logs/index.js +2 -18
- package/dist/commands/settings/index.d.ts +0 -2
- package/dist/commands/settings/index.js +57 -81
- package/dist/lib/yaml-config.d.ts +5 -0
- package/oclif.manifest.json +33 -49
- package/package.json +1 -1
|
@@ -6,14 +6,9 @@ export default class Deploy extends BaseCommand {
|
|
|
6
6
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
7
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
8
|
};
|
|
9
|
-
/**
|
|
10
|
-
* Fetches current DNS verification status for the deploy flow.
|
|
11
|
-
*
|
|
12
|
-
* @param jsonOutput - When true, spinner uses stderr for stdout-safe JSON runs.
|
|
13
|
-
* @param domain - Sending domain to check. If empty, request will fail and trigger setup flow.
|
|
14
|
-
*/
|
|
15
9
|
private fetchDomainVerifyForDeploy;
|
|
16
10
|
run(): Promise<void>;
|
|
11
|
+
private validateSequence;
|
|
17
12
|
private buildDeployPayload;
|
|
18
13
|
private mapEmailToPayload;
|
|
19
14
|
private buildBrandSection;
|
|
@@ -22,32 +17,11 @@ export default class Deploy extends BaseCommand {
|
|
|
22
17
|
private buildProjectPayload;
|
|
23
18
|
private confirmDeploy;
|
|
24
19
|
private ensureDomainReady;
|
|
25
|
-
/**
|
|
26
|
-
* Lists emails about to be deployed (skipped when `--json` is set).
|
|
27
|
-
*
|
|
28
|
-
* @param yamlConfig - Loaded project YAML.
|
|
29
|
-
* @param jsonOutput - When true, skip human-readable output.
|
|
30
|
-
*/
|
|
31
20
|
private logPreDeploySummary;
|
|
32
|
-
|
|
33
|
-
* Prints the post-deploy success message and SDK install snippet for interactive runs.
|
|
34
|
-
*/
|
|
21
|
+
private logDiff;
|
|
35
22
|
private logDeploySuccessInstructions;
|
|
36
|
-
/**
|
|
37
|
-
* Interactive domain setup flow. Collects domain, sender email, and business
|
|
38
|
-
* address from the user, then calls the API to get DNS records to configure.
|
|
39
|
-
* Polls for verification when the user indicates they've added the records.
|
|
40
|
-
*
|
|
41
|
-
* @returns {Promise<boolean>} true if domain was verified, false if skipped.
|
|
42
|
-
*/
|
|
43
23
|
private runDomainSetup;
|
|
44
24
|
private collectDomainInputs;
|
|
45
25
|
private showDnsRecords;
|
|
46
|
-
/**
|
|
47
|
-
* Calls the domain verification API endpoint and reports pass/fail
|
|
48
|
-
* status for each DNS record (DKIM, DMARC, Return-Path).
|
|
49
|
-
*
|
|
50
|
-
* @returns {Promise<boolean>} true if all records pass.
|
|
51
|
-
*/
|
|
52
26
|
private verifyDomain;
|
|
53
27
|
}
|
|
@@ -12,12 +12,6 @@ export default class Deploy extends BaseCommand {
|
|
|
12
12
|
static flags = {
|
|
13
13
|
...BaseCommand.baseFlags,
|
|
14
14
|
};
|
|
15
|
-
/**
|
|
16
|
-
* Fetches current DNS verification status for the deploy flow.
|
|
17
|
-
*
|
|
18
|
-
* @param jsonOutput - When true, spinner uses stderr for stdout-safe JSON runs.
|
|
19
|
-
* @param domain - Sending domain to check. If empty, request will fail and trigger setup flow.
|
|
20
|
-
*/
|
|
21
15
|
fetchDomainVerifyForDeploy(jsonOutput, domain) {
|
|
22
16
|
return this.withApiSpinner({ json: jsonOutput, text: ' Checking domain verification...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY, {
|
|
23
17
|
domain: domain || '',
|
|
@@ -30,11 +24,12 @@ export default class Deploy extends BaseCommand {
|
|
|
30
24
|
const domainReady = await this.ensureDomainReady(yamlConfig, flags);
|
|
31
25
|
if (!domainReady)
|
|
32
26
|
return;
|
|
33
|
-
this.
|
|
27
|
+
const payload = await this.buildDeployPayload(yamlConfig);
|
|
28
|
+
const validateResult = await this.validateSequence(payload, flags);
|
|
29
|
+
this.logPreDeploySummary(yamlConfig, validateResult, flags.json);
|
|
34
30
|
const confirmed = await this.confirmDeploy(yamlConfig, flags);
|
|
35
31
|
if (!confirmed)
|
|
36
32
|
return;
|
|
37
|
-
const payload = await this.buildDeployPayload(yamlConfig);
|
|
38
33
|
const response = await this.withApiSpinner({ json: flags.json, text: ' Deploying email sequences...' }, () => this.apiClient.post(API_ENDPOINTS.SEQUENCES_DEPLOY, payload));
|
|
39
34
|
if (!response.ok) {
|
|
40
35
|
this.handleApiError(response);
|
|
@@ -51,6 +46,19 @@ export default class Deploy extends BaseCommand {
|
|
|
51
46
|
}
|
|
52
47
|
this.logDeploySuccessInstructions(response.data.sdkSnippet);
|
|
53
48
|
}
|
|
49
|
+
async validateSequence(payload, flags) {
|
|
50
|
+
const response = await this.withApiSpinner({ json: flags.json, text: ' Validating sequence...' }, () => this.apiClient.post(API_ENDPOINTS.SEQUENCES_VALIDATE, payload));
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
if (response.data.error === 'senderDomainNotFound') {
|
|
53
|
+
this.error(`Sending domain not registered. Run: ${chalk.cyan('mailmodo domain')}`);
|
|
54
|
+
}
|
|
55
|
+
if (response.data.error === 'senderDomainNotVerified') {
|
|
56
|
+
this.error(`Sending domain not verified. Run: ${chalk.cyan('mailmodo domain --verify')}`);
|
|
57
|
+
}
|
|
58
|
+
this.handleApiError(response);
|
|
59
|
+
}
|
|
60
|
+
return response.data;
|
|
61
|
+
}
|
|
54
62
|
async buildDeployPayload(yamlConfig) {
|
|
55
63
|
const emailsWithHtml = await Promise.all(yamlConfig.emails.map(async (email) => {
|
|
56
64
|
const html = (await loadTemplate(`${email.id}.html`)) || '';
|
|
@@ -64,7 +72,7 @@ export default class Deploy extends BaseCommand {
|
|
|
64
72
|
mapEmailToPayload(email) {
|
|
65
73
|
return {
|
|
66
74
|
condition: email.condition || null,
|
|
67
|
-
ctaText: '',
|
|
75
|
+
ctaText: email.ctaText || '',
|
|
68
76
|
delay: typeof email.delay === 'string'
|
|
69
77
|
? Number.parseInt(email.delay, 10) || 0
|
|
70
78
|
: email.delay,
|
|
@@ -86,11 +94,11 @@ export default class Deploy extends BaseCommand {
|
|
|
86
94
|
buildProductSection(project) {
|
|
87
95
|
return {
|
|
88
96
|
businessType: project?.type || '',
|
|
89
|
-
description: '',
|
|
90
|
-
pricingModel: '',
|
|
97
|
+
description: project?.description || '',
|
|
98
|
+
pricingModel: project?.pricingModel || '',
|
|
91
99
|
productName: project?.name || '',
|
|
92
|
-
saasModel: '',
|
|
93
|
-
targetUser: '',
|
|
100
|
+
saasModel: project?.saasModel || '',
|
|
101
|
+
targetUser: project?.targetUser || '',
|
|
94
102
|
url: project?.url || '',
|
|
95
103
|
};
|
|
96
104
|
}
|
|
@@ -150,25 +158,41 @@ export default class Deploy extends BaseCommand {
|
|
|
150
158
|
}
|
|
151
159
|
return this.runDomainSetup(yamlConfig, flags);
|
|
152
160
|
}
|
|
153
|
-
|
|
154
|
-
* Lists emails about to be deployed (skipped when `--json` is set).
|
|
155
|
-
*
|
|
156
|
-
* @param yamlConfig - Loaded project YAML.
|
|
157
|
-
* @param jsonOutput - When true, skip human-readable output.
|
|
158
|
-
*/
|
|
159
|
-
logPreDeploySummary(yamlConfig, jsonOutput) {
|
|
161
|
+
logPreDeploySummary(yamlConfig, validateResult, jsonOutput) {
|
|
160
162
|
if (jsonOutput)
|
|
161
163
|
return;
|
|
162
164
|
this.log(`\n ${chalk.green('✓')} Domain: ${yamlConfig.project?.domain || 'verified'}\n`);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
if (!validateResult.existingDeployment || !validateResult.diff) {
|
|
166
|
+
this.log(` Deploying:`);
|
|
167
|
+
for (const email of yamlConfig.emails) {
|
|
168
|
+
this.log(` ${chalk.green('+')} ${email.id.padEnd(24)} ${email.trigger}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
this.logDiff(validateResult.diff);
|
|
166
173
|
}
|
|
167
174
|
this.log('');
|
|
168
175
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
176
|
+
logDiff(diff) {
|
|
177
|
+
if (!diff.hasChanges) {
|
|
178
|
+
this.log(` No changes from last deployment.`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this.log(` Changes vs. last deployment:`);
|
|
182
|
+
for (const email of diff.added) {
|
|
183
|
+
this.log(` ${chalk.green('+')} ${email.id.padEnd(24)} ${email.trigger || ''}`);
|
|
184
|
+
}
|
|
185
|
+
for (const email of diff.removed) {
|
|
186
|
+
this.log(` ${chalk.red('-')} ${email.id.padEnd(24)} ${email.trigger || ''}`);
|
|
187
|
+
}
|
|
188
|
+
for (const email of diff.modified) {
|
|
189
|
+
const fields = email.changedFields?.join(', ') || '';
|
|
190
|
+
this.log(` ${chalk.yellow('~')} ${email.id.padEnd(24)} ${fields}`);
|
|
191
|
+
}
|
|
192
|
+
if (diff.unchanged.length > 0) {
|
|
193
|
+
this.log(` ${chalk.dim(`∙ ${diff.unchanged.length} unchanged`)}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
172
196
|
logDeploySuccessInstructions(sdkSnippet) {
|
|
173
197
|
this.log(` ${chalk.green('Deployed.')} Emails are live.\n`);
|
|
174
198
|
this.log(` ${'─'.repeat(53)}`);
|
|
@@ -185,13 +209,6 @@ export default class Deploy extends BaseCommand {
|
|
|
185
209
|
this.log(` Full SDK docs: ${chalk.cyan('mailmodo.com/docs/sdk')}\n`);
|
|
186
210
|
this.log(` ${'─'.repeat(53)}\n`);
|
|
187
211
|
}
|
|
188
|
-
/**
|
|
189
|
-
* Interactive domain setup flow. Collects domain, sender email, and business
|
|
190
|
-
* address from the user, then calls the API to get DNS records to configure.
|
|
191
|
-
* Polls for verification when the user indicates they've added the records.
|
|
192
|
-
*
|
|
193
|
-
* @returns {Promise<boolean>} true if domain was verified, false if skipped.
|
|
194
|
-
*/
|
|
195
212
|
async runDomainSetup(yamlConfig, flags) {
|
|
196
213
|
const { address, domain, senderEmail } = await this.collectDomainInputs(yamlConfig, flags);
|
|
197
214
|
const domainResponse = await this.withApiSpinner({ json: flags.json, text: ' Configuring domain...' }, () => this.apiClient.post(API_ENDPOINTS.DOMAIN, {
|
|
@@ -260,12 +277,6 @@ export default class Deploy extends BaseCommand {
|
|
|
260
277
|
this.log(` DNS changes take 5–30 minutes to propagate.`);
|
|
261
278
|
this.log(` Full guide: ${chalk.cyan(DNS_GUIDE_URL)}\n`);
|
|
262
279
|
}
|
|
263
|
-
/**
|
|
264
|
-
* Calls the domain verification API endpoint and reports pass/fail
|
|
265
|
-
* status for each DNS record (DKIM, DMARC, Return-Path).
|
|
266
|
-
*
|
|
267
|
-
* @returns {Promise<boolean>} true if all records pass.
|
|
268
|
-
*/
|
|
269
280
|
async verifyDomain(jsonOutput, domain) {
|
|
270
281
|
const verify = await this.withApiSpinner({ json: jsonOutput, text: ' Checking DNS...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY, {
|
|
271
282
|
domain,
|
|
@@ -179,7 +179,7 @@ export default class Domain extends BaseCommand {
|
|
|
179
179
|
this.log(` Spam rate: ${data.spamRate ?? 'N/A'}%\n`);
|
|
180
180
|
}
|
|
181
181
|
recordLabel(index) {
|
|
182
|
-
const labels = ['
|
|
182
|
+
const labels = ['DKIM', 'DMARC', 'Return Path'];
|
|
183
183
|
return labels[index] || `Record ${index + 1}`;
|
|
184
184
|
}
|
|
185
185
|
}
|
|
@@ -194,6 +194,8 @@ export default class Edit extends BaseCommand {
|
|
|
194
194
|
email.subject = updated.subject;
|
|
195
195
|
if (updated.previewText)
|
|
196
196
|
email.previewText = updated.previewText;
|
|
197
|
+
if (updated.ctaText)
|
|
198
|
+
email.ctaText = updated.ctaText;
|
|
197
199
|
const updatedYaml = {
|
|
198
200
|
...yamlConfig,
|
|
199
201
|
emails: [...yamlConfig.emails],
|
|
@@ -128,6 +128,7 @@ export default class Init extends BaseCommand {
|
|
|
128
128
|
...(generated?.previewText
|
|
129
129
|
? { previewText: generated.previewText }
|
|
130
130
|
: {}),
|
|
131
|
+
...(generated?.ctaText ? { ctaText: generated.ctaText } : {}),
|
|
131
132
|
goal: rec.goal,
|
|
132
133
|
};
|
|
133
134
|
});
|
|
@@ -135,14 +136,18 @@ export default class Init extends BaseCommand {
|
|
|
135
136
|
emails: emailConfigs,
|
|
136
137
|
project: {
|
|
137
138
|
brandColor: analysisPayload.brand?.color || DEFAULT_BRAND_COLOR,
|
|
139
|
+
description: analysisPayload.description,
|
|
138
140
|
emailStyle: 'branded',
|
|
139
141
|
fromEmail: '',
|
|
140
142
|
fromName: `Team ${analysisPayload.productName}`,
|
|
141
143
|
logoUrl: analysisPayload.brand?.logoUrl || '',
|
|
142
144
|
monthlyCap: DEFAULT_MONTHLY_CAP,
|
|
143
145
|
name: analysisPayload.productName,
|
|
146
|
+
pricingModel: analysisPayload.pricingModel,
|
|
144
147
|
replyTo: '',
|
|
145
|
-
|
|
148
|
+
saasModel: analysisPayload.saasModel,
|
|
149
|
+
targetUser: analysisPayload.targetUser,
|
|
150
|
+
type: analysisPayload.businessType,
|
|
146
151
|
url: productUrl,
|
|
147
152
|
webhookUrl: '',
|
|
148
153
|
},
|
|
@@ -5,8 +5,6 @@ export default class Logs extends BaseCommand {
|
|
|
5
5
|
static flags: {
|
|
6
6
|
email: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
7
|
failed: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
-
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
-
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
8
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
9
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
10
|
};
|
|
@@ -17,22 +17,11 @@ export default class Logs extends BaseCommand {
|
|
|
17
17
|
default: false,
|
|
18
18
|
description: 'Show only failed/bounced events',
|
|
19
19
|
}),
|
|
20
|
-
limit: Flags.integer({
|
|
21
|
-
default: 50,
|
|
22
|
-
description: 'Entries per page (max 200)',
|
|
23
|
-
}),
|
|
24
|
-
page: Flags.integer({
|
|
25
|
-
default: 1,
|
|
26
|
-
description: 'Page number',
|
|
27
|
-
}),
|
|
28
20
|
};
|
|
29
21
|
async run() {
|
|
30
22
|
const { flags } = await this.parse(Logs);
|
|
31
23
|
await this.ensureAuth();
|
|
32
|
-
const params = {
|
|
33
|
-
limit: String(flags.limit),
|
|
34
|
-
page: String(flags.page),
|
|
35
|
-
};
|
|
24
|
+
const params = {};
|
|
36
25
|
if (flags.email)
|
|
37
26
|
params.email = flags.email;
|
|
38
27
|
if (flags.failed)
|
|
@@ -41,7 +30,7 @@ export default class Logs extends BaseCommand {
|
|
|
41
30
|
if (!response.ok) {
|
|
42
31
|
this.handleApiError(response);
|
|
43
32
|
}
|
|
44
|
-
const { entries
|
|
33
|
+
const { entries } = response.data;
|
|
45
34
|
if (flags.json) {
|
|
46
35
|
this.log(JSON.stringify(response.data, null, 2));
|
|
47
36
|
return;
|
|
@@ -60,11 +49,6 @@ export default class Logs extends BaseCommand {
|
|
|
60
49
|
this.log(` ${' '.repeat(52)}${chalk.dim(`(reason: ${entry.reason})`)}`);
|
|
61
50
|
}
|
|
62
51
|
}
|
|
63
|
-
const totalPages = Math.ceil(total / limit);
|
|
64
|
-
this.log(`\n Page ${page} of ${totalPages} · ${total} total entries`);
|
|
65
|
-
if (page < totalPages) {
|
|
66
|
-
this.log(` ${chalk.dim(`Next: --page ${page + 1}`)}`);
|
|
67
|
-
}
|
|
68
52
|
}
|
|
69
53
|
else {
|
|
70
54
|
this.log(` ${chalk.dim('No log entries found.')}`);
|
|
@@ -8,8 +8,6 @@ export default class Settings extends BaseCommand {
|
|
|
8
8
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
9
|
};
|
|
10
10
|
run(): Promise<void>;
|
|
11
|
-
private applySetFlag;
|
|
12
|
-
private displaySettingsGroup;
|
|
13
11
|
/**
|
|
14
12
|
* Prompts the user to pick a setting key to edit and dispatches
|
|
15
13
|
* to the appropriate handler for that key.
|
|
@@ -43,85 +43,73 @@ export default class Settings extends BaseCommand {
|
|
|
43
43
|
async run() {
|
|
44
44
|
const { flags } = await this.parse(Settings);
|
|
45
45
|
const yamlConfig = await this.ensureYaml();
|
|
46
|
+
const { project } = yamlConfig;
|
|
46
47
|
if (flags.set) {
|
|
47
|
-
|
|
48
|
+
const eqIndex = flags.set.indexOf('=');
|
|
49
|
+
if (eqIndex === -1) {
|
|
50
|
+
this.error('Invalid format. Use --set key=value (e.g., --set brand_color=#0F3460)');
|
|
51
|
+
}
|
|
52
|
+
const key = flags.set.slice(0, eqIndex).trim();
|
|
53
|
+
const propKey = settingKeyToProp(key);
|
|
54
|
+
const value = flags.set.slice(eqIndex + 1).trim();
|
|
55
|
+
if (!(propKey in project)) {
|
|
56
|
+
this.error(`Unknown setting: ${key}`);
|
|
57
|
+
}
|
|
58
|
+
project[propKey] =
|
|
59
|
+
propKey === 'monthlyCap' ? Number(value) : value;
|
|
60
|
+
await saveYaml(yamlConfig);
|
|
61
|
+
if (flags.json) {
|
|
62
|
+
this.log(JSON.stringify({ [propKey]: value, status: 'updated' }, null, 2));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
this.log(`\n ${chalk.green('✓')} ${key} updated to ${chalk.cyan(value)}`);
|
|
66
|
+
this.log(` Run ${chalk.cyan("'mailmodo deploy'")} to apply.\n`);
|
|
48
67
|
return;
|
|
49
68
|
}
|
|
50
69
|
if (flags.json) {
|
|
51
|
-
this.log(JSON.stringify({ settings:
|
|
70
|
+
this.log(JSON.stringify({ settings: project }, null, 2));
|
|
52
71
|
return;
|
|
53
72
|
}
|
|
54
|
-
const domainVerified = await this.fetchDomainVerified(
|
|
55
|
-
this.log(`\n Current settings for ${chalk.bold(
|
|
73
|
+
const domainVerified = await this.fetchDomainVerified(project.domain);
|
|
74
|
+
this.log(`\n Current settings for ${chalk.bold(project.name || 'project')}:\n`);
|
|
56
75
|
for (const [group, keys] of Object.entries(SETTINGS_GROUPS)) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.error('Invalid format. Use --set key=value (e.g., --set brand_color=#0F3460)');
|
|
68
|
-
}
|
|
69
|
-
const key = setFlag.slice(0, eqIndex).trim();
|
|
70
|
-
const propKey = settingKeyToProp(key);
|
|
71
|
-
const value = setFlag.slice(eqIndex + 1).trim();
|
|
72
|
-
if (!(propKey in project) && key !== 'logo_file') {
|
|
73
|
-
this.error(`Unknown setting: ${key}`);
|
|
74
|
-
}
|
|
75
|
-
project[propKey] =
|
|
76
|
-
propKey === 'monthlyCap' ? Number(value) : value;
|
|
77
|
-
await saveYaml(yamlConfig);
|
|
78
|
-
if (isJson) {
|
|
79
|
-
this.log(JSON.stringify({ [propKey]: value, status: 'updated' }, null, 2));
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
this.log(`\n ${chalk.green('✓')} ${key} updated to ${chalk.cyan(value)}`);
|
|
83
|
-
this.log(` Run ${chalk.cyan("'mailmodo deploy'")} to apply.\n`);
|
|
84
|
-
}
|
|
85
|
-
displaySettingsGroup(group, keys, project, domainVerified) {
|
|
86
|
-
const availableKeys = keys.filter((key) => {
|
|
87
|
-
if (group === 'brand' && key === 'logo_file')
|
|
88
|
-
return true;
|
|
89
|
-
return settingKeyToProp(key) in project;
|
|
90
|
-
});
|
|
91
|
-
const groupTitle = ` ${chalk.bold(group.charAt(0).toUpperCase() + group.slice(1))}`;
|
|
92
|
-
if (availableKeys.length === 0) {
|
|
93
|
-
const hint = SETUP_HINTS[settingKeyToProp(keys[0])];
|
|
94
|
-
if (hint) {
|
|
95
|
-
this.log(groupTitle);
|
|
96
|
-
this.log(` ${'─'.repeat(49)}`);
|
|
97
|
-
this.log(` ${chalk.dim(`Run ${hint} to configure.`)}`);
|
|
98
|
-
this.log('');
|
|
76
|
+
const availableKeys = keys.filter((key) => settingKeyToProp(key) in project);
|
|
77
|
+
if (availableKeys.length === 0) {
|
|
78
|
+
const hint = SETUP_HINTS[settingKeyToProp(keys[0])];
|
|
79
|
+
if (hint) {
|
|
80
|
+
this.log(` ${chalk.bold(group.charAt(0).toUpperCase() + group.slice(1))}`);
|
|
81
|
+
this.log(` ${'─'.repeat(49)}`);
|
|
82
|
+
this.log(` ${chalk.dim(`Run ${hint} to configure.`)}`);
|
|
83
|
+
this.log('');
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
99
86
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
87
|
+
this.log(` ${chalk.bold(group.charAt(0).toUpperCase() + group.slice(1))}`);
|
|
88
|
+
this.log(` ${'─'.repeat(49)}`);
|
|
89
|
+
for (const key of availableKeys) {
|
|
90
|
+
const propKey = settingKeyToProp(key);
|
|
91
|
+
const value = project[propKey];
|
|
92
|
+
let displayValue = value ? String(value) : chalk.dim('(not set)');
|
|
93
|
+
if (key === 'domain' && value && domainVerified === true) {
|
|
94
|
+
displayValue += ` ${chalk.green('✓ verified')}`;
|
|
95
|
+
}
|
|
96
|
+
else if (key === 'domain' && value && domainVerified === false) {
|
|
97
|
+
displayValue += ` ${chalk.red('✗ not verified')}`;
|
|
98
|
+
}
|
|
99
|
+
this.log(` ${key.padEnd(16)} ${displayValue}`);
|
|
110
100
|
}
|
|
111
|
-
|
|
112
|
-
|
|
101
|
+
const missingKeys = keys.filter((key) => !(settingKeyToProp(key) in project));
|
|
102
|
+
for (const key of missingKeys) {
|
|
103
|
+
const hint = SETUP_HINTS[settingKeyToProp(key)];
|
|
104
|
+
if (hint) {
|
|
105
|
+
this.log(` ${key.padEnd(16)} ${chalk.dim(`(run ${hint} to set up)`)}`);
|
|
106
|
+
}
|
|
113
107
|
}
|
|
114
|
-
this.log(
|
|
108
|
+
this.log('');
|
|
115
109
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
for (const key of missingKeys) {
|
|
119
|
-
const hint = SETUP_HINTS[settingKeyToProp(key)];
|
|
120
|
-
if (hint) {
|
|
121
|
-
this.log(` ${key.padEnd(16)} ${chalk.dim(`(run ${hint} to set up)`)}`);
|
|
122
|
-
}
|
|
110
|
+
if (!flags.yes) {
|
|
111
|
+
await this.promptEditSetting(yamlConfig);
|
|
123
112
|
}
|
|
124
|
-
this.log('');
|
|
125
113
|
}
|
|
126
114
|
/**
|
|
127
115
|
* Prompts the user to pick a setting key to edit and dispatches
|
|
@@ -137,10 +125,6 @@ export default class Settings extends BaseCommand {
|
|
|
137
125
|
return;
|
|
138
126
|
const editPropKey = settingKeyToProp(editKey);
|
|
139
127
|
if (!(editPropKey in project)) {
|
|
140
|
-
if (editKey === 'logo_file') {
|
|
141
|
-
await this.handleLogoUpload(yamlConfig);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
128
|
const hint = SETUP_HINTS[editPropKey];
|
|
145
129
|
if (hint) {
|
|
146
130
|
this.log(`\n ${editKey} is not configured yet. Run ${chalk.cyan(hint)} to set it up.\n`);
|
|
@@ -249,7 +233,7 @@ export default class Settings extends BaseCommand {
|
|
|
249
233
|
this.log(` Help: ${chalk.cyan(DNS_GUIDE_URL)}\n`);
|
|
250
234
|
}
|
|
251
235
|
recordLabel(index) {
|
|
252
|
-
const labels = ['
|
|
236
|
+
const labels = ['DKIM', 'DMARC', 'Return Path'];
|
|
253
237
|
return labels[index] || `Record ${index + 1}`;
|
|
254
238
|
}
|
|
255
239
|
/**
|
|
@@ -268,16 +252,8 @@ export default class Settings extends BaseCommand {
|
|
|
268
252
|
}
|
|
269
253
|
await this.ensureAuth();
|
|
270
254
|
const fileBuffer = await readFile(resolvedPath);
|
|
271
|
-
const ext = resolvedPath.split('.').pop()?.toLowerCase();
|
|
272
|
-
const mimeTypes = {
|
|
273
|
-
png: 'image/png',
|
|
274
|
-
jpg: 'image/jpeg',
|
|
275
|
-
jpeg: 'image/jpeg',
|
|
276
|
-
svg: 'image/svg+xml',
|
|
277
|
-
};
|
|
278
|
-
const mimeType = mimeTypes[ext ?? ''] ?? 'application/octet-stream';
|
|
279
255
|
const formData = new FormData();
|
|
280
|
-
formData.append('logo', new Blob([new Uint8Array(fileBuffer)]
|
|
256
|
+
formData.append('logo', new Blob([new Uint8Array(fileBuffer)]), logoPath.split(/[/\\]/).pop() || 'logo.png');
|
|
281
257
|
const response = await this.apiClient.postFormData(API_ENDPOINTS.ASSETS_LOGO, formData);
|
|
282
258
|
if (!response.ok) {
|
|
283
259
|
this.handleApiError(response);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export interface EmailConfig {
|
|
2
2
|
condition?: string;
|
|
3
|
+
ctaText?: string;
|
|
3
4
|
delay: number | string;
|
|
4
5
|
goal?: string;
|
|
5
6
|
id: string;
|
|
@@ -12,6 +13,7 @@ export interface EmailConfig {
|
|
|
12
13
|
export interface ProjectConfig {
|
|
13
14
|
address?: string;
|
|
14
15
|
brandColor?: string;
|
|
16
|
+
description?: string;
|
|
15
17
|
domain?: string;
|
|
16
18
|
emailStyle?: 'branded' | 'plain';
|
|
17
19
|
fromEmail?: string;
|
|
@@ -20,7 +22,10 @@ export interface ProjectConfig {
|
|
|
20
22
|
logoUrl?: string;
|
|
21
23
|
monthlyCap?: number;
|
|
22
24
|
name?: string;
|
|
25
|
+
pricingModel?: string;
|
|
23
26
|
replyTo?: string;
|
|
27
|
+
saasModel?: string;
|
|
28
|
+
targetUser?: string;
|
|
24
29
|
type?: string;
|
|
25
30
|
url?: string;
|
|
26
31
|
webhookUrl?: string;
|
package/oclif.manifest.json
CHANGED
|
@@ -342,13 +342,12 @@
|
|
|
342
342
|
"index.js"
|
|
343
343
|
]
|
|
344
344
|
},
|
|
345
|
-
"
|
|
345
|
+
"logout": {
|
|
346
346
|
"aliases": [],
|
|
347
347
|
"args": {},
|
|
348
|
-
"description": "
|
|
348
|
+
"description": "Sign out by removing saved credentials from this machine",
|
|
349
349
|
"examples": [
|
|
350
|
-
"<%= config.bin %>
|
|
351
|
-
"MAILMODO_API_KEY=mm_live_xxx <%= config.bin %> login"
|
|
350
|
+
"<%= config.bin %> logout"
|
|
352
351
|
],
|
|
353
352
|
"flags": {
|
|
354
353
|
"json": {
|
|
@@ -367,7 +366,7 @@
|
|
|
367
366
|
},
|
|
368
367
|
"hasDynamicHelp": false,
|
|
369
368
|
"hiddenAliases": [],
|
|
370
|
-
"id": "
|
|
369
|
+
"id": "logout",
|
|
371
370
|
"pluginAlias": "@mailmodo/cli",
|
|
372
371
|
"pluginName": "@mailmodo/cli",
|
|
373
372
|
"pluginType": "core",
|
|
@@ -377,16 +376,19 @@
|
|
|
377
376
|
"relativePath": [
|
|
378
377
|
"dist",
|
|
379
378
|
"commands",
|
|
380
|
-
"
|
|
379
|
+
"logout",
|
|
381
380
|
"index.js"
|
|
382
381
|
]
|
|
383
382
|
},
|
|
384
|
-
"
|
|
383
|
+
"logs": {
|
|
385
384
|
"aliases": [],
|
|
386
385
|
"args": {},
|
|
387
|
-
"description": "
|
|
386
|
+
"description": "View email send logs and delivery events",
|
|
388
387
|
"examples": [
|
|
389
|
-
"<%= config.bin %>
|
|
388
|
+
"<%= config.bin %> logs",
|
|
389
|
+
"<%= config.bin %> logs --email sarah@example.com",
|
|
390
|
+
"<%= config.bin %> logs --failed",
|
|
391
|
+
"<%= config.bin %> logs --json"
|
|
390
392
|
],
|
|
391
393
|
"flags": {
|
|
392
394
|
"json": {
|
|
@@ -401,11 +403,24 @@
|
|
|
401
403
|
"name": "yes",
|
|
402
404
|
"allowNo": false,
|
|
403
405
|
"type": "boolean"
|
|
406
|
+
},
|
|
407
|
+
"email": {
|
|
408
|
+
"description": "Filter logs by contact email",
|
|
409
|
+
"name": "email",
|
|
410
|
+
"hasDynamicHelp": false,
|
|
411
|
+
"multiple": false,
|
|
412
|
+
"type": "option"
|
|
413
|
+
},
|
|
414
|
+
"failed": {
|
|
415
|
+
"description": "Show only failed/bounced events",
|
|
416
|
+
"name": "failed",
|
|
417
|
+
"allowNo": false,
|
|
418
|
+
"type": "boolean"
|
|
404
419
|
}
|
|
405
420
|
},
|
|
406
421
|
"hasDynamicHelp": false,
|
|
407
422
|
"hiddenAliases": [],
|
|
408
|
-
"id": "
|
|
423
|
+
"id": "logs",
|
|
409
424
|
"pluginAlias": "@mailmodo/cli",
|
|
410
425
|
"pluginName": "@mailmodo/cli",
|
|
411
426
|
"pluginType": "core",
|
|
@@ -415,19 +430,17 @@
|
|
|
415
430
|
"relativePath": [
|
|
416
431
|
"dist",
|
|
417
432
|
"commands",
|
|
418
|
-
"
|
|
433
|
+
"logs",
|
|
419
434
|
"index.js"
|
|
420
435
|
]
|
|
421
436
|
},
|
|
422
|
-
"
|
|
437
|
+
"login": {
|
|
423
438
|
"aliases": [],
|
|
424
439
|
"args": {},
|
|
425
|
-
"description": "
|
|
440
|
+
"description": "Authenticate with Mailmodo using your API key",
|
|
426
441
|
"examples": [
|
|
427
|
-
"<%= config.bin %>
|
|
428
|
-
"<%= config.bin %>
|
|
429
|
-
"<%= config.bin %> logs --failed",
|
|
430
|
-
"<%= config.bin %> logs --json"
|
|
442
|
+
"<%= config.bin %> login",
|
|
443
|
+
"MAILMODO_API_KEY=mm_live_xxx <%= config.bin %> login"
|
|
431
444
|
],
|
|
432
445
|
"flags": {
|
|
433
446
|
"json": {
|
|
@@ -442,40 +455,11 @@
|
|
|
442
455
|
"name": "yes",
|
|
443
456
|
"allowNo": false,
|
|
444
457
|
"type": "boolean"
|
|
445
|
-
},
|
|
446
|
-
"email": {
|
|
447
|
-
"description": "Filter logs by contact email",
|
|
448
|
-
"name": "email",
|
|
449
|
-
"hasDynamicHelp": false,
|
|
450
|
-
"multiple": false,
|
|
451
|
-
"type": "option"
|
|
452
|
-
},
|
|
453
|
-
"failed": {
|
|
454
|
-
"description": "Show only failed/bounced events",
|
|
455
|
-
"name": "failed",
|
|
456
|
-
"allowNo": false,
|
|
457
|
-
"type": "boolean"
|
|
458
|
-
},
|
|
459
|
-
"limit": {
|
|
460
|
-
"description": "Entries per page (max 200)",
|
|
461
|
-
"name": "limit",
|
|
462
|
-
"default": 50,
|
|
463
|
-
"hasDynamicHelp": false,
|
|
464
|
-
"multiple": false,
|
|
465
|
-
"type": "option"
|
|
466
|
-
},
|
|
467
|
-
"page": {
|
|
468
|
-
"description": "Page number",
|
|
469
|
-
"name": "page",
|
|
470
|
-
"default": 1,
|
|
471
|
-
"hasDynamicHelp": false,
|
|
472
|
-
"multiple": false,
|
|
473
|
-
"type": "option"
|
|
474
458
|
}
|
|
475
459
|
},
|
|
476
460
|
"hasDynamicHelp": false,
|
|
477
461
|
"hiddenAliases": [],
|
|
478
|
-
"id": "
|
|
462
|
+
"id": "login",
|
|
479
463
|
"pluginAlias": "@mailmodo/cli",
|
|
480
464
|
"pluginName": "@mailmodo/cli",
|
|
481
465
|
"pluginType": "core",
|
|
@@ -485,7 +469,7 @@
|
|
|
485
469
|
"relativePath": [
|
|
486
470
|
"dist",
|
|
487
471
|
"commands",
|
|
488
|
-
"
|
|
472
|
+
"login",
|
|
489
473
|
"index.js"
|
|
490
474
|
]
|
|
491
475
|
},
|
|
@@ -634,5 +618,5 @@
|
|
|
634
618
|
]
|
|
635
619
|
}
|
|
636
620
|
},
|
|
637
|
-
"version": "0.0.21-beta.
|
|
621
|
+
"version": "0.0.21-beta.pr24.38"
|
|
638
622
|
}
|
package/package.json
CHANGED