@mailmodo/cli 0.0.54-beta.pr56.87 → 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.
- package/dist/commands/deploy/index.js +6 -2
- package/dist/commands/login/index.js +1 -1
- package/dist/commands/preview/index.js +24 -12
- package/dist/lib/base-command.d.ts +1 -0
- package/dist/lib/base-command.js +24 -17
- package/dist/lib/deploy/domain-setup.js +3 -1
- package/dist/lib/deploy/output.js +8 -8
- package/dist/lib/deploy/types.d.ts +3 -1
- package/dist/lib/messages.d.ts +13 -0
- package/dist/lib/messages.js +13 -0
- package/dist/lib/templates/missing-templates.d.ts +5 -0
- package/dist/lib/{deploy → templates}/missing-templates.js +14 -10
- package/dist/lib/templates/types.d.ts +13 -0
- package/dist/lib/templates/types.js +1 -0
- package/oclif.manifest.json +98 -98
- package/package.json +1 -1
- package/dist/lib/deploy/missing-templates.d.ts +0 -4
|
@@ -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/
|
|
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);
|
|
@@ -49,7 +49,7 @@ export default class Login extends BaseCommand {
|
|
|
49
49
|
else {
|
|
50
50
|
this.log(` ${chalk.dim('1.')} Run ${chalk.cyan('mailmodo init')} to generate an email sequence.`);
|
|
51
51
|
}
|
|
52
|
-
this.log(` ${chalk.dim('2.')} Run ${chalk.cyan('mailmodo logout')} to log in with another account.\n`);
|
|
52
|
+
this.log(` ${chalk.dim(yamlRestored ? '1.' : '2.')} Run ${chalk.cyan('mailmodo logout')} to log in with another account.\n`);
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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>
|
|
@@ -68,6 +68,7 @@ export declare abstract class BaseCommand extends Command {
|
|
|
68
68
|
* settings and all email sequence definitions.
|
|
69
69
|
*/
|
|
70
70
|
protected ensureYaml(): Promise<MailmodoYaml>;
|
|
71
|
+
private fetchAndWriteYaml;
|
|
71
72
|
/**
|
|
72
73
|
* Attempts to fetch mailmodo.yaml from the server and save it locally.
|
|
73
74
|
* Returns null silently on any failure so callers can fall through to an error.
|
package/dist/lib/base-command.js
CHANGED
|
@@ -95,20 +95,36 @@ export class BaseCommand extends Command {
|
|
|
95
95
|
return restored;
|
|
96
96
|
this.error(ERRORS.NO_YAML);
|
|
97
97
|
}
|
|
98
|
+
async fetchAndWriteYaml(client) {
|
|
99
|
+
try {
|
|
100
|
+
const response = await client.getRawText(API_ENDPOINTS.ASSETS_YAML);
|
|
101
|
+
if (!response.ok || !response.data)
|
|
102
|
+
return false;
|
|
103
|
+
await writeFile(join(process.cwd(), YAML_FILE), response.data);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
98
110
|
/**
|
|
99
111
|
* Attempts to fetch mailmodo.yaml from the server and save it locally.
|
|
100
112
|
* Returns null silently on any failure so callers can fall through to an error.
|
|
101
113
|
*/
|
|
102
114
|
async restoreYamlFromServer() {
|
|
103
|
-
if (!this.apiClient)
|
|
104
|
-
return null;
|
|
105
115
|
try {
|
|
106
|
-
|
|
107
|
-
if (!
|
|
116
|
+
let client = this.apiClient;
|
|
117
|
+
if (!client) {
|
|
118
|
+
const envKey = process.env.MAILMODO_API_KEY;
|
|
119
|
+
const apiKey = envKey ?? (await loadConfig())?.apiKey;
|
|
120
|
+
if (!apiKey)
|
|
121
|
+
return null;
|
|
122
|
+
client = new ApiClient(apiKey);
|
|
123
|
+
}
|
|
124
|
+
const written = await this.fetchAndWriteYaml(client);
|
|
125
|
+
if (!written)
|
|
108
126
|
return null;
|
|
109
|
-
|
|
110
|
-
await writeFile(filePath, response.data);
|
|
111
|
-
this.log(INFO.YAML_RESTORED_FROM_SERVER);
|
|
127
|
+
this.logToStderr(INFO.YAML_RESTORED_FROM_SERVER);
|
|
112
128
|
return loadYaml();
|
|
113
129
|
}
|
|
114
130
|
catch {
|
|
@@ -127,16 +143,7 @@ export class BaseCommand extends Command {
|
|
|
127
143
|
async recoverYamlAfterLogin(client) {
|
|
128
144
|
if (existsSync(join(process.cwd(), YAML_FILE)))
|
|
129
145
|
return false;
|
|
130
|
-
|
|
131
|
-
const response = await client.getRawText(API_ENDPOINTS.ASSETS_YAML);
|
|
132
|
-
if (!response.ok || !response.data)
|
|
133
|
-
return false;
|
|
134
|
-
await writeFile(join(process.cwd(), YAML_FILE), response.data);
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
146
|
+
return this.fetchAndWriteYaml(client);
|
|
140
147
|
}
|
|
141
148
|
/**
|
|
142
149
|
* Uploads the current local mailmodo.yaml to the server as a backup.
|
|
@@ -54,7 +54,9 @@ export async function ensureDomainReady(ctx, yamlConfig, flags) {
|
|
|
54
54
|
const check = await ctx.spinner(' Checking domain verification...', flags.json, () => ctx.get(API_ENDPOINTS.DOMAIN_VERIFY, {
|
|
55
55
|
domain: yamlConfig.project?.domain || '',
|
|
56
56
|
}));
|
|
57
|
-
if (check.ok
|
|
57
|
+
if (!check.ok)
|
|
58
|
+
ctx.onApiError(check);
|
|
59
|
+
if (check.data?.domainStatus === 'VERIFIED')
|
|
58
60
|
return true;
|
|
59
61
|
if (yamlConfig.project?.domain) {
|
|
60
62
|
if (!flags.json)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { SDK_IMPORT_SNIPPET, SDK_INSTALL_COMMAND } from '../constants.js';
|
|
3
|
-
import { SEPARATOR } from '../messages.js';
|
|
3
|
+
import { DEPLOY, SEPARATOR } from '../messages.js';
|
|
4
4
|
export function logDiff(ctx, diff) {
|
|
5
5
|
if (!diff.hasChanges) {
|
|
6
|
-
ctx.log(`
|
|
6
|
+
ctx.log(` ${DEPLOY.NO_CHANGES}`);
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
|
-
ctx.log(`
|
|
9
|
+
ctx.log(` ${DEPLOY.CHANGES_HEADER}`);
|
|
10
10
|
for (const email of diff.added) {
|
|
11
11
|
ctx.log(` ${chalk.green('+')} ${email.id.padEnd(24)} ${email.trigger || ''}`);
|
|
12
12
|
}
|
|
@@ -24,7 +24,7 @@ export function logPreDeploySummary(ctx, yamlConfig, validateResult, jsonOutput)
|
|
|
24
24
|
return;
|
|
25
25
|
ctx.log(`\n ${chalk.green('✓')} Domain: ${yamlConfig.project?.domain || 'verified'}\n`);
|
|
26
26
|
if (!validateResult.existingDeployment || !validateResult.diff) {
|
|
27
|
-
ctx.log(`
|
|
27
|
+
ctx.log(` ${DEPLOY.DEPLOYING_HEADER}`);
|
|
28
28
|
for (const email of yamlConfig.emails) {
|
|
29
29
|
ctx.log(` ${chalk.green('+')} ${email.id.padEnd(24)} ${email.trigger}`);
|
|
30
30
|
}
|
|
@@ -35,14 +35,14 @@ export function logPreDeploySummary(ctx, yamlConfig, validateResult, jsonOutput)
|
|
|
35
35
|
ctx.log('');
|
|
36
36
|
}
|
|
37
37
|
export function logDeploySuccessInstructions(ctx, sdkSnippet) {
|
|
38
|
-
ctx.log(` ${
|
|
38
|
+
ctx.log(` ${DEPLOY.SUCCESS}\n`);
|
|
39
39
|
ctx.log(` ${SEPARATOR}`);
|
|
40
|
-
ctx.log(` ${
|
|
40
|
+
ctx.log(` ${DEPLOY.SDK_ONBOARDING_HEADER}`);
|
|
41
41
|
ctx.log(` ${SEPARATOR}\n`);
|
|
42
42
|
ctx.log(` ${chalk.cyan(sdkSnippet.install ?? SDK_INSTALL_COMMAND)}\n`);
|
|
43
43
|
ctx.log(` ${chalk.dim(SDK_IMPORT_SNIPPET)}\n`);
|
|
44
44
|
if (sdkSnippet.examples) {
|
|
45
|
-
ctx.log(` ${
|
|
45
|
+
ctx.log(` ${DEPLOY.SDK_EXAMPLE_COMMENT}`);
|
|
46
46
|
ctx.log(` ${chalk.dim(sdkSnippet.examples.track)}`);
|
|
47
47
|
ctx.log(` ${chalk.dim(sdkSnippet.examples.identify)}\n`);
|
|
48
48
|
}
|
|
@@ -56,6 +56,6 @@ export function logDeploySuccessInstructions(ctx, sdkSnippet) {
|
|
|
56
56
|
ctx.log(` ${chalk.dim(call)}`);
|
|
57
57
|
if (identifyCalls.length > 0)
|
|
58
58
|
ctx.log('');
|
|
59
|
-
ctx.log(`
|
|
59
|
+
ctx.log(` ${DEPLOY.SDK_DOCS_HINT}\n`);
|
|
60
60
|
ctx.log(` ${SEPARATOR}\n`);
|
|
61
61
|
}
|
|
@@ -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';
|
package/dist/lib/messages.d.ts
CHANGED
|
@@ -15,8 +15,12 @@ export declare const PROMPTS: {
|
|
|
15
15
|
};
|
|
16
16
|
export declare const MISSING_TEMPLATES: {
|
|
17
17
|
readonly ABORT_HINT: `Restore the missing files from version control, then run ${string} again.`;
|
|
18
|
+
readonly CHOICE_ABORT: "Abort (restore from version control)";
|
|
19
|
+
readonly CHOICE_REGENERATE: "Re-generate via AI";
|
|
18
20
|
readonly HEADER: `Some email templates are missing from ${string}:`;
|
|
21
|
+
readonly PROMPT_ACTION: "What would you like to do?";
|
|
19
22
|
readonly REGENERATE_NOTE: string;
|
|
23
|
+
readonly REGENERATE_SPINNER: "Regenerating email templates...";
|
|
20
24
|
readonly REVIEW_HINT: `Templates regenerated. Review them with ${string}, then run ${string} again.`;
|
|
21
25
|
readonly YES_ERROR: `Missing templates cannot be resolved with ${string}. Run ${string} without ${string} to regenerate via AI or restore from version control.`;
|
|
22
26
|
};
|
|
@@ -50,6 +54,15 @@ export declare const INFO: {
|
|
|
50
54
|
readonly YAML_RESTORED_FROM_SERVER: string;
|
|
51
55
|
readonly YAML_RESTORED_ON_LOGIN: ` mailmodo.yaml restored from server. Run ${string} to re-deploy your sequences.`;
|
|
52
56
|
};
|
|
57
|
+
export declare const DEPLOY: {
|
|
58
|
+
readonly CHANGES_HEADER: "Changes vs. last deployment:";
|
|
59
|
+
readonly DEPLOYING_HEADER: "Deploying:";
|
|
60
|
+
readonly NO_CHANGES: "No changes from last deployment.";
|
|
61
|
+
readonly SDK_DOCS_HINT: `Full SDK docs: ${string}`;
|
|
62
|
+
readonly SDK_EXAMPLE_COMMENT: string;
|
|
63
|
+
readonly SDK_ONBOARDING_HEADER: string;
|
|
64
|
+
readonly SUCCESS: `${string} Emails are live.`;
|
|
65
|
+
};
|
|
53
66
|
export declare function pauseSuccess(sequenceId: string): string;
|
|
54
67
|
export declare function pauseAlready(sequenceId: string): string;
|
|
55
68
|
export declare function resumeSuccess(sequenceId: string): string;
|
package/dist/lib/messages.js
CHANGED
|
@@ -16,8 +16,12 @@ export const PROMPTS = {
|
|
|
16
16
|
};
|
|
17
17
|
export const MISSING_TEMPLATES = {
|
|
18
18
|
ABORT_HINT: `Restore the missing files from version control, then run ${chalk.cyan('mailmodo deploy')} again.`,
|
|
19
|
+
CHOICE_ABORT: 'Abort (restore from version control)',
|
|
20
|
+
CHOICE_REGENERATE: 'Re-generate via AI',
|
|
19
21
|
HEADER: `Some email templates are missing from ${chalk.cyan('./mailmodo/')}:`,
|
|
22
|
+
PROMPT_ACTION: 'What would you like to do?',
|
|
20
23
|
REGENERATE_NOTE: chalk.yellow(' Note: any previous manual edits to these files will be replaced with a new AI draft.'),
|
|
24
|
+
REGENERATE_SPINNER: 'Regenerating email templates...',
|
|
21
25
|
REVIEW_HINT: `Templates regenerated. Review them with ${chalk.cyan('mailmodo preview')}, then run ${chalk.cyan('mailmodo deploy')} again.`,
|
|
22
26
|
YES_ERROR: `Missing templates cannot be resolved with ${chalk.cyan('--yes')}. Run ${chalk.cyan('mailmodo deploy')} without ${chalk.cyan('--yes')} to regenerate via AI or restore from version control.`,
|
|
23
27
|
};
|
|
@@ -50,6 +54,15 @@ export const INFO = {
|
|
|
50
54
|
YAML_RESTORED_FROM_SERVER: chalk.dim(' mailmodo.yaml not found locally — restored from server.'),
|
|
51
55
|
YAML_RESTORED_ON_LOGIN: ` mailmodo.yaml restored from server. Run ${chalk.cyan("'mailmodo deploy'")} to re-deploy your sequences.`,
|
|
52
56
|
};
|
|
57
|
+
export const DEPLOY = {
|
|
58
|
+
CHANGES_HEADER: 'Changes vs. last deployment:',
|
|
59
|
+
DEPLOYING_HEADER: 'Deploying:',
|
|
60
|
+
NO_CHANGES: 'No changes from last deployment.',
|
|
61
|
+
SDK_DOCS_HINT: `Full SDK docs: ${chalk.cyan('mailmodo.com/docs/sdk')}`,
|
|
62
|
+
SDK_EXAMPLE_COMMENT: chalk.dim('// Example usage:'),
|
|
63
|
+
SDK_ONBOARDING_HEADER: chalk.bold('ADD THIS TO YOUR APP (one-time only):'),
|
|
64
|
+
SUCCESS: `${chalk.green('Deployed.')} Emails are live.`,
|
|
65
|
+
};
|
|
53
66
|
export function pauseSuccess(sequenceId) {
|
|
54
67
|
return `Sequence ${chalk.cyan(sequenceId)} paused. Run ${chalk.cyan(`mailmodo deploy --resume ${sequenceId}`)} to resume.`;
|
|
55
68
|
}
|
|
@@ -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>;
|
|
@@ -4,19 +4,21 @@ import { select } from '@inquirer/prompts';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { API_ENDPOINTS, TEMPLATES_DIR } from '../constants.js';
|
|
6
6
|
import { MISSING_TEMPLATES } from '../messages.js';
|
|
7
|
-
import { saveTemplate } from '../yaml-config.js';
|
|
8
|
-
import { buildRegeneratePayload } from '
|
|
7
|
+
import { getTemplateFilename, saveTemplate, } from '../yaml-config.js';
|
|
8
|
+
import { buildRegeneratePayload } from '../deploy/payload.js';
|
|
9
9
|
export function getMissingTemplateIds(yamlConfig) {
|
|
10
10
|
return yamlConfig.emails
|
|
11
|
-
.filter((e) => !existsSync(join(process.cwd(), TEMPLATES_DIR,
|
|
11
|
+
.filter((e) => !existsSync(join(process.cwd(), TEMPLATES_DIR, getTemplateFilename(e.id, e.style, yamlConfig.project?.emailStyle))))
|
|
12
12
|
.map((e) => e.id);
|
|
13
13
|
}
|
|
14
14
|
async function regenerateMissingTemplates(ctx, yamlConfig, missingIds, flags) {
|
|
15
|
-
const response = await ctx.spinner(
|
|
15
|
+
const response = await ctx.spinner(` ${MISSING_TEMPLATES.REGENERATE_SPINNER}`, flags.json, () => ctx.post(API_ENDPOINTS.GENERATE, buildRegeneratePayload(yamlConfig, missingIds)));
|
|
16
16
|
if (!response.ok)
|
|
17
17
|
ctx.onApiError(response);
|
|
18
18
|
const saves = [];
|
|
19
19
|
for (const email of response.data?.emails ?? []) {
|
|
20
|
+
if (!/^[\w-]+$/.test(email.id))
|
|
21
|
+
continue;
|
|
20
22
|
if (email.html)
|
|
21
23
|
saves.push(saveTemplate(`${email.id}.html`, email.html));
|
|
22
24
|
if (email.plainHtml)
|
|
@@ -24,7 +26,6 @@ async function regenerateMissingTemplates(ctx, yamlConfig, missingIds, flags) {
|
|
|
24
26
|
}
|
|
25
27
|
await Promise.all(saves);
|
|
26
28
|
await ctx.syncYaml();
|
|
27
|
-
ctx.log(`\n ${chalk.green('✓')} ${MISSING_TEMPLATES.REVIEW_HINT}\n`);
|
|
28
29
|
}
|
|
29
30
|
export async function handleMissingTemplates(ctx, yamlConfig, missingIds, flags) {
|
|
30
31
|
if (flags.json) {
|
|
@@ -34,7 +35,6 @@ export async function handleMissingTemplates(ctx, yamlConfig, missingIds, flags)
|
|
|
34
35
|
missingTemplates: missingIds.map((id) => `${id}.html`),
|
|
35
36
|
}, null, 2));
|
|
36
37
|
ctx.exit(1);
|
|
37
|
-
return;
|
|
38
38
|
}
|
|
39
39
|
if (flags.yes)
|
|
40
40
|
ctx.error(MISSING_TEMPLATES.YES_ERROR);
|
|
@@ -44,14 +44,18 @@ export async function handleMissingTemplates(ctx, yamlConfig, missingIds, flags)
|
|
|
44
44
|
ctx.log(`\n ${MISSING_TEMPLATES.REGENERATE_NOTE}\n`);
|
|
45
45
|
const action = await select({
|
|
46
46
|
choices: [
|
|
47
|
-
{
|
|
48
|
-
|
|
47
|
+
{
|
|
48
|
+
name: MISSING_TEMPLATES.CHOICE_REGENERATE,
|
|
49
|
+
value: 'regenerate',
|
|
50
|
+
},
|
|
51
|
+
{ name: MISSING_TEMPLATES.CHOICE_ABORT, value: 'abort' },
|
|
49
52
|
],
|
|
50
|
-
message:
|
|
53
|
+
message: MISSING_TEMPLATES.PROMPT_ACTION,
|
|
51
54
|
});
|
|
52
55
|
if (action === 'abort') {
|
|
53
56
|
ctx.log(`\n ${MISSING_TEMPLATES.ABORT_HINT}\n`);
|
|
54
|
-
return;
|
|
57
|
+
return false;
|
|
55
58
|
}
|
|
56
59
|
await regenerateMissingTemplates(ctx, yamlConfig, missingIds, flags);
|
|
60
|
+
return true;
|
|
57
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 {};
|
package/oclif.manifest.json
CHANGED
|
@@ -76,6 +76,67 @@
|
|
|
76
76
|
"index.js"
|
|
77
77
|
]
|
|
78
78
|
},
|
|
79
|
+
"deploy": {
|
|
80
|
+
"aliases": [],
|
|
81
|
+
"args": {},
|
|
82
|
+
"description": "Deploy, pause, or resume an email sequence",
|
|
83
|
+
"examples": [
|
|
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
|
+
],
|
|
89
|
+
"flags": {
|
|
90
|
+
"json": {
|
|
91
|
+
"description": "Output as JSON",
|
|
92
|
+
"name": "json",
|
|
93
|
+
"allowNo": false,
|
|
94
|
+
"type": "boolean"
|
|
95
|
+
},
|
|
96
|
+
"yes": {
|
|
97
|
+
"char": "y",
|
|
98
|
+
"description": "Skip confirmation prompts",
|
|
99
|
+
"name": "yes",
|
|
100
|
+
"allowNo": false,
|
|
101
|
+
"type": "boolean"
|
|
102
|
+
},
|
|
103
|
+
"pause": {
|
|
104
|
+
"description": "Pause a deployed sequence by ID (stops scheduled + triggered sends)",
|
|
105
|
+
"exclusive": [
|
|
106
|
+
"resume"
|
|
107
|
+
],
|
|
108
|
+
"name": "pause",
|
|
109
|
+
"hasDynamicHelp": false,
|
|
110
|
+
"multiple": false,
|
|
111
|
+
"type": "option"
|
|
112
|
+
},
|
|
113
|
+
"resume": {
|
|
114
|
+
"description": "Resume a paused sequence by ID",
|
|
115
|
+
"exclusive": [
|
|
116
|
+
"pause"
|
|
117
|
+
],
|
|
118
|
+
"name": "resume",
|
|
119
|
+
"hasDynamicHelp": false,
|
|
120
|
+
"multiple": false,
|
|
121
|
+
"type": "option"
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"hasDynamicHelp": false,
|
|
125
|
+
"hiddenAliases": [],
|
|
126
|
+
"id": "deploy",
|
|
127
|
+
"pluginAlias": "@mailmodo/cli",
|
|
128
|
+
"pluginName": "@mailmodo/cli",
|
|
129
|
+
"pluginType": "core",
|
|
130
|
+
"strict": true,
|
|
131
|
+
"enableJsonFlag": false,
|
|
132
|
+
"isESM": true,
|
|
133
|
+
"relativePath": [
|
|
134
|
+
"dist",
|
|
135
|
+
"commands",
|
|
136
|
+
"deploy",
|
|
137
|
+
"index.js"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
79
140
|
"contacts": {
|
|
80
141
|
"aliases": [],
|
|
81
142
|
"args": {},
|
|
@@ -176,15 +237,14 @@
|
|
|
176
237
|
"index.js"
|
|
177
238
|
]
|
|
178
239
|
},
|
|
179
|
-
"
|
|
240
|
+
"domain": {
|
|
180
241
|
"aliases": [],
|
|
181
242
|
"args": {},
|
|
182
|
-
"description": "
|
|
243
|
+
"description": "Set up and verify your sending domain",
|
|
183
244
|
"examples": [
|
|
184
|
-
"<%= config.bin %>
|
|
185
|
-
"<%= config.bin %>
|
|
186
|
-
"<%= config.bin %>
|
|
187
|
-
"<%= config.bin %> deploy --resume seq_abc123 --json"
|
|
245
|
+
"<%= config.bin %> domain",
|
|
246
|
+
"<%= config.bin %> domain --verify",
|
|
247
|
+
"<%= config.bin %> domain --status"
|
|
188
248
|
],
|
|
189
249
|
"flags": {
|
|
190
250
|
"json": {
|
|
@@ -200,30 +260,22 @@
|
|
|
200
260
|
"allowNo": false,
|
|
201
261
|
"type": "boolean"
|
|
202
262
|
},
|
|
203
|
-
"
|
|
204
|
-
"description": "
|
|
205
|
-
"
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
"name": "pause",
|
|
209
|
-
"hasDynamicHelp": false,
|
|
210
|
-
"multiple": false,
|
|
211
|
-
"type": "option"
|
|
263
|
+
"status": {
|
|
264
|
+
"description": "Show domain health status",
|
|
265
|
+
"name": "status",
|
|
266
|
+
"allowNo": false,
|
|
267
|
+
"type": "boolean"
|
|
212
268
|
},
|
|
213
|
-
"
|
|
214
|
-
"description": "
|
|
215
|
-
"
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
"name": "resume",
|
|
219
|
-
"hasDynamicHelp": false,
|
|
220
|
-
"multiple": false,
|
|
221
|
-
"type": "option"
|
|
269
|
+
"verify": {
|
|
270
|
+
"description": "Verify DNS records",
|
|
271
|
+
"name": "verify",
|
|
272
|
+
"allowNo": false,
|
|
273
|
+
"type": "boolean"
|
|
222
274
|
}
|
|
223
275
|
},
|
|
224
276
|
"hasDynamicHelp": false,
|
|
225
277
|
"hiddenAliases": [],
|
|
226
|
-
"id": "
|
|
278
|
+
"id": "domain",
|
|
227
279
|
"pluginAlias": "@mailmodo/cli",
|
|
228
280
|
"pluginName": "@mailmodo/cli",
|
|
229
281
|
"pluginType": "core",
|
|
@@ -233,18 +285,17 @@
|
|
|
233
285
|
"relativePath": [
|
|
234
286
|
"dist",
|
|
235
287
|
"commands",
|
|
236
|
-
"
|
|
288
|
+
"domain",
|
|
237
289
|
"index.js"
|
|
238
290
|
]
|
|
239
291
|
},
|
|
240
|
-
"
|
|
292
|
+
"emails": {
|
|
241
293
|
"aliases": [],
|
|
242
294
|
"args": {},
|
|
243
|
-
"description": "
|
|
295
|
+
"description": "List and view configured email sequences",
|
|
244
296
|
"examples": [
|
|
245
|
-
"<%= config.bin %>
|
|
246
|
-
"<%= config.bin %>
|
|
247
|
-
"<%= config.bin %> domain --status"
|
|
297
|
+
"<%= config.bin %> emails",
|
|
298
|
+
"<%= config.bin %> emails --json"
|
|
248
299
|
],
|
|
249
300
|
"flags": {
|
|
250
301
|
"json": {
|
|
@@ -259,23 +310,11 @@
|
|
|
259
310
|
"name": "yes",
|
|
260
311
|
"allowNo": false,
|
|
261
312
|
"type": "boolean"
|
|
262
|
-
},
|
|
263
|
-
"status": {
|
|
264
|
-
"description": "Show domain health status",
|
|
265
|
-
"name": "status",
|
|
266
|
-
"allowNo": false,
|
|
267
|
-
"type": "boolean"
|
|
268
|
-
},
|
|
269
|
-
"verify": {
|
|
270
|
-
"description": "Verify DNS records",
|
|
271
|
-
"name": "verify",
|
|
272
|
-
"allowNo": false,
|
|
273
|
-
"type": "boolean"
|
|
274
313
|
}
|
|
275
314
|
},
|
|
276
315
|
"hasDynamicHelp": false,
|
|
277
316
|
"hiddenAliases": [],
|
|
278
|
-
"id": "
|
|
317
|
+
"id": "emails",
|
|
279
318
|
"pluginAlias": "@mailmodo/cli",
|
|
280
319
|
"pluginName": "@mailmodo/cli",
|
|
281
320
|
"pluginType": "core",
|
|
@@ -285,7 +324,7 @@
|
|
|
285
324
|
"relativePath": [
|
|
286
325
|
"dist",
|
|
287
326
|
"commands",
|
|
288
|
-
"
|
|
327
|
+
"emails",
|
|
289
328
|
"index.js"
|
|
290
329
|
]
|
|
291
330
|
},
|
|
@@ -341,13 +380,13 @@
|
|
|
341
380
|
"index.js"
|
|
342
381
|
]
|
|
343
382
|
},
|
|
344
|
-
"
|
|
383
|
+
"init": {
|
|
345
384
|
"aliases": [],
|
|
346
385
|
"args": {},
|
|
347
|
-
"description": "
|
|
386
|
+
"description": "Analyze your product and generate email sequences",
|
|
348
387
|
"examples": [
|
|
349
|
-
"<%= config.bin %>
|
|
350
|
-
"<%= config.bin %>
|
|
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": "
|
|
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
|
-
"
|
|
425
|
+
"init",
|
|
380
426
|
"index.js"
|
|
381
427
|
]
|
|
382
428
|
},
|
|
@@ -717,53 +763,7 @@
|
|
|
717
763
|
"status",
|
|
718
764
|
"index.js"
|
|
719
765
|
]
|
|
720
|
-
},
|
|
721
|
-
"init": {
|
|
722
|
-
"aliases": [],
|
|
723
|
-
"args": {},
|
|
724
|
-
"description": "Analyze your product and generate email sequences",
|
|
725
|
-
"examples": [
|
|
726
|
-
"<%= config.bin %> init",
|
|
727
|
-
"<%= config.bin %> init --url https://myapp.com --yes"
|
|
728
|
-
],
|
|
729
|
-
"flags": {
|
|
730
|
-
"json": {
|
|
731
|
-
"description": "Output as JSON",
|
|
732
|
-
"name": "json",
|
|
733
|
-
"allowNo": false,
|
|
734
|
-
"type": "boolean"
|
|
735
|
-
},
|
|
736
|
-
"yes": {
|
|
737
|
-
"char": "y",
|
|
738
|
-
"description": "Skip confirmation prompts",
|
|
739
|
-
"name": "yes",
|
|
740
|
-
"allowNo": false,
|
|
741
|
-
"type": "boolean"
|
|
742
|
-
},
|
|
743
|
-
"url": {
|
|
744
|
-
"description": "Product URL to analyze",
|
|
745
|
-
"name": "url",
|
|
746
|
-
"hasDynamicHelp": false,
|
|
747
|
-
"multiple": false,
|
|
748
|
-
"type": "option"
|
|
749
|
-
}
|
|
750
|
-
},
|
|
751
|
-
"hasDynamicHelp": false,
|
|
752
|
-
"hiddenAliases": [],
|
|
753
|
-
"id": "init",
|
|
754
|
-
"pluginAlias": "@mailmodo/cli",
|
|
755
|
-
"pluginName": "@mailmodo/cli",
|
|
756
|
-
"pluginType": "core",
|
|
757
|
-
"strict": true,
|
|
758
|
-
"enableJsonFlag": false,
|
|
759
|
-
"isESM": true,
|
|
760
|
-
"relativePath": [
|
|
761
|
-
"dist",
|
|
762
|
-
"commands",
|
|
763
|
-
"init",
|
|
764
|
-
"index.js"
|
|
765
|
-
]
|
|
766
766
|
}
|
|
767
767
|
},
|
|
768
|
-
"version": "0.0.54-beta.pr56.
|
|
768
|
+
"version": "0.0.54-beta.pr56.89"
|
|
769
769
|
}
|
package/package.json
CHANGED
|
@@ -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>;
|