@mailmodo/cli 0.0.28 → 0.0.29-beta.pr31.49
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.js +8 -6
- package/dist/commands/domain/index.js +30 -9
- package/dist/commands/init/index.d.ts +1 -0
- package/dist/commands/init/index.js +21 -2
- package/dist/commands/settings/index.js +4 -2
- package/dist/lib/api-client.d.ts +2 -0
- package/dist/lib/api-client.js +20 -2
- package/dist/lib/base-command.js +5 -5
- package/oclif.manifest.json +21 -21
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { confirm, input } from '@inquirer/prompts';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
4
|
-
import { API_ENDPOINTS, DEFAULT_BRAND_COLOR, DEFAULT_MONTHLY_CAP,
|
|
4
|
+
import { API_ENDPOINTS, DEFAULT_BRAND_COLOR, DEFAULT_MONTHLY_CAP, } from '../../lib/constants.js';
|
|
5
5
|
import { loadTemplate, saveYaml, } from '../../lib/yaml-config.js';
|
|
6
6
|
export default class Deploy extends BaseCommand {
|
|
7
7
|
static description = 'Deploy email sequences and verify sending domain';
|
|
@@ -232,7 +232,7 @@ export default class Deploy extends BaseCommand {
|
|
|
232
232
|
yamlConfig.project.fromEmail = senderEmail;
|
|
233
233
|
yamlConfig.project.address = address;
|
|
234
234
|
await saveYaml(yamlConfig);
|
|
235
|
-
this.showDnsRecords(domainResponse.data?.dnsRecords || [], flags.json);
|
|
235
|
+
this.showDnsRecords(domainResponse.data?.dnsRecords || [], flags.json, domainResponse.data?.dnsGuideUrl);
|
|
236
236
|
if (flags.yes) {
|
|
237
237
|
return this.verifyDomain(flags.json, domain);
|
|
238
238
|
}
|
|
@@ -273,7 +273,7 @@ export default class Deploy extends BaseCommand {
|
|
|
273
273
|
});
|
|
274
274
|
return { address, domain, senderEmail };
|
|
275
275
|
}
|
|
276
|
-
showDnsRecords(dnsRecords, jsonOutput) {
|
|
276
|
+
showDnsRecords(dnsRecords, jsonOutput, dnsGuideUrl) {
|
|
277
277
|
if (jsonOutput)
|
|
278
278
|
return;
|
|
279
279
|
this.log(`\n Add these ${dnsRecords.length} DNS records to your domain provider:\n`);
|
|
@@ -284,7 +284,8 @@ export default class Deploy extends BaseCommand {
|
|
|
284
284
|
this.log(` Value: ${record.value}\n`);
|
|
285
285
|
}
|
|
286
286
|
this.log(` DNS changes take 5–30 minutes to propagate.`);
|
|
287
|
-
|
|
287
|
+
if (dnsGuideUrl)
|
|
288
|
+
this.log(` Full guide: ${chalk.cyan(dnsGuideUrl)}\n`);
|
|
288
289
|
}
|
|
289
290
|
async verifyDomain(jsonOutput, domain) {
|
|
290
291
|
const verify = await this.withApiSpinner({ json: jsonOutput, text: ' Checking DNS...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY, {
|
|
@@ -293,7 +294,7 @@ export default class Deploy extends BaseCommand {
|
|
|
293
294
|
if (!verify.ok) {
|
|
294
295
|
this.handleApiError(verify);
|
|
295
296
|
}
|
|
296
|
-
const { dkim, dmarc, domainStatus, returnPath } = verify.data;
|
|
297
|
+
const { dkim, dmarc, dnsGuideUrl, domainStatus, returnPath } = verify.data;
|
|
297
298
|
const allPassed = domainStatus === 'VERIFIED';
|
|
298
299
|
if (!jsonOutput) {
|
|
299
300
|
this.log(` DKIM ${dkim ? chalk.green('✓') : chalk.red('✗')}`);
|
|
@@ -304,7 +305,8 @@ export default class Deploy extends BaseCommand {
|
|
|
304
305
|
}
|
|
305
306
|
else {
|
|
306
307
|
this.log(`\n ${chalk.yellow('Some records failed.')} Fix them and run ${chalk.cyan('mailmodo domain --verify')}.`);
|
|
307
|
-
|
|
308
|
+
if (dnsGuideUrl)
|
|
309
|
+
this.log(` Help: ${chalk.cyan(dnsGuideUrl)}\n`);
|
|
308
310
|
}
|
|
309
311
|
}
|
|
310
312
|
return allPassed;
|
|
@@ -2,7 +2,7 @@ import { Flags } from '@oclif/core';
|
|
|
2
2
|
import { input } from '@inquirer/prompts';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
5
|
-
import { API_ENDPOINTS
|
|
5
|
+
import { API_ENDPOINTS } from '../../lib/constants.js';
|
|
6
6
|
import { saveConfig } from '../../lib/config.js';
|
|
7
7
|
import { saveYaml } from '../../lib/yaml-config.js';
|
|
8
8
|
export default class Domain extends BaseCommand {
|
|
@@ -45,22 +45,31 @@ export default class Domain extends BaseCommand {
|
|
|
45
45
|
this.log(`\n ${'─'.repeat(53)}`);
|
|
46
46
|
this.log(` ${chalk.bold('DOMAIN SETUP')}`);
|
|
47
47
|
this.log(` ${'─'.repeat(53)}\n`);
|
|
48
|
-
const { domain, senderEmail, address } = await this.collectDomainInputs(flags.yes, yamlConfig);
|
|
49
|
-
const
|
|
48
|
+
const { domain, senderEmail, fromName, replyTo, address } = await this.collectDomainInputs(flags.yes, yamlConfig);
|
|
49
|
+
const apiPayload = {
|
|
50
50
|
address,
|
|
51
51
|
domain,
|
|
52
52
|
fromEmail: senderEmail,
|
|
53
|
-
}
|
|
53
|
+
};
|
|
54
|
+
if (fromName)
|
|
55
|
+
apiPayload.fromName = fromName;
|
|
56
|
+
if (replyTo)
|
|
57
|
+
apiPayload.replyTo = replyTo;
|
|
58
|
+
const response = await this.withApiSpinner({ json: flags.json, text: ' Configuring domain...' }, () => this.apiClient.post(API_ENDPOINTS.DOMAIN, apiPayload));
|
|
54
59
|
if (!response.ok) {
|
|
55
60
|
this.handleApiError(response);
|
|
56
61
|
}
|
|
57
62
|
yamlConfig.project.domain = domain;
|
|
58
63
|
yamlConfig.project.fromEmail = senderEmail;
|
|
59
64
|
yamlConfig.project.address = address;
|
|
65
|
+
if (fromName)
|
|
66
|
+
yamlConfig.project.fromName = fromName;
|
|
67
|
+
if (replyTo)
|
|
68
|
+
yamlConfig.project.replyTo = replyTo;
|
|
60
69
|
await saveYaml(yamlConfig);
|
|
61
70
|
await saveConfig({ ...config, domain });
|
|
62
71
|
const records = response.data?.dnsRecords || [];
|
|
63
|
-
const guideUrl = response.data?.dnsGuideUrl
|
|
72
|
+
const guideUrl = response.data?.dnsGuideUrl;
|
|
64
73
|
if (flags.json) {
|
|
65
74
|
this.log(JSON.stringify({ dnsRecords: records, domain }, null, 2));
|
|
66
75
|
return;
|
|
@@ -73,7 +82,8 @@ export default class Domain extends BaseCommand {
|
|
|
73
82
|
this.log(` Value: ${record.value}\n`);
|
|
74
83
|
}
|
|
75
84
|
this.log(` DNS changes take 5–30 minutes to propagate.`);
|
|
76
|
-
|
|
85
|
+
if (guideUrl)
|
|
86
|
+
this.log(` Full guide: ${chalk.cyan(guideUrl)}\n`);
|
|
77
87
|
if (!flags.yes) {
|
|
78
88
|
const action = await input({
|
|
79
89
|
default: '',
|
|
@@ -98,7 +108,7 @@ export default class Domain extends BaseCommand {
|
|
|
98
108
|
if (!response.ok) {
|
|
99
109
|
this.handleApiError(response);
|
|
100
110
|
}
|
|
101
|
-
const { dkim, dmarc, returnPath, domainStatus } = response.data;
|
|
111
|
+
const { dkim, dmarc, dnsGuideUrl, returnPath, domainStatus } = response.data;
|
|
102
112
|
if (jsonOutput) {
|
|
103
113
|
this.log(JSON.stringify({ dkim, dmarc, returnPath, domainStatus }, null, 2));
|
|
104
114
|
return;
|
|
@@ -124,7 +134,8 @@ export default class Domain extends BaseCommand {
|
|
|
124
134
|
this.log(` - Cloudflare: proxy must be OFF (grey cloud, not orange)`);
|
|
125
135
|
}
|
|
126
136
|
this.log(`\n Fix the records and run ${chalk.cyan('mailmodo domain --verify')} again.`);
|
|
127
|
-
|
|
137
|
+
if (dnsGuideUrl)
|
|
138
|
+
this.log(` Help: ${chalk.cyan(dnsGuideUrl)}\n`);
|
|
128
139
|
}
|
|
129
140
|
}
|
|
130
141
|
/**
|
|
@@ -161,6 +172,8 @@ export default class Domain extends BaseCommand {
|
|
|
161
172
|
return {
|
|
162
173
|
address: yamlConfig.project?.address || '',
|
|
163
174
|
domain,
|
|
175
|
+
fromName: yamlConfig.project?.fromName || '',
|
|
176
|
+
replyTo: yamlConfig.project?.replyTo || '',
|
|
164
177
|
senderEmail: yamlConfig.project?.fromEmail || '',
|
|
165
178
|
};
|
|
166
179
|
}
|
|
@@ -174,12 +187,20 @@ export default class Domain extends BaseCommand {
|
|
|
174
187
|
message: 'Sender email address:',
|
|
175
188
|
validate: (v) => (v?.includes('@') ? true : 'Please enter a valid email'),
|
|
176
189
|
});
|
|
190
|
+
const fromName = await input({
|
|
191
|
+
default: yamlConfig.project?.fromName || '',
|
|
192
|
+
message: 'Display name (optional, shown as sender name):',
|
|
193
|
+
});
|
|
194
|
+
const replyTo = await input({
|
|
195
|
+
default: yamlConfig.project?.replyTo || '',
|
|
196
|
+
message: 'Reply-to address (optional, press Enter to use sender email):',
|
|
197
|
+
});
|
|
177
198
|
const address = await input({
|
|
178
199
|
default: yamlConfig.project?.address,
|
|
179
200
|
message: 'Business address (required by law):',
|
|
180
201
|
validate: (v) => (v?.trim() ? true : 'Address is required'),
|
|
181
202
|
});
|
|
182
|
-
return { address, domain, senderEmail };
|
|
203
|
+
return { address, domain, fromName, replyTo, senderEmail };
|
|
183
204
|
}
|
|
184
205
|
recordLabel(index) {
|
|
185
206
|
const labels = ['DKIM', 'DMARC', 'Return Path'];
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import { editor, input, select } from '@inquirer/prompts';
|
|
2
|
+
import { confirm, editor, 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, DEFAULT_BRAND_COLOR, DEFAULT_MONTHLY_CAP, } from '../../lib/constants.js';
|
|
6
|
-
import { saveTemplate, saveYaml, } from '../../lib/yaml-config.js';
|
|
6
|
+
import { loadYaml, saveTemplate, saveYaml, } from '../../lib/yaml-config.js';
|
|
7
7
|
function isValidUrl(value) {
|
|
8
8
|
try {
|
|
9
9
|
return Boolean(new URL(value));
|
|
@@ -42,6 +42,8 @@ export default class Init extends BaseCommand {
|
|
|
42
42
|
async run() {
|
|
43
43
|
const { flags } = await this.parse(Init);
|
|
44
44
|
await this.ensureAuth();
|
|
45
|
+
if (!(await this.confirmOverwriteIfNeeded(flags)))
|
|
46
|
+
return;
|
|
45
47
|
let productUrl = flags.url;
|
|
46
48
|
if (!productUrl) {
|
|
47
49
|
productUrl = await input({
|
|
@@ -177,4 +179,21 @@ export default class Init extends BaseCommand {
|
|
|
177
179
|
this.log(` Created ${chalk.green('mailmodo.yaml')} + ${chalk.green(String(emailConfigs.length))} email templates in ${chalk.green('/mailmodo')}\n`);
|
|
178
180
|
this.log(` Run ${chalk.cyan("'mailmodo emails'")} to review.\n`);
|
|
179
181
|
}
|
|
182
|
+
async confirmOverwriteIfNeeded(flags) {
|
|
183
|
+
const existing = await loadYaml();
|
|
184
|
+
if (!existing)
|
|
185
|
+
return true;
|
|
186
|
+
if (flags.yes)
|
|
187
|
+
return true;
|
|
188
|
+
this.log(`\n ${chalk.yellow('⚠')} ${chalk.bold('mailmodo.yaml already exists.')}`);
|
|
189
|
+
this.log(` Running init will overwrite your current project configuration and all email templates.\n`);
|
|
190
|
+
const proceed = await confirm({
|
|
191
|
+
default: false,
|
|
192
|
+
message: 'Overwrite existing configuration and templates?',
|
|
193
|
+
});
|
|
194
|
+
if (!proceed) {
|
|
195
|
+
this.log(`\n Init cancelled. Run ${chalk.cyan('mailmodo edit')} to modify individual emails.\n`);
|
|
196
|
+
}
|
|
197
|
+
return proceed;
|
|
198
|
+
}
|
|
180
199
|
}
|
|
@@ -5,7 +5,7 @@ import { existsSync } from 'node:fs';
|
|
|
5
5
|
import { readFile } from 'node:fs/promises';
|
|
6
6
|
import { resolve } from 'node:path';
|
|
7
7
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
8
|
-
import { API_ENDPOINTS
|
|
8
|
+
import { API_ENDPOINTS } from '../../lib/constants.js';
|
|
9
9
|
import { saveYaml } from '../../lib/yaml-config.js';
|
|
10
10
|
const SETTINGS_GROUPS = Object.freeze({
|
|
11
11
|
billing: ['monthly_cap'],
|
|
@@ -232,6 +232,7 @@ export default class Settings extends BaseCommand {
|
|
|
232
232
|
this.handleApiError(response);
|
|
233
233
|
}
|
|
234
234
|
const records = response.data?.dnsRecords || [];
|
|
235
|
+
const dnsGuideUrl = response.data?.dnsGuideUrl;
|
|
235
236
|
yamlConfig.project.domain = newDomain;
|
|
236
237
|
yamlConfig.project.fromEmail = newFromEmail;
|
|
237
238
|
yamlConfig.project.address = newAddress;
|
|
@@ -246,7 +247,8 @@ export default class Settings extends BaseCommand {
|
|
|
246
247
|
}
|
|
247
248
|
this.log(` Run ${chalk.cyan("'mailmodo domain --verify'")} once records are added.`);
|
|
248
249
|
this.log(` Emails will not send until the new domain is verified.`);
|
|
249
|
-
|
|
250
|
+
if (dnsGuideUrl)
|
|
251
|
+
this.log(` Help: ${chalk.cyan(dnsGuideUrl)}\n`);
|
|
250
252
|
}
|
|
251
253
|
recordLabel(index) {
|
|
252
254
|
const labels = ['DKIM', 'DMARC', 'Return Path'];
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface ApiRequestDebugInfo {
|
|
|
12
12
|
responseSummary?: string;
|
|
13
13
|
}
|
|
14
14
|
export interface ApiResponse<T = Record<string, unknown>> {
|
|
15
|
+
/** Warning string from the API when the user's card is expiring or has issues. */
|
|
16
|
+
addCardWarning?: string;
|
|
15
17
|
data: T;
|
|
16
18
|
/** Populated for tracing; especially useful when `ok` is false. */
|
|
17
19
|
debug?: ApiRequestDebugInfo;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -14,6 +14,12 @@ import { fetchFileNoAuth, fetchFileWithBearerAuth, } from './fetch-file.js';
|
|
|
14
14
|
* indicating the API may not be available, rather than a raw stack trace.
|
|
15
15
|
*/
|
|
16
16
|
const RESPONSE_BODY_DEBUG_MAX = 800;
|
|
17
|
+
function extractAddCardWarning(data) {
|
|
18
|
+
const raw = data;
|
|
19
|
+
return typeof raw.addCardWarning === 'string'
|
|
20
|
+
? raw.addCardWarning
|
|
21
|
+
: undefined;
|
|
22
|
+
}
|
|
17
23
|
function summarizeResponseBody(data) {
|
|
18
24
|
if (data === null || data === undefined)
|
|
19
25
|
return undefined;
|
|
@@ -90,7 +96,13 @@ export class ApiClient {
|
|
|
90
96
|
status: response.status,
|
|
91
97
|
};
|
|
92
98
|
}
|
|
93
|
-
|
|
99
|
+
const addCardWarning = extractAddCardWarning(data);
|
|
100
|
+
return {
|
|
101
|
+
...(addCardWarning === undefined ? {} : { addCardWarning }),
|
|
102
|
+
data: data,
|
|
103
|
+
ok: true,
|
|
104
|
+
status: response.status,
|
|
105
|
+
};
|
|
94
106
|
}
|
|
95
107
|
catch (error) {
|
|
96
108
|
const err = error;
|
|
@@ -164,7 +176,13 @@ export class ApiClient {
|
|
|
164
176
|
status: response.status,
|
|
165
177
|
};
|
|
166
178
|
}
|
|
167
|
-
|
|
179
|
+
const addCardWarning = extractAddCardWarning(data);
|
|
180
|
+
return {
|
|
181
|
+
...(addCardWarning === undefined ? {} : { addCardWarning }),
|
|
182
|
+
data: data,
|
|
183
|
+
ok: true,
|
|
184
|
+
status: response.status,
|
|
185
|
+
};
|
|
168
186
|
}
|
|
169
187
|
catch (error) {
|
|
170
188
|
const err = error;
|
package/dist/lib/base-command.js
CHANGED
|
@@ -63,12 +63,12 @@ export class BaseCommand extends Command {
|
|
|
63
63
|
color: 'cyan',
|
|
64
64
|
text: options.text,
|
|
65
65
|
}).start();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
spinner.stop();
|
|
66
|
+
const result = await work().finally(() => spinner.stop());
|
|
67
|
+
const warning = result.addCardWarning;
|
|
68
|
+
if (typeof warning === 'string' && warning) {
|
|
69
|
+
this.warn(warning);
|
|
71
70
|
}
|
|
71
|
+
return result;
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
74
|
* Loads and returns the mailmodo.yaml configuration from the current directory.
|
package/oclif.manifest.json
CHANGED
|
@@ -570,14 +570,13 @@
|
|
|
570
570
|
"index.js"
|
|
571
571
|
]
|
|
572
572
|
},
|
|
573
|
-
"
|
|
573
|
+
"status": {
|
|
574
574
|
"aliases": [],
|
|
575
575
|
"args": {},
|
|
576
|
-
"description": "View and
|
|
576
|
+
"description": "View email performance metrics and quota usage",
|
|
577
577
|
"examples": [
|
|
578
|
-
"<%= config.bin %>
|
|
579
|
-
"<%= config.bin %>
|
|
580
|
-
"<%= config.bin %> settings --json"
|
|
578
|
+
"<%= config.bin %> status",
|
|
579
|
+
"<%= config.bin %> status --json"
|
|
581
580
|
],
|
|
582
581
|
"flags": {
|
|
583
582
|
"json": {
|
|
@@ -592,18 +591,11 @@
|
|
|
592
591
|
"name": "yes",
|
|
593
592
|
"allowNo": false,
|
|
594
593
|
"type": "boolean"
|
|
595
|
-
},
|
|
596
|
-
"set": {
|
|
597
|
-
"description": "Set a setting (format: key=value)",
|
|
598
|
-
"name": "set",
|
|
599
|
-
"hasDynamicHelp": false,
|
|
600
|
-
"multiple": false,
|
|
601
|
-
"type": "option"
|
|
602
594
|
}
|
|
603
595
|
},
|
|
604
596
|
"hasDynamicHelp": false,
|
|
605
597
|
"hiddenAliases": [],
|
|
606
|
-
"id": "
|
|
598
|
+
"id": "status",
|
|
607
599
|
"pluginAlias": "@mailmodo/cli",
|
|
608
600
|
"pluginName": "@mailmodo/cli",
|
|
609
601
|
"pluginType": "core",
|
|
@@ -613,17 +605,18 @@
|
|
|
613
605
|
"relativePath": [
|
|
614
606
|
"dist",
|
|
615
607
|
"commands",
|
|
616
|
-
"
|
|
608
|
+
"status",
|
|
617
609
|
"index.js"
|
|
618
610
|
]
|
|
619
611
|
},
|
|
620
|
-
"
|
|
612
|
+
"settings": {
|
|
621
613
|
"aliases": [],
|
|
622
614
|
"args": {},
|
|
623
|
-
"description": "View
|
|
615
|
+
"description": "View and update project settings",
|
|
624
616
|
"examples": [
|
|
625
|
-
"<%= config.bin %>
|
|
626
|
-
"<%= config.bin %>
|
|
617
|
+
"<%= config.bin %> settings",
|
|
618
|
+
"<%= config.bin %> settings --set brand_color=#0F3460",
|
|
619
|
+
"<%= config.bin %> settings --json"
|
|
627
620
|
],
|
|
628
621
|
"flags": {
|
|
629
622
|
"json": {
|
|
@@ -638,11 +631,18 @@
|
|
|
638
631
|
"name": "yes",
|
|
639
632
|
"allowNo": false,
|
|
640
633
|
"type": "boolean"
|
|
634
|
+
},
|
|
635
|
+
"set": {
|
|
636
|
+
"description": "Set a setting (format: key=value)",
|
|
637
|
+
"name": "set",
|
|
638
|
+
"hasDynamicHelp": false,
|
|
639
|
+
"multiple": false,
|
|
640
|
+
"type": "option"
|
|
641
641
|
}
|
|
642
642
|
},
|
|
643
643
|
"hasDynamicHelp": false,
|
|
644
644
|
"hiddenAliases": [],
|
|
645
|
-
"id": "
|
|
645
|
+
"id": "settings",
|
|
646
646
|
"pluginAlias": "@mailmodo/cli",
|
|
647
647
|
"pluginName": "@mailmodo/cli",
|
|
648
648
|
"pluginType": "core",
|
|
@@ -652,10 +652,10 @@
|
|
|
652
652
|
"relativePath": [
|
|
653
653
|
"dist",
|
|
654
654
|
"commands",
|
|
655
|
-
"
|
|
655
|
+
"settings",
|
|
656
656
|
"index.js"
|
|
657
657
|
]
|
|
658
658
|
}
|
|
659
659
|
},
|
|
660
|
-
"version": "0.0.
|
|
660
|
+
"version": "0.0.29-beta.pr31.49"
|
|
661
661
|
}
|