@mailmodo/cli 0.0.21 → 0.0.22
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.
|
@@ -5,6 +5,8 @@ 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>;
|
|
8
10
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
11
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
12
|
};
|
|
@@ -17,11 +17,22 @@ 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
|
+
}),
|
|
20
28
|
};
|
|
21
29
|
async run() {
|
|
22
30
|
const { flags } = await this.parse(Logs);
|
|
23
31
|
await this.ensureAuth();
|
|
24
|
-
const params = {
|
|
32
|
+
const params = {
|
|
33
|
+
limit: String(flags.limit),
|
|
34
|
+
page: String(flags.page),
|
|
35
|
+
};
|
|
25
36
|
if (flags.email)
|
|
26
37
|
params.email = flags.email;
|
|
27
38
|
if (flags.failed)
|
|
@@ -30,7 +41,7 @@ export default class Logs extends BaseCommand {
|
|
|
30
41
|
if (!response.ok) {
|
|
31
42
|
this.handleApiError(response);
|
|
32
43
|
}
|
|
33
|
-
const { entries } = response.data;
|
|
44
|
+
const { entries, limit, page, total } = response.data;
|
|
34
45
|
if (flags.json) {
|
|
35
46
|
this.log(JSON.stringify(response.data, null, 2));
|
|
36
47
|
return;
|
|
@@ -49,6 +60,11 @@ export default class Logs extends BaseCommand {
|
|
|
49
60
|
this.log(` ${' '.repeat(52)}${chalk.dim(`(reason: ${entry.reason})`)}`);
|
|
50
61
|
}
|
|
51
62
|
}
|
|
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
|
+
}
|
|
52
68
|
}
|
|
53
69
|
else {
|
|
54
70
|
this.log(` ${chalk.dim('No log entries found.')}`);
|
|
@@ -8,6 +8,8 @@ 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;
|
|
11
13
|
/**
|
|
12
14
|
* Prompts the user to pick a setting key to edit and dispatches
|
|
13
15
|
* to the appropriate handler for that key.
|
|
@@ -43,73 +43,85 @@ 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;
|
|
47
46
|
if (flags.set) {
|
|
48
|
-
|
|
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`);
|
|
47
|
+
await this.applySetFlag(flags.set, yamlConfig, flags.json ?? false);
|
|
67
48
|
return;
|
|
68
49
|
}
|
|
69
50
|
if (flags.json) {
|
|
70
|
-
this.log(JSON.stringify({ settings: project }, null, 2));
|
|
51
|
+
this.log(JSON.stringify({ settings: yamlConfig.project }, null, 2));
|
|
71
52
|
return;
|
|
72
53
|
}
|
|
73
|
-
const domainVerified = await this.fetchDomainVerified(project.domain);
|
|
74
|
-
this.log(`\n Current settings for ${chalk.bold(project.name || 'project')}:\n`);
|
|
54
|
+
const domainVerified = await this.fetchDomainVerified(yamlConfig.project.domain);
|
|
55
|
+
this.log(`\n Current settings for ${chalk.bold(yamlConfig.project.name || 'project')}:\n`);
|
|
75
56
|
for (const [group, keys] of Object.entries(SETTINGS_GROUPS)) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
57
|
+
this.displaySettingsGroup(group, keys, yamlConfig.project, domainVerified);
|
|
58
|
+
}
|
|
59
|
+
if (!flags.yes) {
|
|
60
|
+
await this.promptEditSetting(yamlConfig);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async applySetFlag(setFlag, yamlConfig, isJson) {
|
|
64
|
+
const { project } = yamlConfig;
|
|
65
|
+
const eqIndex = setFlag.indexOf('=');
|
|
66
|
+
if (eqIndex === -1) {
|
|
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('');
|
|
86
99
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
displayValue += ` ${chalk.red('✗ not verified')}`;
|
|
98
|
-
}
|
|
99
|
-
this.log(` ${key.padEnd(16)} ${displayValue}`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.log(groupTitle);
|
|
103
|
+
this.log(` ${'─'.repeat(49)}`);
|
|
104
|
+
for (const key of availableKeys) {
|
|
105
|
+
const propKey = settingKeyToProp(key);
|
|
106
|
+
const value = project[propKey];
|
|
107
|
+
let displayValue = value ? String(value) : chalk.dim('(not set)');
|
|
108
|
+
if (key === 'domain' && value && domainVerified === true) {
|
|
109
|
+
displayValue += ` ${chalk.green('✓ verified')}`;
|
|
100
110
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const hint = SETUP_HINTS[settingKeyToProp(key)];
|
|
104
|
-
if (hint) {
|
|
105
|
-
this.log(` ${key.padEnd(16)} ${chalk.dim(`(run ${hint} to set up)`)}`);
|
|
106
|
-
}
|
|
111
|
+
else if (key === 'domain' && value && domainVerified === false) {
|
|
112
|
+
displayValue += ` ${chalk.red('✗ not verified')}`;
|
|
107
113
|
}
|
|
108
|
-
this.log(
|
|
114
|
+
this.log(` ${key.padEnd(16)} ${displayValue}`);
|
|
109
115
|
}
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
const missingKeys = keys.filter((key) => !(settingKeyToProp(key) in project) &&
|
|
117
|
+
!(group === 'brand' && key === 'logo_file'));
|
|
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
|
+
}
|
|
112
123
|
}
|
|
124
|
+
this.log('');
|
|
113
125
|
}
|
|
114
126
|
/**
|
|
115
127
|
* Prompts the user to pick a setting key to edit and dispatches
|
|
@@ -125,6 +137,10 @@ export default class Settings extends BaseCommand {
|
|
|
125
137
|
return;
|
|
126
138
|
const editPropKey = settingKeyToProp(editKey);
|
|
127
139
|
if (!(editPropKey in project)) {
|
|
140
|
+
if (editKey === 'logo_file') {
|
|
141
|
+
await this.handleLogoUpload(yamlConfig);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
128
144
|
const hint = SETUP_HINTS[editPropKey];
|
|
129
145
|
if (hint) {
|
|
130
146
|
this.log(`\n ${editKey} is not configured yet. Run ${chalk.cyan(hint)} to set it up.\n`);
|
|
@@ -252,8 +268,16 @@ export default class Settings extends BaseCommand {
|
|
|
252
268
|
}
|
|
253
269
|
await this.ensureAuth();
|
|
254
270
|
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';
|
|
255
279
|
const formData = new FormData();
|
|
256
|
-
formData.append('logo', new Blob([new Uint8Array(fileBuffer)]), logoPath.split(/[/\\]/).pop() || 'logo.png');
|
|
280
|
+
formData.append('logo', new Blob([new Uint8Array(fileBuffer)], { type: mimeType }), logoPath.split(/[/\\]/).pop() || 'logo.png');
|
|
257
281
|
const response = await this.apiClient.postFormData(API_ENDPOINTS.ASSETS_LOGO, formData);
|
|
258
282
|
if (!response.ok) {
|
|
259
283
|
this.handleApiError(response);
|
package/oclif.manifest.json
CHANGED
|
@@ -455,6 +455,22 @@
|
|
|
455
455
|
"name": "failed",
|
|
456
456
|
"allowNo": false,
|
|
457
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"
|
|
458
474
|
}
|
|
459
475
|
},
|
|
460
476
|
"hasDynamicHelp": false,
|
|
@@ -618,5 +634,5 @@
|
|
|
618
634
|
]
|
|
619
635
|
}
|
|
620
636
|
},
|
|
621
|
-
"version": "0.0.
|
|
637
|
+
"version": "0.0.22"
|
|
622
638
|
}
|