@cakemail-org/cakemail-cli 1.5.0 → 2.0.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.
Files changed (234) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/.env.example +40 -0
  3. package/.env.test.example +45 -0
  4. package/CHANGELOG.md +1031 -0
  5. package/README.md +319 -15
  6. package/audit-formats.js +128 -0
  7. package/cakemail.rb +20 -0
  8. package/dist/cli.js +27 -10
  9. package/dist/cli.js.map +1 -1
  10. package/dist/client.d.ts +2 -0
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +16 -6
  13. package/dist/client.js.map +1 -1
  14. package/dist/commands/account.js +1 -1
  15. package/dist/commands/account.js.map +1 -1
  16. package/dist/commands/attributes.js +1 -1
  17. package/dist/commands/attributes.js.map +1 -1
  18. package/dist/commands/campaigns.d.ts.map +1 -1
  19. package/dist/commands/campaigns.js +103 -8
  20. package/dist/commands/campaigns.js.map +1 -1
  21. package/dist/commands/config.d.ts.map +1 -1
  22. package/dist/commands/config.js +63 -4
  23. package/dist/commands/config.js.map +1 -1
  24. package/dist/commands/contacts.d.ts.map +1 -1
  25. package/dist/commands/contacts.js +91 -12
  26. package/dist/commands/contacts.js.map +1 -1
  27. package/dist/commands/emails.js +1 -1
  28. package/dist/commands/emails.js.map +1 -1
  29. package/dist/commands/interests.d.ts +5 -0
  30. package/dist/commands/interests.d.ts.map +1 -0
  31. package/dist/commands/interests.js +172 -0
  32. package/dist/commands/interests.js.map +1 -0
  33. package/dist/commands/lists.d.ts.map +1 -1
  34. package/dist/commands/lists.js +6 -8
  35. package/dist/commands/lists.js.map +1 -1
  36. package/dist/commands/logs.d.ts +5 -0
  37. package/dist/commands/logs.d.ts.map +1 -0
  38. package/dist/commands/logs.js +237 -0
  39. package/dist/commands/logs.js.map +1 -0
  40. package/dist/commands/reports.js +1 -1
  41. package/dist/commands/reports.js.map +1 -1
  42. package/dist/commands/segments.js +1 -1
  43. package/dist/commands/segments.js.map +1 -1
  44. package/dist/commands/senders.d.ts.map +1 -1
  45. package/dist/commands/senders.js +11 -8
  46. package/dist/commands/senders.js.map +1 -1
  47. package/dist/commands/suppressed.js +1 -1
  48. package/dist/commands/suppressed.js.map +1 -1
  49. package/dist/commands/tags.d.ts +5 -0
  50. package/dist/commands/tags.d.ts.map +1 -0
  51. package/dist/commands/tags.js +124 -0
  52. package/dist/commands/tags.js.map +1 -0
  53. package/dist/commands/templates.js +1 -1
  54. package/dist/commands/templates.js.map +1 -1
  55. package/dist/commands/transactional-templates.d.ts +5 -0
  56. package/dist/commands/transactional-templates.d.ts.map +1 -0
  57. package/dist/commands/transactional-templates.js +354 -0
  58. package/dist/commands/transactional-templates.js.map +1 -0
  59. package/dist/commands/webhooks.js +1 -1
  60. package/dist/commands/webhooks.js.map +1 -1
  61. package/dist/utils/auth.d.ts +8 -1
  62. package/dist/utils/auth.d.ts.map +1 -1
  63. package/dist/utils/auth.js +39 -11
  64. package/dist/utils/auth.js.map +1 -1
  65. package/dist/utils/config-file.d.ts +7 -0
  66. package/dist/utils/config-file.d.ts.map +1 -1
  67. package/dist/utils/config-file.js +15 -0
  68. package/dist/utils/config-file.js.map +1 -1
  69. package/dist/utils/config.d.ts +2 -0
  70. package/dist/utils/config.d.ts.map +1 -1
  71. package/dist/utils/config.js +12 -4
  72. package/dist/utils/config.js.map +1 -1
  73. package/dist/utils/errors.js +1 -1
  74. package/dist/utils/errors.js.map +1 -1
  75. package/dist/utils/list-defaults.d.ts +33 -0
  76. package/dist/utils/list-defaults.d.ts.map +1 -0
  77. package/dist/utils/list-defaults.js +52 -0
  78. package/dist/utils/list-defaults.js.map +1 -0
  79. package/dist/utils/output.d.ts.map +1 -1
  80. package/dist/utils/output.js +36 -13
  81. package/dist/utils/output.js.map +1 -1
  82. package/dist/utils/progress.d.ts.map +1 -1
  83. package/dist/utils/progress.js +32 -4
  84. package/dist/utils/progress.js.map +1 -1
  85. package/dist/utils/spinner.d.ts +17 -0
  86. package/dist/utils/spinner.d.ts.map +1 -0
  87. package/dist/utils/spinner.js +43 -0
  88. package/dist/utils/spinner.js.map +1 -0
  89. package/docs/DOCUMENTATION-STANDARD.md +1068 -0
  90. package/docs/README.md +161 -0
  91. package/docs/developer/ARCHITECTURE.md +516 -0
  92. package/docs/developer/AUTH.md +204 -0
  93. package/docs/developer/CONTRIBUTING.md +227 -0
  94. package/docs/developer/DOCUMENTATION_SUMMARY.md +346 -0
  95. package/docs/developer/PROJECT_INDEX.md +365 -0
  96. package/docs/planning/API_COVERAGE.md +1045 -0
  97. package/docs/planning/BACKLOG.md +1159 -0
  98. package/docs/planning/PROFILE_SYSTEM_TASKS.md +287 -0
  99. package/docs/planning/UX_IMPLEMENTATION_PLAN.md +691 -0
  100. package/docs/planning/archive/RELEASE_CHECKLIST_v1.3.0.md +332 -0
  101. package/docs/planning/archive/RELEASE_v1.3.0.md +428 -0
  102. package/docs/planning/archive/cakemail-cli-ux-improvements.md +438 -0
  103. package/docs/planning/cakemail-profile-system-plan.md +1121 -0
  104. package/docs/testing/AI_USER_SIMULATION_DESIGN.md +1342 -0
  105. package/docs/testing/KENOGAMI_BIDIRECTIONAL_FLOW.md +1517 -0
  106. package/docs/testing/KENOGAMI_TRUTH_RECONCILIATION_SYSTEM.md +1369 -0
  107. package/docs/user-manual/.obsidian/app.json +1 -0
  108. package/docs/user-manual/.obsidian/appearance.json +1 -0
  109. package/docs/user-manual/.obsidian/core-plugins.json +33 -0
  110. package/docs/user-manual/.obsidian/workspace.json +167 -0
  111. package/docs/user-manual/01-getting-started/01-installation.md +214 -0
  112. package/docs/user-manual/01-getting-started/02-quick-start.md +432 -0
  113. package/docs/user-manual/01-getting-started/03-authentication.md +448 -0
  114. package/docs/user-manual/01-getting-started/04-configuration.md +430 -0
  115. package/docs/user-manual/01-getting-started/05-output-formats.md +447 -0
  116. package/docs/user-manual/02-core-concepts/01-accounts.md +514 -0
  117. package/docs/user-manual/02-core-concepts/02-profile-system.md +771 -0
  118. package/docs/user-manual/02-core-concepts/03-smart-defaults.md +485 -0
  119. package/docs/user-manual/02-core-concepts/04-authentication-methods.md +435 -0
  120. package/docs/user-manual/02-core-concepts/05-pagination-filtering.md +600 -0
  121. package/docs/user-manual/02-core-concepts/06-error-handling.md +718 -0
  122. package/docs/user-manual/02-core-concepts/07-api-coverage.md +483 -0
  123. package/docs/user-manual/03-email-operations/01-senders.md +490 -0
  124. package/docs/user-manual/03-email-operations/02-templates.md +444 -0
  125. package/docs/user-manual/03-email-operations/03-transactional-emails.md +706 -0
  126. package/docs/user-manual/03-email-operations/04-email-tracking.md +407 -0
  127. package/docs/user-manual/04-campaign-management/01-campaigns-basics.md +394 -0
  128. package/docs/user-manual/04-campaign-management/02-campaign-scheduling.md +630 -0
  129. package/docs/user-manual/04-campaign-management/03-campaign-testing.md +997 -0
  130. package/docs/user-manual/04-campaign-management/04-campaign-lifecycle.md +709 -0
  131. package/docs/user-manual/04-campaign-management/05-campaign-links.md +934 -0
  132. package/docs/user-manual/05-contact-management/01-lists.md +836 -0
  133. package/docs/user-manual/05-contact-management/02-contacts.md +1035 -0
  134. package/docs/user-manual/05-contact-management/03-custom-attributes.md +788 -0
  135. package/docs/user-manual/05-contact-management/04-segments.md +1028 -0
  136. package/docs/user-manual/05-contact-management/05-contact-import-export.md +1031 -0
  137. package/docs/user-manual/06-analytics-reporting/01-campaign-analytics.md +867 -0
  138. package/docs/user-manual/06-analytics-reporting/02-account-reports.md +227 -0
  139. package/docs/user-manual/07-integrations/01-webhooks-integration.md +259 -0
  140. package/docs/user-manual/07-integrations/02-automation.md +326 -0
  141. package/docs/user-manual/08-advanced-usage/01-scripting-patterns.md +672 -0
  142. package/docs/user-manual/08-advanced-usage/02-bulk-operations.md +932 -0
  143. package/docs/user-manual/08-advanced-usage/03-ci-cd-integration.md +892 -0
  144. package/docs/user-manual/08-advanced-usage/04-performance-optimization.md +766 -0
  145. package/docs/user-manual/09-command-reference/01-config.md +776 -0
  146. package/docs/user-manual/09-command-reference/02-account.md +652 -0
  147. package/docs/user-manual/09-command-reference/03-lists.md +958 -0
  148. package/docs/user-manual/09-command-reference/04-contacts.md +1408 -0
  149. package/docs/user-manual/09-command-reference/05-attributes.md +617 -0
  150. package/docs/user-manual/09-command-reference/06-segments.md +894 -0
  151. package/docs/user-manual/09-command-reference/07-senders.md +803 -0
  152. package/docs/user-manual/09-command-reference/08-templates.md +818 -0
  153. package/docs/user-manual/09-command-reference/09-campaigns.md +1250 -0
  154. package/docs/user-manual/09-command-reference/10-emails.md +807 -0
  155. package/docs/user-manual/09-command-reference/11-reports.md +1135 -0
  156. package/docs/user-manual/09-command-reference/12-webhooks.md +773 -0
  157. package/docs/user-manual/09-command-reference/13-suppressed.md +797 -0
  158. package/docs/user-manual/09-command-reference/14-interests.md +630 -0
  159. package/docs/user-manual/09-command-reference/15-tags.md +584 -0
  160. package/docs/user-manual/09-command-reference/16-logs.md +656 -0
  161. package/docs/user-manual/09-command-reference/17-transactional-templates.md +850 -0
  162. package/docs/user-manual/10-troubleshooting/01-common-errors.md +457 -0
  163. package/docs/user-manual/10-troubleshooting/02-authentication-issues.md +558 -0
  164. package/docs/user-manual/10-troubleshooting/03-connection-problems.md +634 -0
  165. package/docs/user-manual/10-troubleshooting/04-debugging.md +725 -0
  166. package/docs/user-manual/11-appendix/04-faq.md +484 -0
  167. package/docs/user-manual/11-appendix/05-glossary.md +250 -0
  168. package/docs/user-manual/README.md +0 -0
  169. package/package.json +13 -47
  170. package/src/cli.ts +125 -0
  171. package/src/client.ts +16 -0
  172. package/src/commands/account.ts +267 -0
  173. package/src/commands/accounts.ts +78 -0
  174. package/src/commands/actions.ts +249 -0
  175. package/src/commands/attributes.ts +139 -0
  176. package/src/commands/campaign-blueprints.ts +106 -0
  177. package/src/commands/campaigns.ts +469 -0
  178. package/src/commands/config.ts +77 -0
  179. package/src/commands/contacts.ts +612 -0
  180. package/src/commands/custom-attributes.ts +127 -0
  181. package/src/commands/dkims.ts +117 -0
  182. package/src/commands/domains.ts +82 -0
  183. package/src/commands/email-apis.ts +569 -0
  184. package/src/commands/emails.ts +197 -0
  185. package/src/commands/forms.ts +283 -0
  186. package/src/commands/interests.ts +155 -0
  187. package/src/commands/links.ts +38 -0
  188. package/src/commands/lists.ts +406 -0
  189. package/src/commands/logos.ts +71 -0
  190. package/src/commands/logs.ts +386 -0
  191. package/src/commands/reports.ts +306 -0
  192. package/src/commands/segments.ts +158 -0
  193. package/src/commands/senders.ts +204 -0
  194. package/src/commands/sub-accounts.ts +271 -0
  195. package/src/commands/suppressed-emails.ts +234 -0
  196. package/src/commands/suppressed.ts +198 -0
  197. package/src/commands/system-emails.ts +85 -0
  198. package/src/commands/tags.ts +146 -0
  199. package/src/commands/tasks.ts +116 -0
  200. package/src/commands/templates.ts +189 -0
  201. package/src/commands/tokens.ts +83 -0
  202. package/src/commands/transactional-emails.ts +374 -0
  203. package/src/commands/transactional-templates.ts +385 -0
  204. package/src/commands/users.ts +506 -0
  205. package/src/commands/webhooks.ts +172 -0
  206. package/src/commands/workflow-blueprints.ts +123 -0
  207. package/src/commands/workflows.ts +265 -0
  208. package/src/types/profile.ts +93 -0
  209. package/src/utils/auth.ts +272 -0
  210. package/src/utils/config-file.ts +96 -0
  211. package/src/utils/config.ts +134 -0
  212. package/src/utils/confirm.ts +32 -0
  213. package/src/utils/defaults.ts +99 -0
  214. package/src/utils/errors.ts +116 -0
  215. package/src/utils/interactive.ts +91 -0
  216. package/src/utils/list-defaults.ts +74 -0
  217. package/src/utils/output.ts +190 -0
  218. package/src/utils/progress.ts +320 -0
  219. package/src/utils/spinner.ts +22 -0
  220. package/tests/IMPLEMENTATION_STATUS.md +258 -0
  221. package/tests/PTY_SETUP.md +118 -0
  222. package/tests/PTY_TESTING_GUIDE.md +507 -0
  223. package/tests/README.md +244 -0
  224. package/tests/fixtures/api-responses/campaigns.json +34 -0
  225. package/tests/fixtures/test-config.json +13 -0
  226. package/tests/helpers/cli-runner.ts +128 -0
  227. package/tests/helpers/mock-server.ts +301 -0
  228. package/tests/helpers/pty-runner.ts +181 -0
  229. package/tests/integration/campaigns-real-api.test.ts +196 -0
  230. package/tests/integration/setup-integration.ts +50 -0
  231. package/tests/pty/campaigns.test.ts +241 -0
  232. package/tests/setup.ts +34 -0
  233. package/tsconfig.json +15 -0
  234. package/vitest.config.ts +28 -0
@@ -0,0 +1,320 @@
1
+ import ora, { Ora } from 'ora';
2
+ import chalk from 'chalk';
3
+
4
+ /**
5
+ * Progress indicator utilities for long-running operations
6
+ */
7
+
8
+ export interface ProgressConfig {
9
+ text: string;
10
+ prefixText?: string;
11
+ }
12
+
13
+ export interface PollingConfig {
14
+ checkInterval?: number; // milliseconds between checks
15
+ maxAttempts?: number; // max polling attempts before timeout
16
+ onCheck?: (attempt: number) => void;
17
+ }
18
+
19
+ export interface BatchProgressConfig {
20
+ total: number;
21
+ current: number;
22
+ operation: string;
23
+ }
24
+
25
+ /**
26
+ * Check if we should disable spinners (batch mode, CI, testing)
27
+ */
28
+ function shouldDisableSpinner(): boolean {
29
+ // If CAKEMAIL_BATCH_MODE is explicitly set, respect it
30
+ if (process.env.CAKEMAIL_BATCH_MODE === 'false') {
31
+ return false; // Explicitly enabled
32
+ }
33
+ if (process.env.CAKEMAIL_BATCH_MODE === 'true') {
34
+ return true; // Explicitly disabled
35
+ }
36
+
37
+ // Otherwise, disable in CI and test environments
38
+ return !!(
39
+ process.env.CI === 'true' ||
40
+ process.env.NODE_ENV === 'test'
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Simple spinner for basic operations (existing pattern)
46
+ */
47
+ export function createSpinner(text: string): Ora {
48
+ const spinner = ora({
49
+ text,
50
+ isSilent: shouldDisableSpinner() // isSilent suppresses all output
51
+ });
52
+ return spinner.start();
53
+ }
54
+
55
+ /**
56
+ * Progress indicator for async operations with polling
57
+ * Use for exports, imports, and other async tasks
58
+ */
59
+ export class PollingProgress {
60
+ private spinner: Ora;
61
+ private startTime: number;
62
+ private attempts: number = 0;
63
+ private config: Required<PollingConfig>;
64
+
65
+ constructor(initialText: string, config: PollingConfig = {}) {
66
+ this.config = {
67
+ checkInterval: config.checkInterval || 2000,
68
+ maxAttempts: config.maxAttempts || 30,
69
+ onCheck: config.onCheck || (() => {}),
70
+ };
71
+
72
+ this.spinner = ora({
73
+ text: initialText,
74
+ isSilent: shouldDisableSpinner()
75
+ }).start();
76
+ this.startTime = Date.now();
77
+ }
78
+
79
+ /**
80
+ * Update progress text during polling
81
+ */
82
+ update(text: string): void {
83
+ this.attempts++;
84
+ const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
85
+ this.spinner.text = `${text} ${chalk.gray(`(${elapsed}s, attempt ${this.attempts})`)}`;
86
+ this.config.onCheck(this.attempts);
87
+ }
88
+
89
+ /**
90
+ * Check if max attempts reached
91
+ */
92
+ isTimeout(): boolean {
93
+ return this.attempts >= this.config.maxAttempts;
94
+ }
95
+
96
+ /**
97
+ * Get check interval
98
+ */
99
+ getInterval(): number {
100
+ return this.config.checkInterval;
101
+ }
102
+
103
+ /**
104
+ * Mark as successful
105
+ */
106
+ succeed(text?: string): void {
107
+ const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
108
+ this.spinner.succeed(`${text || this.spinner.text} ${chalk.gray(`(${elapsed}s)`)}`);
109
+ }
110
+
111
+ /**
112
+ * Mark as failed
113
+ */
114
+ fail(text?: string): void {
115
+ this.spinner.fail(text || this.spinner.text);
116
+ }
117
+
118
+ /**
119
+ * Stop without status
120
+ */
121
+ stop(): void {
122
+ this.spinner.stop();
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Progress indicator for batch operations
128
+ * Use for bulk tagging, multiple deletes, etc.
129
+ */
130
+ export class BatchProgress {
131
+ private spinner: Ora;
132
+ private total: number;
133
+ private current: number = 0;
134
+ private operation: string;
135
+ private startTime: number;
136
+ private failures: number = 0;
137
+
138
+ constructor(config: BatchProgressConfig) {
139
+ this.total = config.total;
140
+ this.current = config.current || 0;
141
+ this.operation = config.operation;
142
+ this.startTime = Date.now();
143
+
144
+ this.spinner = ora({
145
+ text: this.formatText(),
146
+ isSilent: shouldDisableSpinner()
147
+ }).start();
148
+ }
149
+
150
+ private formatText(): string {
151
+ const percentage = this.total > 0 ? Math.round((this.current / this.total) * 100) : 0;
152
+ const bar = this.createProgressBar(percentage);
153
+ return `${this.operation} ${bar} ${this.current}/${this.total} ${chalk.gray(`(${percentage}%)`)}`;
154
+ }
155
+
156
+ private createProgressBar(percentage: number): string {
157
+ const width = 20;
158
+ const filled = Math.round((percentage / 100) * width);
159
+ const empty = width - filled;
160
+ return chalk.cyan('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
161
+ }
162
+
163
+ /**
164
+ * Increment progress
165
+ */
166
+ increment(count: number = 1): void {
167
+ this.current = Math.min(this.current + count, this.total);
168
+ this.spinner.text = this.formatText();
169
+ }
170
+
171
+ /**
172
+ * Record a failure
173
+ */
174
+ recordFailure(): void {
175
+ this.failures++;
176
+ }
177
+
178
+ /**
179
+ * Mark as complete
180
+ */
181
+ complete(successMessage?: string): void {
182
+ const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
183
+ const message = successMessage || this.operation;
184
+
185
+ if (this.failures > 0) {
186
+ this.spinner.warn(
187
+ `${message} completed with ${this.failures} error${this.failures > 1 ? 's' : ''} ` +
188
+ chalk.gray(`(${elapsed}s)`)
189
+ );
190
+ } else {
191
+ this.spinner.succeed(
192
+ `${message} completed successfully ` +
193
+ chalk.gray(`(${this.total} items, ${elapsed}s)`)
194
+ );
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Mark as failed
200
+ */
201
+ fail(errorMessage?: string): void {
202
+ this.spinner.fail(errorMessage || `${this.operation} failed`);
203
+ }
204
+
205
+ /**
206
+ * Stop without status
207
+ */
208
+ stop(): void {
209
+ this.spinner.stop();
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Multi-step progress indicator
215
+ * Use for operations with distinct phases
216
+ */
217
+ export class MultiStepProgress {
218
+ private spinner: Ora;
219
+ private steps: string[];
220
+ private currentStep: number = 0;
221
+ private startTime: number;
222
+
223
+ constructor(steps: string[]) {
224
+ this.steps = steps;
225
+ this.startTime = Date.now();
226
+ this.spinner = ora({
227
+ text: this.formatText(),
228
+ isSilent: shouldDisableSpinner()
229
+ }).start();
230
+ }
231
+
232
+ private formatText(): string {
233
+ const step = this.steps[this.currentStep];
234
+ const progress = `${this.currentStep + 1}/${this.steps.length}`;
235
+ return `${step} ${chalk.gray(`[${progress}]`)}`;
236
+ }
237
+
238
+ /**
239
+ * Move to next step
240
+ */
241
+ nextStep(): void {
242
+ if (this.currentStep < this.steps.length - 1) {
243
+ this.currentStep++;
244
+ this.spinner.text = this.formatText();
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Update current step text
250
+ */
251
+ updateStepText(text: string): void {
252
+ this.steps[this.currentStep] = text;
253
+ this.spinner.text = this.formatText();
254
+ }
255
+
256
+ /**
257
+ * Mark as complete
258
+ */
259
+ complete(finalMessage?: string): void {
260
+ const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
261
+ this.spinner.succeed(`${finalMessage || 'All steps completed'} ${chalk.gray(`(${elapsed}s)`)}`);
262
+ }
263
+
264
+ /**
265
+ * Mark as failed
266
+ */
267
+ fail(errorMessage?: string): void {
268
+ this.spinner.fail(errorMessage || `Failed at step ${this.currentStep + 1}`);
269
+ }
270
+
271
+ /**
272
+ * Stop without status
273
+ */
274
+ stop(): void {
275
+ this.spinner.stop();
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Helper to wait with a delay (for polling)
281
+ */
282
+ export function sleep(ms: number): Promise<void> {
283
+ return new Promise(resolve => setTimeout(resolve, ms));
284
+ }
285
+
286
+ /**
287
+ * Poll an async operation until complete
288
+ * @param checkFn Function that returns status: { complete: boolean, status?: string, progress?: number }
289
+ * @param config Polling configuration
290
+ */
291
+ export async function pollUntilComplete<T>(
292
+ checkFn: () => Promise<{ complete: boolean; status?: string; data?: T }>,
293
+ initialText: string,
294
+ config: PollingConfig = {}
295
+ ): Promise<T> {
296
+ const progress = new PollingProgress(initialText, config);
297
+
298
+ while (true) {
299
+ await sleep(progress.getInterval());
300
+
301
+ try {
302
+ const result = await checkFn();
303
+
304
+ if (result.complete) {
305
+ progress.succeed(result.status || 'Operation completed');
306
+ return result.data as T;
307
+ }
308
+
309
+ if (progress.isTimeout()) {
310
+ progress.fail('Operation timed out');
311
+ throw new Error('Operation timed out');
312
+ }
313
+
314
+ progress.update(result.status || initialText);
315
+ } catch (error: any) {
316
+ progress.fail('Operation failed');
317
+ throw error;
318
+ }
319
+ }
320
+ }
@@ -0,0 +1,22 @@
1
+ // ABOUTME: Profile-aware spinner wrapper.
2
+ // ABOUTME: Disables spinners based on profile settings and batch mode.
3
+
4
+ import ora, { type Ora } from 'ora';
5
+ import type { ProfileConfig } from '../types/profile.js';
6
+
7
+ const noopSpinner = {
8
+ start() { return this as any; },
9
+ stop() { return this as any; },
10
+ succeed(text?: string) { if (text) console.log(text); return this as any; },
11
+ fail(text?: string) { if (text) console.error(text); return this as any; },
12
+ } as Ora;
13
+
14
+ export function createSpinner(text: string, profileConfig?: ProfileConfig): Ora {
15
+ const showProgress = profileConfig?.behavior.show_progress ?? true;
16
+
17
+ if (!showProgress || !process.stdout.isTTY || process.argv.includes('--batch')) {
18
+ return noopSpinner;
19
+ }
20
+
21
+ return ora(text);
22
+ }
@@ -0,0 +1,258 @@
1
+ # Test Suite Implementation Status
2
+
3
+ ## ✅ What We've Built (Phase 1 Complete)
4
+
5
+ ### Infrastructure
6
+ - ✅ Vitest test framework configured
7
+ - ✅ Test directory structure created
8
+ - ✅ Helper utilities (CLI runner, API mocking)
9
+ - ✅ Environment configuration (.env.test.example)
10
+ - ✅ NPM test scripts
11
+
12
+ ### Test Files Created
13
+ 1. **Mocked E2E Tests** (20 tests)
14
+ - `tests/e2e/campaigns.test.ts`
15
+ - Tests: list, get, create, update, delete, profiles
16
+
17
+ 2. **Integration Tests** (6 tests)
18
+ - `tests/integration/campaigns-real-api.test.ts`
19
+ - Tests: full lifecycle with real API calls
20
+
21
+ 3. **Test Helpers**
22
+ - `tests/helpers/cli-runner.ts` - Execute CLI commands
23
+ - `tests/helpers/mock-api.ts` - Mock API responses
24
+ - `tests/setup.ts` - Global test setup
25
+ - `tests/integration/setup-integration.ts` - Integration test setup
26
+
27
+ 4. **Documentation**
28
+ - `tests/README.md` - Complete test guide
29
+ - `.env.test.example` - Environment template
30
+ - `tests/IMPLEMENTATION_STATUS.md` - This file
31
+
32
+ ### Files Modified
33
+ - `package.json` - Added test dependencies and scripts
34
+ - `vitest.config.ts` - Test configuration
35
+
36
+ ---
37
+
38
+ ## 🟡 Current Status: Mocked Tests Need SDK Fix
39
+
40
+ ### The Challenge
41
+
42
+ The mocked tests are failing with "Request configuration error" because:
43
+
44
+ 1. The CLI uses `@cakemail-org/cakemail-sdk`
45
+ 2. The SDK requires email/password for initialization
46
+ 3. The SDK makes authentication calls before any command
47
+ 4. Our nock mocks aren't intercepting the SDK's axios requests properly
48
+
49
+ ### Why This Happens
50
+
51
+ ```
52
+ User runs: cakemail campaigns list
53
+
54
+ CLI initializes SDK with email/password
55
+
56
+ SDK tries to authenticate → POST /auth/login
57
+
58
+ Nock should intercept but doesn't properly
59
+
60
+ SDK fails with "Request configuration error"
61
+ ```
62
+
63
+ ---
64
+
65
+ ## ✅ Good News: Integration Tests Work!
66
+
67
+ The **integration test suite is fully functional** and ready to use:
68
+
69
+ ```bash
70
+ # Setup (one time)
71
+ cp .env.test.example .env.test
72
+ # Edit .env.test with real test credentials
73
+
74
+ # Run integration tests
75
+ npm run test:integration
76
+ ```
77
+
78
+ These tests:
79
+ - ✅ Make real API calls
80
+ - ✅ Test full authentication flow
81
+ - ✅ Test actual data creation/modification
82
+ - ✅ Provide real confidence
83
+
84
+ ---
85
+
86
+ ## 🎯 Recommended Path Forward
87
+
88
+ ### Option A: Use Integration Tests Only (Simplest)
89
+ **Effort**: None - already complete
90
+ **Benefit**: Real API testing that actually works
91
+
92
+ ```bash
93
+ # Just use the integration tests
94
+ npm run test:integration
95
+ ```
96
+
97
+ **Pros**:
98
+ - Already working
99
+ - Tests real API interaction
100
+ - No mocking complexity
101
+
102
+ **Cons**:
103
+ - Slower (network calls)
104
+ - Requires test account
105
+ - Limited error scenario testing
106
+
107
+ ---
108
+
109
+ ### Option B: Fix Mocked Tests (More Work)
110
+ **Effort**: 1-2 days
111
+ **Benefit**: Fast mocked tests + integration tests
112
+
113
+ **What needs to be done**:
114
+ 1. Deep-dive into how `@cakemail-org/cakemail-sdk` makes HTTP requests
115
+ 2. Configure nock to intercept at the right level (axios adapters)
116
+ 3. Mock the full authentication flow properly
117
+ 4. Test with SDK source code inspection
118
+
119
+ **Pros**:
120
+ - Fast test execution
121
+ - Can test all error scenarios
122
+ - No API account needed for development
123
+
124
+ **Cons**:
125
+ - Complex setup
126
+ - Mocks can drift from reality
127
+ - Requires maintenance
128
+
129
+ ---
130
+
131
+ ### Option C: Hybrid with Mock SDK (Recommended)
132
+ **Effort**: 2-3 days
133
+ **Benefit**: Best of both worlds
134
+
135
+ Instead of mocking HTTP, mock the SDK itself:
136
+
137
+ ```typescript
138
+ // Create mock SDK for testing
139
+ class MockCakemailSDK {
140
+ campaigns = {
141
+ list: vi.fn().mockResolvedValue({ data: [...] }),
142
+ get: vi.fn().mockResolvedValue({ id: 123 }),
143
+ // ...
144
+ }
145
+ }
146
+
147
+ // Inject into CLI during tests
148
+ ```
149
+
150
+ **Pros**:
151
+ - Mocking at the right level
152
+ - Fast test execution
153
+ - Easy to maintain
154
+ - Integration tests still validate real API
155
+
156
+ **Cons**:
157
+ - Requires code changes to support dependency injection
158
+ - More setup work
159
+
160
+ ---
161
+
162
+ ## 📊 Test Coverage Status
163
+
164
+ | Test Type | Files | Tests | Status |
165
+ |-----------|-------|-------|--------|
166
+ | **Mocked E2E** | 1 | 20 | 🟡 Infrastructure ready, SDK auth issue |
167
+ | **Integration** | 1 | 6 | ✅ Fully working (needs credentials) |
168
+ | **Unit** | 0 | 0 | ⚪ Not started |
169
+ | **Total** | 2 | 26 | 🟡 Partial |
170
+
171
+ ---
172
+
173
+ ## 🚀 Next Steps
174
+
175
+ ### Immediate (To Get Tests Working)
176
+
177
+ **Choose One**:
178
+
179
+ 1. **Use Integration Tests** (Recommended - Already Working)
180
+ ```bash
181
+ cp .env.test.example .env.test
182
+ # Add test credentials
183
+ npm run test:integration
184
+ ```
185
+
186
+ 2. **Fix Mocked Tests** (If fast mocked tests are critical)
187
+ - Debug nock + axios + SDK interaction
188
+ - Or implement Option C (Mock SDK)
189
+
190
+ ### Long-term (Phase 2+)
191
+
192
+ Once we have one working approach:
193
+
194
+ 1. **Expand Coverage**
195
+ - Lists commands (10 tests)
196
+ - Contacts commands (12 tests)
197
+ - Templates commands (8 tests)
198
+ - All other command groups
199
+
200
+ 2. **Add Unit Tests**
201
+ - Test utilities in isolation
202
+ - Test output formatters
203
+ - Test error handlers
204
+
205
+ 3. **CI/CD Integration**
206
+ - GitHub Actions workflow
207
+ - Run tests on every PR
208
+ - Coverage reporting
209
+
210
+ ---
211
+
212
+ ## 💡 My Recommendation
213
+
214
+ **Start with Integration Tests** because:
215
+
216
+ 1. ✅ They already work
217
+ 2. ✅ They test what matters (real API interaction)
218
+ 3. ✅ You can start testing immediately
219
+ 4. ✅ Provides real confidence
220
+
221
+ Then **optionally** add mocked tests later if you need:
222
+ - Faster CI/CD feedback
223
+ - Error scenario testing
224
+ - Development without API access
225
+
226
+ ---
227
+
228
+ ## 📝 Summary
229
+
230
+ **What Works**:
231
+ - ✅ Test infrastructure (Vitest, helpers, config)
232
+ - ✅ Integration tests with real API
233
+ - ✅ Documentation and examples
234
+
235
+ **What Needs Work**:
236
+ - 🟡 Mocked tests (SDK authentication issue)
237
+
238
+ **Recommendation**:
239
+ - Use integration tests now (already working)
240
+ - Optionally fix mocked tests later if needed
241
+
242
+ **Bottom Line**:
243
+ You have a **fully functional integration test suite** ready to use. The mocked tests are a "nice to have" for speed, but not essential for comprehensive API testing.
244
+
245
+ ---
246
+
247
+ ## Questions?
248
+
249
+ 1. **Do you want to use integration tests as-is?**
250
+ → They're ready to go with real API testing
251
+
252
+ 2. **Do you need mocked tests fixed?**
253
+ → I can investigate the SDK + nock issue
254
+
255
+ 3. **Should we expand integration test coverage?**
256
+ → Add more command groups with real API tests
257
+
258
+ Let me know which direction you'd like to go!
@@ -0,0 +1,118 @@
1
+ # PTY Testing Setup Status
2
+
3
+ ## Current Status: ⚠️ Node Version Compatibility Issue
4
+
5
+ PTY tests are **ready to use** but currently cannot run due to a Node.js version compatibility issue.
6
+
7
+ ### The Issue
8
+
9
+ - **Your Node Version**: v24.10.0
10
+ - **node-pty Compatibility**: Supports Node 18.x and 20.x only
11
+ - **Error**: node-pty fails to compile on Node 24.x (too new)
12
+
13
+ ### Solution Options
14
+
15
+ #### Option 1: Switch to Node 20 (Recommended)
16
+
17
+ ```bash
18
+ # Install Node 20 LTS
19
+ nvm install 20
20
+ nvm use 20
21
+
22
+ # Reinstall dependencies
23
+ rm -rf node_modules package-lock.json
24
+ npm install
25
+
26
+ # Run PTY tests
27
+ npm run test:pty
28
+ ```
29
+
30
+ #### Option 2: Use Node 18 LTS
31
+
32
+ ```bash
33
+ # Install Node 18 LTS
34
+ nvm install 18
35
+ nvm use 18
36
+
37
+ # Reinstall dependencies
38
+ rm -rf node_modules package-lock.json
39
+ npm install
40
+
41
+ # Run PTY tests
42
+ npm run test:pty
43
+ ```
44
+
45
+ #### Option 3: Wait for node-pty Update
46
+
47
+ Monitor the node-pty repository for Node 24 support:
48
+ https://github.com/microsoft/node-pty/issues
49
+
50
+ ### What Works Now
51
+
52
+ ✅ **Integration Tests**: Tests that make real API calls work perfectly
53
+ ```bash
54
+ npm run test:integration # ✅ Works (5 tests passing)
55
+ ```
56
+
57
+ ✅ **CLI Build**: CLI compiles and runs successfully
58
+ ```bash
59
+ npm run build # ✅ Works
60
+ ./dist/cli.js campaigns list # ✅ Works
61
+ ```
62
+
63
+ ✅ **Mock Server**: Express-based mock server is ready (tests/helpers/mock-server.ts)
64
+
65
+ ✅ **PTY Helpers**: Helper functions are ready (tests/helpers/pty-runner.ts)
66
+
67
+ ✅ **Example Tests**: Complete test suite ready (tests/pty/campaigns.test.ts)
68
+
69
+ ### What's Ready But Can't Run
70
+
71
+ ⏳ **PTY Tests**: 13 tests in tests/pty/campaigns.test.ts
72
+ - Tests real terminal simulation
73
+ - Tests colors, spinners, formatting
74
+ - Tests all output profiles
75
+ - Needs node-pty to run
76
+
77
+ ### Testing Strategy Until PTY Works
78
+
79
+ 1. **For CLI functionality**: Use integration tests (real API)
80
+ 2. **For output formatting**: Test manually with real CLI
81
+ 3. **For CI/CD**: Use Node 20 in CI environment
82
+
83
+ ```bash
84
+ # In CI (GitHub Actions example)
85
+ - uses: actions/setup-node@v4
86
+ with:
87
+ node-version: '20'
88
+ ```
89
+
90
+ ### File Structure
91
+
92
+ ```
93
+ tests/
94
+ ├── PTY_TESTING_GUIDE.md # Complete guide (keep)
95
+ ├── PTY_SETUP.md # This file (current status)
96
+ ├── setup.ts # Global setup (✅ cleaned)
97
+ ├── helpers/
98
+ │ ├── cli-runner.ts # Subprocess runner (✅ works)
99
+ │ ├── mock-server.ts # Express mock server (✅ ready)
100
+ │ └── pty-runner.ts # PTY helpers (⏳ needs node-pty)
101
+ ├── integration/
102
+ │ ├── setup-integration.ts # Integration setup (✅ cleaned)
103
+ │ └── campaigns-real-api.test.ts # Real API tests (✅ working)
104
+ └── pty/
105
+ └── campaigns.test.ts # PTY tests (⏳ needs node-pty)
106
+ ```
107
+
108
+ ### What Was Cleaned Up
109
+
110
+ ✅ Removed nock-based mocking (doesn't work with subprocess)
111
+ ✅ Removed 147 ineffective e2e tests
112
+ ✅ Removed outdated documentation
113
+ ✅ Simplified test setup files
114
+ ✅ Updated package.json dependencies
115
+
116
+ ---
117
+
118
+ **Next Steps**: Switch to Node 20 to enable PTY testing, or continue with integration tests on Node 24.