@mailmodo/cli 0.0.54-beta.pr56.88 → 0.0.54-beta.pr56.89

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.
@@ -1,12 +1,14 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import { confirm } from '@inquirer/prompts';
3
+ import chalk from 'chalk';
3
4
  import { BaseCommand } from '../../lib/base-command.js';
4
5
  import { API_ENDPOINTS } from '../../lib/constants.js';
6
+ import { MISSING_TEMPLATES } from '../../lib/messages.js';
5
7
  import { buildDeployPayload } from '../../lib/deploy/payload.js';
6
8
  import { logDeploySuccessInstructions, logPreDeploySummary, } from '../../lib/deploy/output.js';
7
9
  import { pauseSequence, resumeSequence, } from '../../lib/deploy/sequence-status.js';
8
10
  import { ensureDomainReady, validateDeploySequence, } from '../../lib/deploy/domain-setup.js';
9
- import { getMissingTemplateIds, handleMissingTemplates, } from '../../lib/deploy/missing-templates.js';
11
+ import { getMissingTemplateIds, handleMissingTemplates, } from '../../lib/templates/missing-templates.js';
10
12
  export default class Deploy extends BaseCommand {
11
13
  static description = 'Deploy, pause, or resume an email sequence';
12
14
  static examples = [
@@ -42,7 +44,9 @@ export default class Deploy extends BaseCommand {
42
44
  const yamlConfig = await this.ensureYaml();
43
45
  const missingIds = getMissingTemplateIds(yamlConfig);
44
46
  if (missingIds.length > 0) {
45
- await handleMissingTemplates(ctx, yamlConfig, missingIds, baseFlags);
47
+ const regenerated = await handleMissingTemplates(ctx, yamlConfig, missingIds, baseFlags);
48
+ if (regenerated)
49
+ ctx.log(`\n ${chalk.green('✓')} ${MISSING_TEMPLATES.REVIEW_HINT}\n`);
46
50
  return;
47
51
  }
48
52
  const domainReady = await ensureDomainReady(ctx, yamlConfig, baseFlags);
@@ -6,6 +6,7 @@ import { BaseCommand } from '../../lib/base-command.js';
6
6
  import { API_ENDPOINTS, PREVIEW_PORT } from '../../lib/constants.js';
7
7
  import { INFO } from '../../lib/messages.js';
8
8
  import { loadTemplate, getEmailStyle, getTemplateFilename, } from '../../lib/yaml-config.js';
9
+ import { handleMissingTemplates } from '../../lib/templates/missing-templates.js';
9
10
  /* eslint-disable camelcase */
10
11
  const SAMPLE_DATA = Object.freeze({
11
12
  app_url: 'https://yourapp.com',
@@ -86,12 +87,28 @@ export default class Preview extends BaseCommand {
86
87
  product_name: yamlConfig.project?.name || 'YourApp', // eslint-disable-line camelcase
87
88
  };
88
89
  const effectiveStyle = getEmailStyle(email.style, yamlConfig.project?.emailStyle);
89
- const templateHtml = await loadTemplate(getTemplateFilename(email.id, email.style, yamlConfig.project?.emailStyle));
90
+ const templateFilename = getTemplateFilename(email.id, email.style, yamlConfig.project?.emailStyle);
91
+ let templateHtml = await loadTemplate(templateFilename);
92
+ if (!templateHtml) {
93
+ await this.ensureAuth();
94
+ const regenCtx = {
95
+ error: (msg) => this.error(msg),
96
+ exit: (code) => this.exit(code),
97
+ log: (msg) => this.log(msg),
98
+ onApiError: (r) => this.handleApiError(r),
99
+ post: (path, body) => this.apiClient.post(path, body),
100
+ spinner: (text, json, work) => this.withApiSpinner({ json, text }, work),
101
+ syncYaml: () => this.syncYamlToServer(),
102
+ };
103
+ const regenerated = await handleMissingTemplates(regenCtx, yamlConfig, [email.id], { json: flags.json, yes: flags.yes });
104
+ if (!regenerated)
105
+ return;
106
+ templateHtml = await loadTemplate(templateFilename);
107
+ if (!templateHtml)
108
+ this.error('Template regeneration failed.');
109
+ }
90
110
  if (flags.send) {
91
- const rendered = templateHtml
92
- ? renderTemplate(templateHtml, sampleData)
93
- : '';
94
- await this.sendTestEmail(email, rendered, {
111
+ await this.sendTestEmail(email, renderTemplate(templateHtml, sampleData), {
95
112
  domain: yamlConfig.project?.domain,
96
113
  jsonOutput: flags.json,
97
114
  toAddress: flags.send,
@@ -112,10 +129,7 @@ export default class Preview extends BaseCommand {
112
129
  * and CI pipelines that cannot open a browser.
113
130
  */
114
131
  async renderText(email, templateHtml, sampleData, jsonOutput) {
115
- const rendered = templateHtml
116
- ? renderTemplate(templateHtml, sampleData)
117
- : '';
118
- const plainText = htmlToText(rendered);
132
+ const plainText = htmlToText(renderTemplate(templateHtml, sampleData));
119
133
  if (jsonOutput) {
120
134
  this.log(JSON.stringify({
121
135
  body: plainText,
@@ -181,9 +195,7 @@ export default class Preview extends BaseCommand {
181
195
  */
182
196
  async startPreviewServer(email, templateHtml, sampleData, opts) {
183
197
  const { effectiveStyle, jsonOutput } = opts;
184
- const rendered = templateHtml
185
- ? renderTemplate(templateHtml, sampleData)
186
- : '<p>No template found.</p>';
198
+ const rendered = renderTemplate(templateHtml, sampleData);
187
199
  const wrapperHtml = `<!DOCTYPE html>
188
200
  <html>
189
201
  <head>
@@ -1,5 +1,6 @@
1
1
  import type { ApiResponse } from '../api-client.js';
2
2
  import type { MailmodoYaml } from '../yaml-config.js';
3
+ import type { RegenCtx } from '../templates/types.js';
3
4
  export interface EmailDiffEntry {
4
5
  changedFields?: string[];
5
6
  id: string;
@@ -44,7 +45,7 @@ export type DeployFlags = {
44
45
  json: boolean;
45
46
  yes: boolean;
46
47
  };
47
- export type DeployCtx = {
48
+ export type DeployCtx = RegenCtx & {
48
49
  collectDomainInputs(yaml: MailmodoYaml, skip: boolean): Promise<{
49
50
  address: string;
50
51
  domain: string;
@@ -84,3 +85,4 @@ export type DeployCtx = {
84
85
  spinner<T>(text: string, json: boolean, work: () => Promise<T>): Promise<T>;
85
86
  syncYaml(): Promise<void>;
86
87
  };
88
+ export { type RegenCtx } from '../templates/types.js';
@@ -0,0 +1,5 @@
1
+ import { type MailmodoYaml } from '../yaml-config.js';
2
+ import type { DeployFlags } from '../deploy/types.js';
3
+ import type { RegenCtx } from './types.js';
4
+ export declare function getMissingTemplateIds(yamlConfig: MailmodoYaml): string[];
5
+ export declare function handleMissingTemplates(ctx: RegenCtx, yamlConfig: MailmodoYaml, missingIds: string[], flags: DeployFlags): Promise<boolean>;
@@ -5,7 +5,7 @@ import chalk from 'chalk';
5
5
  import { API_ENDPOINTS, TEMPLATES_DIR } from '../constants.js';
6
6
  import { MISSING_TEMPLATES } from '../messages.js';
7
7
  import { getTemplateFilename, saveTemplate, } from '../yaml-config.js';
8
- import { buildRegeneratePayload } from './payload.js';
8
+ import { buildRegeneratePayload } from '../deploy/payload.js';
9
9
  export function getMissingTemplateIds(yamlConfig) {
10
10
  return yamlConfig.emails
11
11
  .filter((e) => !existsSync(join(process.cwd(), TEMPLATES_DIR, getTemplateFilename(e.id, e.style, yamlConfig.project?.emailStyle))))
@@ -26,7 +26,6 @@ async function regenerateMissingTemplates(ctx, yamlConfig, missingIds, flags) {
26
26
  }
27
27
  await Promise.all(saves);
28
28
  await ctx.syncYaml();
29
- ctx.log(`\n ${chalk.green('✓')} ${MISSING_TEMPLATES.REVIEW_HINT}\n`);
30
29
  }
31
30
  export async function handleMissingTemplates(ctx, yamlConfig, missingIds, flags) {
32
31
  if (flags.json) {
@@ -36,7 +35,6 @@ export async function handleMissingTemplates(ctx, yamlConfig, missingIds, flags)
36
35
  missingTemplates: missingIds.map((id) => `${id}.html`),
37
36
  }, null, 2));
38
37
  ctx.exit(1);
39
- return;
40
38
  }
41
39
  if (flags.yes)
42
40
  ctx.error(MISSING_TEMPLATES.YES_ERROR);
@@ -56,7 +54,8 @@ export async function handleMissingTemplates(ctx, yamlConfig, missingIds, flags)
56
54
  });
57
55
  if (action === 'abort') {
58
56
  ctx.log(`\n ${MISSING_TEMPLATES.ABORT_HINT}\n`);
59
- return;
57
+ return false;
60
58
  }
61
59
  await regenerateMissingTemplates(ctx, yamlConfig, missingIds, flags);
60
+ return true;
62
61
  }
@@ -0,0 +1,13 @@
1
+ import type { ApiResponse } from '../api-client.js';
2
+ export type RegenCtx = {
3
+ error(msg: string): never;
4
+ exit(code?: number): never;
5
+ log(msg?: string): void;
6
+ onApiError(resp: {
7
+ error?: string;
8
+ status: number;
9
+ }): never;
10
+ post<T = Record<string, unknown>>(path: string, body?: Record<string, unknown> | unknown): Promise<ApiResponse<T>>;
11
+ spinner<T>(text: string, json: boolean, work: () => Promise<T>): Promise<T>;
12
+ syncYaml(): Promise<void>;
13
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -76,15 +76,15 @@
76
76
  "index.js"
77
77
  ]
78
78
  },
79
- "contacts": {
79
+ "deploy": {
80
80
  "aliases": [],
81
81
  "args": {},
82
- "description": "Manage contacts — search, export, or delete",
82
+ "description": "Deploy, pause, or resume an email sequence",
83
83
  "examples": [
84
- "<%= config.bin %> contacts",
85
- "<%= config.bin %> contacts --search sarah@example.com",
86
- "<%= config.bin %> contacts --export # GDPR CSV → contacts.csv",
87
- "<%= config.bin %> contacts --delete sarah@example.com"
84
+ "<%= config.bin %> deploy",
85
+ "<%= config.bin %> deploy --yes",
86
+ "<%= config.bin %> deploy --pause seq_abc123",
87
+ "<%= config.bin %> deploy --resume seq_abc123 --json"
88
88
  ],
89
89
  "flags": {
90
90
  "json": {
@@ -100,22 +100,22 @@
100
100
  "allowNo": false,
101
101
  "type": "boolean"
102
102
  },
103
- "delete": {
104
- "description": "GDPR hard delete a contact by email",
105
- "name": "delete",
103
+ "pause": {
104
+ "description": "Pause a deployed sequence by ID (stops scheduled + triggered sends)",
105
+ "exclusive": [
106
+ "resume"
107
+ ],
108
+ "name": "pause",
106
109
  "hasDynamicHelp": false,
107
110
  "multiple": false,
108
111
  "type": "option"
109
112
  },
110
- "export": {
111
- "description": "Export all contacts as GDPR-compliant CSV (writes contacts.csv in the current directory)",
112
- "name": "export",
113
- "allowNo": false,
114
- "type": "boolean"
115
- },
116
- "search": {
117
- "description": "Search for a contact by email",
118
- "name": "search",
113
+ "resume": {
114
+ "description": "Resume a paused sequence by ID",
115
+ "exclusive": [
116
+ "pause"
117
+ ],
118
+ "name": "resume",
119
119
  "hasDynamicHelp": false,
120
120
  "multiple": false,
121
121
  "type": "option"
@@ -123,7 +123,7 @@
123
123
  },
124
124
  "hasDynamicHelp": false,
125
125
  "hiddenAliases": [],
126
- "id": "contacts",
126
+ "id": "deploy",
127
127
  "pluginAlias": "@mailmodo/cli",
128
128
  "pluginName": "@mailmodo/cli",
129
129
  "pluginType": "core",
@@ -133,19 +133,19 @@
133
133
  "relativePath": [
134
134
  "dist",
135
135
  "commands",
136
- "contacts",
136
+ "deploy",
137
137
  "index.js"
138
138
  ]
139
139
  },
140
- "deploy": {
140
+ "contacts": {
141
141
  "aliases": [],
142
142
  "args": {},
143
- "description": "Deploy, pause, or resume an email sequence",
143
+ "description": "Manage contacts — search, export, or delete",
144
144
  "examples": [
145
- "<%= config.bin %> deploy",
146
- "<%= config.bin %> deploy --yes",
147
- "<%= config.bin %> deploy --pause seq_abc123",
148
- "<%= config.bin %> deploy --resume seq_abc123 --json"
145
+ "<%= config.bin %> contacts",
146
+ "<%= config.bin %> contacts --search sarah@example.com",
147
+ "<%= config.bin %> contacts --export # GDPR CSV → contacts.csv",
148
+ "<%= config.bin %> contacts --delete sarah@example.com"
149
149
  ],
150
150
  "flags": {
151
151
  "json": {
@@ -161,22 +161,22 @@
161
161
  "allowNo": false,
162
162
  "type": "boolean"
163
163
  },
164
- "pause": {
165
- "description": "Pause a deployed sequence by ID (stops scheduled + triggered sends)",
166
- "exclusive": [
167
- "resume"
168
- ],
169
- "name": "pause",
164
+ "delete": {
165
+ "description": "GDPR hard delete a contact by email",
166
+ "name": "delete",
170
167
  "hasDynamicHelp": false,
171
168
  "multiple": false,
172
169
  "type": "option"
173
170
  },
174
- "resume": {
175
- "description": "Resume a paused sequence by ID",
176
- "exclusive": [
177
- "pause"
178
- ],
179
- "name": "resume",
171
+ "export": {
172
+ "description": "Export all contacts as GDPR-compliant CSV (writes contacts.csv in the current directory)",
173
+ "name": "export",
174
+ "allowNo": false,
175
+ "type": "boolean"
176
+ },
177
+ "search": {
178
+ "description": "Search for a contact by email",
179
+ "name": "search",
180
180
  "hasDynamicHelp": false,
181
181
  "multiple": false,
182
182
  "type": "option"
@@ -184,7 +184,7 @@
184
184
  },
185
185
  "hasDynamicHelp": false,
186
186
  "hiddenAliases": [],
187
- "id": "deploy",
187
+ "id": "contacts",
188
188
  "pluginAlias": "@mailmodo/cli",
189
189
  "pluginName": "@mailmodo/cli",
190
190
  "pluginType": "core",
@@ -194,7 +194,7 @@
194
194
  "relativePath": [
195
195
  "dist",
196
196
  "commands",
197
- "deploy",
197
+ "contacts",
198
198
  "index.js"
199
199
  ]
200
200
  },
@@ -289,6 +289,45 @@
289
289
  "index.js"
290
290
  ]
291
291
  },
292
+ "emails": {
293
+ "aliases": [],
294
+ "args": {},
295
+ "description": "List and view configured email sequences",
296
+ "examples": [
297
+ "<%= config.bin %> emails",
298
+ "<%= config.bin %> emails --json"
299
+ ],
300
+ "flags": {
301
+ "json": {
302
+ "description": "Output as JSON",
303
+ "name": "json",
304
+ "allowNo": false,
305
+ "type": "boolean"
306
+ },
307
+ "yes": {
308
+ "char": "y",
309
+ "description": "Skip confirmation prompts",
310
+ "name": "yes",
311
+ "allowNo": false,
312
+ "type": "boolean"
313
+ }
314
+ },
315
+ "hasDynamicHelp": false,
316
+ "hiddenAliases": [],
317
+ "id": "emails",
318
+ "pluginAlias": "@mailmodo/cli",
319
+ "pluginName": "@mailmodo/cli",
320
+ "pluginType": "core",
321
+ "strict": true,
322
+ "enableJsonFlag": false,
323
+ "isESM": true,
324
+ "relativePath": [
325
+ "dist",
326
+ "commands",
327
+ "emails",
328
+ "index.js"
329
+ ]
330
+ },
292
331
  "edit": {
293
332
  "aliases": [],
294
333
  "args": {
@@ -341,13 +380,13 @@
341
380
  "index.js"
342
381
  ]
343
382
  },
344
- "emails": {
383
+ "init": {
345
384
  "aliases": [],
346
385
  "args": {},
347
- "description": "List and view configured email sequences",
386
+ "description": "Analyze your product and generate email sequences",
348
387
  "examples": [
349
- "<%= config.bin %> emails",
350
- "<%= config.bin %> emails --json"
388
+ "<%= config.bin %> init",
389
+ "<%= config.bin %> init --url https://myapp.com --yes"
351
390
  ],
352
391
  "flags": {
353
392
  "json": {
@@ -362,11 +401,18 @@
362
401
  "name": "yes",
363
402
  "allowNo": false,
364
403
  "type": "boolean"
404
+ },
405
+ "url": {
406
+ "description": "Product URL to analyze",
407
+ "name": "url",
408
+ "hasDynamicHelp": false,
409
+ "multiple": false,
410
+ "type": "option"
365
411
  }
366
412
  },
367
413
  "hasDynamicHelp": false,
368
414
  "hiddenAliases": [],
369
- "id": "emails",
415
+ "id": "init",
370
416
  "pluginAlias": "@mailmodo/cli",
371
417
  "pluginName": "@mailmodo/cli",
372
418
  "pluginType": "core",
@@ -376,7 +422,7 @@
376
422
  "relativePath": [
377
423
  "dist",
378
424
  "commands",
379
- "emails",
425
+ "init",
380
426
  "index.js"
381
427
  ]
382
428
  },
@@ -527,13 +573,19 @@
527
573
  "index.js"
528
574
  ]
529
575
  },
530
- "init": {
576
+ "preview": {
531
577
  "aliases": [],
532
- "args": {},
533
- "description": "Analyze your product and generate email sequences",
578
+ "args": {
579
+ "id": {
580
+ "description": "Email template ID to preview",
581
+ "name": "id"
582
+ }
583
+ },
584
+ "description": "Preview an email in browser, as text, or send a test",
534
585
  "examples": [
535
- "<%= config.bin %> init",
536
- "<%= config.bin %> init --url https://myapp.com --yes"
586
+ "<%= config.bin %> preview welcome",
587
+ "<%= config.bin %> preview welcome --text",
588
+ "<%= config.bin %> preview welcome --send me@example.com"
537
589
  ],
538
590
  "flags": {
539
591
  "json": {
@@ -549,17 +601,23 @@
549
601
  "allowNo": false,
550
602
  "type": "boolean"
551
603
  },
552
- "url": {
553
- "description": "Product URL to analyze",
554
- "name": "url",
604
+ "send": {
605
+ "description": "Send test email to this address",
606
+ "name": "send",
555
607
  "hasDynamicHelp": false,
556
608
  "multiple": false,
557
609
  "type": "option"
610
+ },
611
+ "text": {
612
+ "description": "Output plain text version (for AI agents)",
613
+ "name": "text",
614
+ "allowNo": false,
615
+ "type": "boolean"
558
616
  }
559
617
  },
560
618
  "hasDynamicHelp": false,
561
619
  "hiddenAliases": [],
562
- "id": "init",
620
+ "id": "preview",
563
621
  "pluginAlias": "@mailmodo/cli",
564
622
  "pluginName": "@mailmodo/cli",
565
623
  "pluginType": "core",
@@ -569,7 +627,7 @@
569
627
  "relativePath": [
570
628
  "dist",
571
629
  "commands",
572
- "init",
630
+ "preview",
573
631
  "index.js"
574
632
  ]
575
633
  },
@@ -620,64 +678,6 @@
620
678
  "index.js"
621
679
  ]
622
680
  },
623
- "preview": {
624
- "aliases": [],
625
- "args": {
626
- "id": {
627
- "description": "Email template ID to preview",
628
- "name": "id"
629
- }
630
- },
631
- "description": "Preview an email in browser, as text, or send a test",
632
- "examples": [
633
- "<%= config.bin %> preview welcome",
634
- "<%= config.bin %> preview welcome --text",
635
- "<%= config.bin %> preview welcome --send me@example.com"
636
- ],
637
- "flags": {
638
- "json": {
639
- "description": "Output as JSON",
640
- "name": "json",
641
- "allowNo": false,
642
- "type": "boolean"
643
- },
644
- "yes": {
645
- "char": "y",
646
- "description": "Skip confirmation prompts",
647
- "name": "yes",
648
- "allowNo": false,
649
- "type": "boolean"
650
- },
651
- "send": {
652
- "description": "Send test email to this address",
653
- "name": "send",
654
- "hasDynamicHelp": false,
655
- "multiple": false,
656
- "type": "option"
657
- },
658
- "text": {
659
- "description": "Output plain text version (for AI agents)",
660
- "name": "text",
661
- "allowNo": false,
662
- "type": "boolean"
663
- }
664
- },
665
- "hasDynamicHelp": false,
666
- "hiddenAliases": [],
667
- "id": "preview",
668
- "pluginAlias": "@mailmodo/cli",
669
- "pluginName": "@mailmodo/cli",
670
- "pluginType": "core",
671
- "strict": true,
672
- "enableJsonFlag": false,
673
- "isESM": true,
674
- "relativePath": [
675
- "dist",
676
- "commands",
677
- "preview",
678
- "index.js"
679
- ]
680
- },
681
681
  "settings": {
682
682
  "aliases": [],
683
683
  "args": {},
@@ -765,5 +765,5 @@
765
765
  ]
766
766
  }
767
767
  },
768
- "version": "0.0.54-beta.pr56.88"
768
+ "version": "0.0.54-beta.pr56.89"
769
769
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mailmodo/cli",
3
3
  "description": "Email lifecycle automation for the AI-native builder generation.",
4
- "version": "0.0.54-beta.pr56.88",
4
+ "version": "0.0.54-beta.pr56.89",
5
5
  "author": "provishalk",
6
6
  "bin": {
7
7
  "mailmodo": "bin/run.js"
@@ -1,4 +0,0 @@
1
- import { type MailmodoYaml } from '../yaml-config.js';
2
- import type { DeployCtx, DeployFlags } from './types.js';
3
- export declare function getMissingTemplateIds(yamlConfig: MailmodoYaml): string[];
4
- export declare function handleMissingTemplates(ctx: DeployCtx, yamlConfig: MailmodoYaml, missingIds: string[], flags: DeployFlags): Promise<void>;