@cakemail-org/cakemail-cli 1.3.0 → 1.5.0
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/cli.js +40 -6
- package/dist/cli.js.map +1 -1
- package/dist/client.js +0 -2
- package/dist/client.js.map +1 -1
- package/dist/commands/account.d.ts +5 -0
- package/dist/commands/account.d.ts.map +1 -0
- package/dist/commands/account.js +231 -0
- package/dist/commands/account.js.map +1 -0
- package/dist/commands/attributes.d.ts.map +1 -1
- package/dist/commands/attributes.js +43 -15
- package/dist/commands/attributes.js.map +1 -1
- package/dist/commands/campaigns.d.ts.map +1 -1
- package/dist/commands/campaigns.js +153 -19
- package/dist/commands/campaigns.js.map +1 -1
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +235 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/contacts.d.ts.map +1 -1
- package/dist/commands/contacts.js +233 -38
- package/dist/commands/contacts.js.map +1 -1
- package/dist/commands/lists.d.ts.map +1 -1
- package/dist/commands/lists.js +160 -23
- package/dist/commands/lists.js.map +1 -1
- package/dist/commands/reports.d.ts.map +1 -1
- package/dist/commands/reports.js +90 -13
- package/dist/commands/reports.js.map +1 -1
- package/dist/commands/segments.d.ts.map +1 -1
- package/dist/commands/segments.js +59 -21
- package/dist/commands/segments.js.map +1 -1
- package/dist/commands/senders.d.ts.map +1 -1
- package/dist/commands/senders.js +36 -5
- package/dist/commands/senders.js.map +1 -1
- package/dist/commands/suppressed.d.ts.map +1 -1
- package/dist/commands/suppressed.js +20 -6
- package/dist/commands/suppressed.js.map +1 -1
- package/dist/commands/templates.d.ts.map +1 -1
- package/dist/commands/templates.js +11 -3
- package/dist/commands/templates.js.map +1 -1
- package/dist/types/profile.d.ts +92 -0
- package/dist/types/profile.d.ts.map +1 -0
- package/dist/types/profile.js +119 -0
- package/dist/types/profile.js.map +1 -0
- package/dist/utils/auth.d.ts +26 -0
- package/dist/utils/auth.d.ts.map +1 -0
- package/dist/utils/auth.js +198 -0
- package/dist/utils/auth.js.map +1 -0
- package/dist/utils/config-file.d.ts +92 -0
- package/dist/utils/config-file.d.ts.map +1 -0
- package/dist/utils/config-file.js +244 -0
- package/dist/utils/config-file.js.map +1 -0
- package/dist/utils/config.d.ts +11 -2
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +74 -8
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/confirm.d.ts +38 -0
- package/dist/utils/confirm.d.ts.map +1 -0
- package/dist/utils/confirm.js +124 -0
- package/dist/utils/confirm.js.map +1 -0
- package/dist/utils/defaults.d.ts +39 -0
- package/dist/utils/defaults.d.ts.map +1 -0
- package/dist/utils/defaults.js +195 -0
- package/dist/utils/defaults.js.map +1 -0
- package/dist/utils/errors.d.ts +67 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +395 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/interactive.d.ts +97 -0
- package/dist/utils/interactive.d.ts.map +1 -0
- package/dist/utils/interactive.js +265 -0
- package/dist/utils/interactive.js.map +1 -0
- package/dist/utils/output.d.ts +72 -2
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +383 -37
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/progress.d.ts +139 -0
- package/dist/utils/progress.d.ts.map +1 -0
- package/dist/utils/progress.js +216 -0
- package/dist/utils/progress.js.map +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
// In-memory cache for session (cleared on CLI exit)
|
|
3
|
+
const sessionCache = {};
|
|
4
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
5
|
+
/**
|
|
6
|
+
* Check if cached resource is still valid
|
|
7
|
+
*/
|
|
8
|
+
function isCacheValid(cached) {
|
|
9
|
+
if (!cached)
|
|
10
|
+
return false;
|
|
11
|
+
return Date.now() - cached.timestamp < CACHE_TTL;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Auto-detect list ID when only one exists
|
|
15
|
+
* Returns the list ID if only one list exists, otherwise undefined
|
|
16
|
+
*/
|
|
17
|
+
export async function autoDetectList(client, formatter, providedListId, options = {}) {
|
|
18
|
+
// If user provided a list ID, use it
|
|
19
|
+
if (providedListId !== undefined) {
|
|
20
|
+
const listId = typeof providedListId === 'string' ? parseInt(providedListId) : providedListId;
|
|
21
|
+
// Cache it for future use
|
|
22
|
+
sessionCache.lastList = {
|
|
23
|
+
id: listId,
|
|
24
|
+
timestamp: Date.now()
|
|
25
|
+
};
|
|
26
|
+
return listId;
|
|
27
|
+
}
|
|
28
|
+
// Check cache if enabled
|
|
29
|
+
if (options.useCache && isCacheValid(sessionCache.lastList)) {
|
|
30
|
+
if (!options.silent) {
|
|
31
|
+
formatter.info(`Using cached list: ${sessionCache.lastList.id}${sessionCache.lastList.name ? ` (${sessionCache.lastList.name})` : ''}`);
|
|
32
|
+
}
|
|
33
|
+
return sessionCache.lastList.id;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
// Fetch all lists
|
|
37
|
+
const response = await client.sdk.lists.list({ per_page: 100 });
|
|
38
|
+
const lists = response.data || [];
|
|
39
|
+
if (lists.length === 0) {
|
|
40
|
+
if (!options.silent) {
|
|
41
|
+
formatter.error('No lists found. Create a list first with: cakemail lists create -n "My List"');
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
if (lists.length === 1) {
|
|
46
|
+
const list = lists[0];
|
|
47
|
+
const listId = list.id;
|
|
48
|
+
// Cache it
|
|
49
|
+
sessionCache.lastList = {
|
|
50
|
+
id: listId,
|
|
51
|
+
name: list.name,
|
|
52
|
+
timestamp: Date.now()
|
|
53
|
+
};
|
|
54
|
+
if (!options.silent) {
|
|
55
|
+
formatter.info(chalk.gray(`Auto-detected list: ${listId} (${list.name})`));
|
|
56
|
+
}
|
|
57
|
+
return listId;
|
|
58
|
+
}
|
|
59
|
+
// Multiple lists exist
|
|
60
|
+
if (!options.silent) {
|
|
61
|
+
formatter.info(chalk.yellow('Multiple lists found. Please specify --list-id <id>'));
|
|
62
|
+
formatter.info(chalk.gray('Available lists:'));
|
|
63
|
+
lists.slice(0, 5).forEach((list) => {
|
|
64
|
+
formatter.info(chalk.gray(` ${list.id}: ${list.name}`));
|
|
65
|
+
});
|
|
66
|
+
if (lists.length > 5) {
|
|
67
|
+
formatter.info(chalk.gray(` ... and ${lists.length - 5} more`));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (!options.silent) {
|
|
74
|
+
formatter.error(`Failed to auto-detect list: ${error.message}`);
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Auto-detect sender ID when only one verified sender exists
|
|
81
|
+
*/
|
|
82
|
+
export async function autoDetectSender(client, formatter, providedSenderId, options = {}) {
|
|
83
|
+
// If user provided a sender ID, use it
|
|
84
|
+
if (providedSenderId !== undefined) {
|
|
85
|
+
const senderId = String(providedSenderId);
|
|
86
|
+
// Cache it for future use
|
|
87
|
+
sessionCache.lastSender = {
|
|
88
|
+
id: senderId,
|
|
89
|
+
timestamp: Date.now()
|
|
90
|
+
};
|
|
91
|
+
return senderId;
|
|
92
|
+
}
|
|
93
|
+
// Check cache if enabled
|
|
94
|
+
if (options.useCache && isCacheValid(sessionCache.lastSender)) {
|
|
95
|
+
if (!options.silent) {
|
|
96
|
+
formatter.info(`Using cached sender: ${sessionCache.lastSender.id}${sessionCache.lastSender.name ? ` (${sessionCache.lastSender.name})` : ''}`);
|
|
97
|
+
}
|
|
98
|
+
return sessionCache.lastSender.id;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
// Fetch all senders
|
|
102
|
+
const response = await client.sdk.senderService.listSenders({ perPage: 100 });
|
|
103
|
+
const allSenders = response.data || [];
|
|
104
|
+
// Filter to confirmed senders if required
|
|
105
|
+
const senders = options.requireConfirmed
|
|
106
|
+
? allSenders.filter((s) => s.confirmed === true)
|
|
107
|
+
: allSenders;
|
|
108
|
+
if (senders.length === 0) {
|
|
109
|
+
if (!options.silent) {
|
|
110
|
+
if (options.requireConfirmed && allSenders.length > 0) {
|
|
111
|
+
formatter.error('No confirmed senders found. Please confirm a sender first.');
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
formatter.error('No senders found. Create a sender first with: cakemail senders create -n "Name" -e "email@domain.com"');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
if (senders.length === 1) {
|
|
120
|
+
const sender = senders[0];
|
|
121
|
+
const senderId = sender.id;
|
|
122
|
+
// Cache it
|
|
123
|
+
sessionCache.lastSender = {
|
|
124
|
+
id: senderId,
|
|
125
|
+
name: sender.name,
|
|
126
|
+
timestamp: Date.now()
|
|
127
|
+
};
|
|
128
|
+
if (!options.silent) {
|
|
129
|
+
formatter.info(chalk.gray(`Auto-detected sender: ${senderId} (${sender.name} <${sender.email}>)`));
|
|
130
|
+
}
|
|
131
|
+
return senderId;
|
|
132
|
+
}
|
|
133
|
+
// Multiple senders exist
|
|
134
|
+
if (!options.silent) {
|
|
135
|
+
const senderType = options.requireConfirmed ? 'confirmed senders' : 'senders';
|
|
136
|
+
formatter.info(chalk.yellow(`Multiple ${senderType} found. Please specify --sender-id <id>`));
|
|
137
|
+
formatter.info(chalk.gray('Available senders:'));
|
|
138
|
+
senders.slice(0, 5).forEach((sender) => {
|
|
139
|
+
const confirmed = sender.confirmed ? chalk.green('✓') : chalk.gray('✗');
|
|
140
|
+
formatter.info(chalk.gray(` ${sender.id}: ${sender.name} <${sender.email}> ${confirmed}`));
|
|
141
|
+
});
|
|
142
|
+
if (senders.length > 5) {
|
|
143
|
+
formatter.info(chalk.gray(` ... and ${senders.length - 5} more`));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (!options.silent) {
|
|
150
|
+
formatter.error(`Failed to auto-detect sender: ${error.message}`);
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get last used list from cache
|
|
157
|
+
*/
|
|
158
|
+
export function getLastList() {
|
|
159
|
+
if (isCacheValid(sessionCache.lastList)) {
|
|
160
|
+
return sessionCache.lastList.id;
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get last used sender from cache
|
|
166
|
+
*/
|
|
167
|
+
export function getLastSender() {
|
|
168
|
+
if (isCacheValid(sessionCache.lastSender)) {
|
|
169
|
+
return sessionCache.lastSender.id;
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Clear session cache (useful for testing)
|
|
175
|
+
*/
|
|
176
|
+
export function clearCache() {
|
|
177
|
+
sessionCache.lastList = undefined;
|
|
178
|
+
sessionCache.lastCampaign = undefined;
|
|
179
|
+
sessionCache.lastSender = undefined;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Suggest a default value for a missing required option
|
|
183
|
+
* Returns the detected value or undefined
|
|
184
|
+
*/
|
|
185
|
+
export async function suggestDefault(client, formatter, type, options = {}) {
|
|
186
|
+
switch (type) {
|
|
187
|
+
case 'list':
|
|
188
|
+
return autoDetectList(client, formatter, undefined, options);
|
|
189
|
+
case 'sender':
|
|
190
|
+
return autoDetectSender(client, formatter, undefined, options);
|
|
191
|
+
default:
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/utils/defaults.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ProfileConfig } from '../types/profile.js';
|
|
2
|
+
/**
|
|
3
|
+
* Enhanced error handling with user-friendly messages and suggestions
|
|
4
|
+
*/
|
|
5
|
+
export interface ErrorContext {
|
|
6
|
+
command: string;
|
|
7
|
+
resource?: string;
|
|
8
|
+
resourceId?: string | number;
|
|
9
|
+
operation?: string;
|
|
10
|
+
profileConfig?: ProfileConfig;
|
|
11
|
+
}
|
|
12
|
+
export interface EnhancedError {
|
|
13
|
+
message: string;
|
|
14
|
+
suggestion?: string;
|
|
15
|
+
help?: string;
|
|
16
|
+
docsUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Enhance error with user-friendly message and suggestions
|
|
20
|
+
*/
|
|
21
|
+
export declare function enhanceError(error: any, context: ErrorContext): EnhancedError;
|
|
22
|
+
/**
|
|
23
|
+
* Display enhanced error to user (profile-aware)
|
|
24
|
+
*/
|
|
25
|
+
export declare function displayError(error: any, context: ErrorContext): void;
|
|
26
|
+
/**
|
|
27
|
+
* Validation helpers
|
|
28
|
+
*/
|
|
29
|
+
export declare const validate: {
|
|
30
|
+
/**
|
|
31
|
+
* Validate email format
|
|
32
|
+
*/
|
|
33
|
+
email(email: string): {
|
|
34
|
+
valid: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Validate ID is a positive integer
|
|
39
|
+
*/
|
|
40
|
+
id(id: string | number, resourceName?: string): {
|
|
41
|
+
valid: boolean;
|
|
42
|
+
error?: string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Validate date format (YYYY-MM-DD)
|
|
46
|
+
*/
|
|
47
|
+
date(date: string): {
|
|
48
|
+
valid: boolean;
|
|
49
|
+
error?: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Validate required field
|
|
53
|
+
*/
|
|
54
|
+
required(value: any, fieldName: string): {
|
|
55
|
+
valid: boolean;
|
|
56
|
+
error?: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Validate JSON string
|
|
60
|
+
*/
|
|
61
|
+
json(jsonString: string, fieldName?: string): {
|
|
62
|
+
valid: boolean;
|
|
63
|
+
error?: string;
|
|
64
|
+
parsed?: any;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;GAEG;AAEH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAoND;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,GAAG,aAAa,CA6B7E;AAwBD;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI,CAapE;AA0ED;;GAEG;AACH,eAAO,MAAM,QAAQ;IACnB;;OAEG;iBACU,MAAM,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAWxD;;OAEG;WACI,MAAM,GAAG,MAAM,iBAAgB,MAAM,GAAU;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAWxF;;OAEG;eACQ,MAAM,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAoBtD;;OAEG;oBACa,GAAG,aAAa,MAAM,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAU3E;;OAEG;qBACc,MAAM,cAAa,MAAM,GAAY;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,GAAG,CAAA;KAAE;CAWvG,CAAC"}
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
/**
|
|
3
|
+
* Common HTTP status codes and their user-friendly messages
|
|
4
|
+
*/
|
|
5
|
+
const HTTP_ERROR_MESSAGES = {
|
|
6
|
+
400: (ctx) => ({
|
|
7
|
+
message: 'Invalid request parameters',
|
|
8
|
+
suggestion: ctx.operation
|
|
9
|
+
? `Check the parameters for '${ctx.operation}' operation`
|
|
10
|
+
: 'Please check your command arguments and try again',
|
|
11
|
+
help: 'Use --help to see available options'
|
|
12
|
+
}),
|
|
13
|
+
401: (ctx) => ({
|
|
14
|
+
message: 'Authentication failed',
|
|
15
|
+
suggestion: 'Your credentials are invalid or expired',
|
|
16
|
+
help: [
|
|
17
|
+
'To re-authenticate, run: cakemail account logout --force',
|
|
18
|
+
'Then run any command to authenticate again',
|
|
19
|
+
'Or check your .env file credentials'
|
|
20
|
+
].join('\n')
|
|
21
|
+
}),
|
|
22
|
+
403: (ctx) => ({
|
|
23
|
+
message: 'Permission denied',
|
|
24
|
+
suggestion: ctx.resource
|
|
25
|
+
? `You don't have permission to access this ${ctx.resource}`
|
|
26
|
+
: 'You don\'t have permission to perform this action',
|
|
27
|
+
help: ctx.resourceId
|
|
28
|
+
? `Check if ${ctx.resource} ${ctx.resourceId} exists and you have access`
|
|
29
|
+
: 'Verify your account permissions'
|
|
30
|
+
}),
|
|
31
|
+
404: (ctx) => ({
|
|
32
|
+
message: ctx.resource
|
|
33
|
+
? `${capitalize(ctx.resource)} not found`
|
|
34
|
+
: 'Resource not found',
|
|
35
|
+
suggestion: ctx.resourceId && ctx.resource
|
|
36
|
+
? `${capitalize(ctx.resource)} with ID '${ctx.resourceId}' doesn't exist`
|
|
37
|
+
: `The requested ${ctx.resource || 'resource'} doesn't exist`,
|
|
38
|
+
help: getListCommandSuggestion(ctx.resource)
|
|
39
|
+
}),
|
|
40
|
+
409: (ctx) => ({
|
|
41
|
+
message: 'Conflict - resource already exists',
|
|
42
|
+
suggestion: ctx.resource
|
|
43
|
+
? `This ${ctx.resource} already exists or conflicts with existing data`
|
|
44
|
+
: 'The resource already exists',
|
|
45
|
+
help: 'Try updating the existing resource instead of creating a new one'
|
|
46
|
+
}),
|
|
47
|
+
422: (ctx) => ({
|
|
48
|
+
message: 'Validation error',
|
|
49
|
+
suggestion: 'One or more fields have invalid values',
|
|
50
|
+
help: 'Check the error details above for specific field errors'
|
|
51
|
+
}),
|
|
52
|
+
429: (ctx) => ({
|
|
53
|
+
message: 'Rate limit exceeded',
|
|
54
|
+
suggestion: 'You\'re making too many requests',
|
|
55
|
+
help: 'Wait a moment before trying again, or reduce request frequency'
|
|
56
|
+
}),
|
|
57
|
+
500: (ctx) => ({
|
|
58
|
+
message: 'Server error',
|
|
59
|
+
suggestion: 'Something went wrong on the Cakemail server',
|
|
60
|
+
help: 'This is not your fault. Please try again in a few moments or contact support'
|
|
61
|
+
}),
|
|
62
|
+
502: (ctx) => ({
|
|
63
|
+
message: 'Bad gateway',
|
|
64
|
+
suggestion: 'The Cakemail API is temporarily unavailable',
|
|
65
|
+
help: 'Please try again in a few moments'
|
|
66
|
+
}),
|
|
67
|
+
503: (ctx) => ({
|
|
68
|
+
message: 'Service unavailable',
|
|
69
|
+
suggestion: 'The Cakemail API is temporarily down for maintenance',
|
|
70
|
+
help: 'Please try again later'
|
|
71
|
+
})
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Common error patterns and their user-friendly messages
|
|
75
|
+
*/
|
|
76
|
+
const ERROR_PATTERNS = [
|
|
77
|
+
{
|
|
78
|
+
pattern: /network|ECONNREFUSED|ENOTFOUND|timeout/i,
|
|
79
|
+
handler: (match, ctx) => ({
|
|
80
|
+
message: 'Network connection error',
|
|
81
|
+
suggestion: 'Unable to connect to the Cakemail API',
|
|
82
|
+
help: [
|
|
83
|
+
'Check your internet connection',
|
|
84
|
+
'Verify the API endpoint is correct',
|
|
85
|
+
'Check if there\'s a firewall blocking the connection'
|
|
86
|
+
].join('\n')
|
|
87
|
+
})
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
pattern: /email.*required|invalid.*email/i,
|
|
91
|
+
handler: (match, ctx) => ({
|
|
92
|
+
message: 'Invalid email address',
|
|
93
|
+
suggestion: 'Please provide a valid email address',
|
|
94
|
+
help: 'Example: user@example.com'
|
|
95
|
+
})
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
pattern: /list.*not.*found/i,
|
|
99
|
+
handler: (match, ctx) => ({
|
|
100
|
+
message: 'List not found',
|
|
101
|
+
suggestion: ctx.resourceId
|
|
102
|
+
? `List ${ctx.resourceId} doesn't exist or you don't have access`
|
|
103
|
+
: 'The specified list doesn\'t exist',
|
|
104
|
+
help: 'Use: cakemail lists list to see all available lists'
|
|
105
|
+
})
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
pattern: /campaign.*not.*found/i,
|
|
109
|
+
handler: (match, ctx) => ({
|
|
110
|
+
message: 'Campaign not found',
|
|
111
|
+
suggestion: ctx.resourceId
|
|
112
|
+
? `Campaign ${ctx.resourceId} doesn't exist or you don't have access`
|
|
113
|
+
: 'The specified campaign doesn\'t exist',
|
|
114
|
+
help: 'Use: cakemail campaigns list to see all campaigns'
|
|
115
|
+
})
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
pattern: /contact.*not.*found/i,
|
|
119
|
+
handler: (match, ctx) => ({
|
|
120
|
+
message: 'Contact not found',
|
|
121
|
+
suggestion: ctx.resourceId
|
|
122
|
+
? `Contact ${ctx.resourceId} doesn't exist in this list`
|
|
123
|
+
: 'The specified contact doesn\'t exist',
|
|
124
|
+
help: 'Use: cakemail contacts list <list-id> to see contacts'
|
|
125
|
+
})
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
pattern: /Missing.*CAKEMAIL/i,
|
|
129
|
+
handler: (match, ctx) => ({
|
|
130
|
+
message: 'Missing credentials',
|
|
131
|
+
suggestion: 'Authentication credentials are not configured',
|
|
132
|
+
help: [
|
|
133
|
+
'Set credentials in .env file or environment variables:',
|
|
134
|
+
' CAKEMAIL_EMAIL=your@email.com',
|
|
135
|
+
' CAKEMAIL_PASSWORD=your_password',
|
|
136
|
+
'',
|
|
137
|
+
'Or run any command to authenticate interactively'
|
|
138
|
+
].join('\n')
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
];
|
|
142
|
+
/**
|
|
143
|
+
* Get list command suggestion for a resource type
|
|
144
|
+
*/
|
|
145
|
+
function getListCommandSuggestion(resource) {
|
|
146
|
+
if (!resource)
|
|
147
|
+
return undefined;
|
|
148
|
+
const commands = {
|
|
149
|
+
'list': 'cakemail lists list',
|
|
150
|
+
'campaign': 'cakemail campaigns list',
|
|
151
|
+
'contact': 'cakemail contacts list <list-id>',
|
|
152
|
+
'sender': 'cakemail senders list',
|
|
153
|
+
'template': 'cakemail templates list',
|
|
154
|
+
'webhook': 'cakemail webhooks list',
|
|
155
|
+
'segment': 'cakemail segments list <list-id>',
|
|
156
|
+
'attribute': 'cakemail attributes list <list-id>'
|
|
157
|
+
};
|
|
158
|
+
const command = commands[resource.toLowerCase()];
|
|
159
|
+
return command ? `To see all ${resource}s, use: ${command}` : undefined;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Capitalize first letter
|
|
163
|
+
*/
|
|
164
|
+
function capitalize(str) {
|
|
165
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Parse API error response
|
|
169
|
+
*/
|
|
170
|
+
function parseApiError(error) {
|
|
171
|
+
// Check for SDK/Axios error structure
|
|
172
|
+
if (error.response) {
|
|
173
|
+
return {
|
|
174
|
+
statusCode: error.response.status,
|
|
175
|
+
message: error.response.data?.message || error.response.data?.error || error.message,
|
|
176
|
+
details: error.response.data
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// Check for SDK ApiError structure
|
|
180
|
+
if (error.status) {
|
|
181
|
+
return {
|
|
182
|
+
statusCode: error.status,
|
|
183
|
+
message: error.body?.message || error.message,
|
|
184
|
+
details: error.body
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
// Fallback to error message
|
|
188
|
+
return {
|
|
189
|
+
message: error.message || 'An unknown error occurred'
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Enhance error with user-friendly message and suggestions
|
|
194
|
+
*/
|
|
195
|
+
export function enhanceError(error, context) {
|
|
196
|
+
const parsed = parseApiError(error);
|
|
197
|
+
// Try HTTP status code mapping first
|
|
198
|
+
if (parsed.statusCode && HTTP_ERROR_MESSAGES[parsed.statusCode]) {
|
|
199
|
+
const enhanced = HTTP_ERROR_MESSAGES[parsed.statusCode](context);
|
|
200
|
+
// Add validation details for 422 errors
|
|
201
|
+
if (parsed.statusCode === 422 && parsed.details?.errors) {
|
|
202
|
+
enhanced.suggestion = formatValidationErrors(parsed.details.errors);
|
|
203
|
+
}
|
|
204
|
+
return enhanced;
|
|
205
|
+
}
|
|
206
|
+
// Try pattern matching
|
|
207
|
+
for (const { pattern, handler } of ERROR_PATTERNS) {
|
|
208
|
+
const match = parsed.message.match(pattern);
|
|
209
|
+
if (match) {
|
|
210
|
+
return handler(match, context);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Fallback to original error message
|
|
214
|
+
return {
|
|
215
|
+
message: parsed.message,
|
|
216
|
+
suggestion: 'An unexpected error occurred',
|
|
217
|
+
help: 'If this persists, please report it at: https://github.com/cakemail/cli/issues'
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Format validation errors from API response
|
|
222
|
+
*/
|
|
223
|
+
function formatValidationErrors(errors) {
|
|
224
|
+
if (Array.isArray(errors)) {
|
|
225
|
+
return errors.map(err => {
|
|
226
|
+
if (typeof err === 'string')
|
|
227
|
+
return err;
|
|
228
|
+
if (err.field && err.message)
|
|
229
|
+
return `${err.field}: ${err.message}`;
|
|
230
|
+
if (err.loc && err.msg)
|
|
231
|
+
return `${err.loc.join('.')}: ${err.msg}`;
|
|
232
|
+
return JSON.stringify(err);
|
|
233
|
+
}).join('\n');
|
|
234
|
+
}
|
|
235
|
+
if (typeof errors === 'object') {
|
|
236
|
+
return Object.entries(errors)
|
|
237
|
+
.map(([field, message]) => `${field}: ${message}`)
|
|
238
|
+
.join('\n');
|
|
239
|
+
}
|
|
240
|
+
return String(errors);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Display enhanced error to user (profile-aware)
|
|
244
|
+
*/
|
|
245
|
+
export function displayError(error, context) {
|
|
246
|
+
const enhanced = enhanceError(error, context);
|
|
247
|
+
const profile = context.profileConfig;
|
|
248
|
+
const verboseErrors = profile?.display.verbose_errors ?? false;
|
|
249
|
+
const showApiDetails = profile?.display.show_api_details ?? false;
|
|
250
|
+
// For developer profile: show technical details
|
|
251
|
+
if (verboseErrors && showApiDetails) {
|
|
252
|
+
displayDeveloperError(error, enhanced, context);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
// For marketer/balanced profile: show friendly error
|
|
256
|
+
displayFriendlyError(enhanced, context);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Display friendly error message (marketer/balanced profile)
|
|
261
|
+
*/
|
|
262
|
+
function displayFriendlyError(enhanced, context) {
|
|
263
|
+
// Main error message
|
|
264
|
+
console.error(chalk.red('✗ Oops!'), chalk.bold(enhanced.message));
|
|
265
|
+
// Suggestion
|
|
266
|
+
if (enhanced.suggestion) {
|
|
267
|
+
console.error(chalk.yellow('\n→'), enhanced.suggestion);
|
|
268
|
+
}
|
|
269
|
+
// Help text
|
|
270
|
+
if (enhanced.help) {
|
|
271
|
+
console.error(chalk.gray('\n💡 What you can do:'));
|
|
272
|
+
enhanced.help.split('\n').forEach(line => {
|
|
273
|
+
console.error(chalk.gray(' ' + line));
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
// Docs URL
|
|
277
|
+
if (enhanced.docsUrl) {
|
|
278
|
+
console.error(chalk.cyan('\n📖 Learn more:'), enhanced.docsUrl);
|
|
279
|
+
}
|
|
280
|
+
console.error(''); // Empty line for spacing
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Display technical error message (developer profile)
|
|
284
|
+
*/
|
|
285
|
+
function displayDeveloperError(error, enhanced, context) {
|
|
286
|
+
const parsed = parseApiError(error);
|
|
287
|
+
// Main error message
|
|
288
|
+
console.error(chalk.red('Error:'), enhanced.message);
|
|
289
|
+
// Status code and details
|
|
290
|
+
if (parsed.statusCode) {
|
|
291
|
+
console.error(chalk.gray(`Status: ${parsed.statusCode}`));
|
|
292
|
+
}
|
|
293
|
+
if (context.operation) {
|
|
294
|
+
console.error(chalk.gray(`Operation: ${context.operation}`));
|
|
295
|
+
}
|
|
296
|
+
if (context.resource && context.resourceId) {
|
|
297
|
+
console.error(chalk.gray(`Resource: ${context.resource} (${context.resourceId})`));
|
|
298
|
+
}
|
|
299
|
+
// API response details
|
|
300
|
+
if (parsed.details) {
|
|
301
|
+
console.error(chalk.gray('\nAPI Response:'));
|
|
302
|
+
console.error(chalk.gray(JSON.stringify(parsed.details, null, 2)));
|
|
303
|
+
}
|
|
304
|
+
// Suggestion
|
|
305
|
+
if (enhanced.suggestion) {
|
|
306
|
+
console.error(chalk.yellow('\nSuggestion:'), enhanced.suggestion);
|
|
307
|
+
}
|
|
308
|
+
// Help text
|
|
309
|
+
if (enhanced.help) {
|
|
310
|
+
console.error(chalk.gray('\nHelp:'));
|
|
311
|
+
enhanced.help.split('\n').forEach(line => {
|
|
312
|
+
console.error(chalk.gray(' ' + line));
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
console.error(''); // Empty line for spacing
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Validation helpers
|
|
319
|
+
*/
|
|
320
|
+
export const validate = {
|
|
321
|
+
/**
|
|
322
|
+
* Validate email format
|
|
323
|
+
*/
|
|
324
|
+
email(email) {
|
|
325
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
326
|
+
if (!emailRegex.test(email)) {
|
|
327
|
+
return {
|
|
328
|
+
valid: false,
|
|
329
|
+
error: `Invalid email format: ${email}`
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
return { valid: true };
|
|
333
|
+
},
|
|
334
|
+
/**
|
|
335
|
+
* Validate ID is a positive integer
|
|
336
|
+
*/
|
|
337
|
+
id(id, resourceName = 'ID') {
|
|
338
|
+
const numId = typeof id === 'string' ? parseInt(id, 10) : id;
|
|
339
|
+
if (isNaN(numId) || numId <= 0) {
|
|
340
|
+
return {
|
|
341
|
+
valid: false,
|
|
342
|
+
error: `${resourceName} must be a positive number, got: ${id}`
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
return { valid: true };
|
|
346
|
+
},
|
|
347
|
+
/**
|
|
348
|
+
* Validate date format (YYYY-MM-DD)
|
|
349
|
+
*/
|
|
350
|
+
date(date) {
|
|
351
|
+
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
|
352
|
+
if (!dateRegex.test(date)) {
|
|
353
|
+
return {
|
|
354
|
+
valid: false,
|
|
355
|
+
error: `Invalid date format: ${date}. Expected: YYYY-MM-DD (e.g., 2025-01-15)`
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const parsed = new Date(date);
|
|
359
|
+
if (isNaN(parsed.getTime())) {
|
|
360
|
+
return {
|
|
361
|
+
valid: false,
|
|
362
|
+
error: `Invalid date: ${date}`
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return { valid: true };
|
|
366
|
+
},
|
|
367
|
+
/**
|
|
368
|
+
* Validate required field
|
|
369
|
+
*/
|
|
370
|
+
required(value, fieldName) {
|
|
371
|
+
if (value === undefined || value === null || value === '') {
|
|
372
|
+
return {
|
|
373
|
+
valid: false,
|
|
374
|
+
error: `${fieldName} is required`
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return { valid: true };
|
|
378
|
+
},
|
|
379
|
+
/**
|
|
380
|
+
* Validate JSON string
|
|
381
|
+
*/
|
|
382
|
+
json(jsonString, fieldName = 'JSON') {
|
|
383
|
+
try {
|
|
384
|
+
const parsed = JSON.parse(jsonString);
|
|
385
|
+
return { valid: true, parsed };
|
|
386
|
+
}
|
|
387
|
+
catch (e) {
|
|
388
|
+
return {
|
|
389
|
+
valid: false,
|
|
390
|
+
error: `Invalid ${fieldName}: ${e.message}`
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
//# sourceMappingURL=errors.js.map
|