@mailmodo/cli 0.0.56-beta.pr58.108 → 0.0.56-beta.pr58.95
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 +3 -5
- package/dist/commands/init/index.js +3 -11
- package/dist/commands/login/index.js +5 -2
- package/dist/lib/api-client.d.ts +0 -2
- package/dist/lib/api-client.js +2 -2
- package/dist/lib/base-command.d.ts +13 -10
- package/dist/lib/base-command.js +27 -86
- package/dist/lib/commands/login/output.d.ts +2 -2
- package/dist/lib/commands/login/output.js +18 -5
- package/dist/lib/config.d.ts +0 -2
- package/dist/lib/config.js +10 -19
- package/dist/lib/constants.d.ts +2 -3
- package/dist/lib/constants.js +5 -4
- package/dist/lib/messages.d.ts +0 -18
- package/dist/lib/messages.js +0 -38
- package/dist/lib/templates/missing-templates.d.ts +1 -1
- package/dist/lib/templates/missing-templates.js +2 -2
- package/dist/lib/yaml-config.d.ts +0 -1
- package/dist/lib/yaml-config.js +0 -8
- package/oclif.manifest.json +1 -168
- package/package.json +1 -1
- package/dist/commands/report/index.d.ts +0 -22
- package/dist/commands/report/index.js +0 -123
- package/dist/lib/commands/report/output-entries.d.ts +0 -2
- package/dist/lib/commands/report/output-entries.js +0 -59
- package/dist/lib/commands/report/output-timeseries.d.ts +0 -2
- package/dist/lib/commands/report/output-timeseries.js +0 -28
- package/dist/lib/commands/report/output.d.ts +0 -3
- package/dist/lib/commands/report/output.js +0 -56
- package/dist/lib/commands/report/payload.d.ts +0 -2
- package/dist/lib/commands/report/payload.js +0 -49
- package/dist/lib/commands/report/prompt.d.ts +0 -2
- package/dist/lib/commands/report/prompt.js +0 -82
- package/dist/lib/commands/report/types.d.ts +0 -97
- package/dist/lib/commands/report/types.js +0 -1
|
@@ -3,7 +3,7 @@ import { confirm } from '@inquirer/prompts';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
5
5
|
import { API_ENDPOINTS } from '../../lib/constants.js';
|
|
6
|
-
import { MISSING_TEMPLATES
|
|
6
|
+
import { MISSING_TEMPLATES } from '../../lib/messages.js';
|
|
7
7
|
import { buildDeployPayload } from '../../lib/commands/deploy/payload.js';
|
|
8
8
|
import { logDeploySuccessInstructions, logPreDeploySummary, } from '../../lib/commands/deploy/output.js';
|
|
9
9
|
import { pauseSequence, resumeSequence, } from '../../lib/commands/deploy/sequence-status.js';
|
|
@@ -44,10 +44,8 @@ export default class Deploy extends BaseCommand {
|
|
|
44
44
|
const yamlConfig = await this.ensureYaml();
|
|
45
45
|
const missingIds = getMissingTemplateIds(yamlConfig);
|
|
46
46
|
if (missingIds.length > 0) {
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
ctx.log(`\n ${chalk.green('✓')} ${restoredFromServerHint(missingIds)}\n`);
|
|
50
|
-
else if (result === 'regenerated')
|
|
47
|
+
const regenerated = await handleMissingTemplates(ctx, yamlConfig, missingIds, baseFlags);
|
|
48
|
+
if (regenerated)
|
|
51
49
|
ctx.log(`\n ${chalk.green('✓')} ${MISSING_TEMPLATES.REVIEW_HINT}\n`);
|
|
52
50
|
return;
|
|
53
51
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
3
3
|
import { API_ENDPOINTS } from '../../lib/constants.js';
|
|
4
|
-
import { loadYaml, saveYaml
|
|
4
|
+
import { loadYaml, saveYaml } from '../../lib/yaml-config.js';
|
|
5
5
|
import { analyzeProduct, promptProductUrl, } from '../../lib/commands/init/analysis.js';
|
|
6
6
|
import { confirmOverwrite, logInitSuccess, } from '../../lib/commands/init/output.js';
|
|
7
7
|
import { applyMonthlyCap, buildEmailConfigs, buildYamlConfig, preserveUserFields, saveAllTemplates, } from '../../lib/commands/init/payload.js';
|
|
@@ -20,13 +20,6 @@ export default class Init extends BaseCommand {
|
|
|
20
20
|
await this.ensureAuth();
|
|
21
21
|
const ctx = this.makeCtx();
|
|
22
22
|
const baseFlags = { json: flags.json, yes: flags.yes };
|
|
23
|
-
let serverYaml = null;
|
|
24
|
-
if (this.isBlankDirectory()) {
|
|
25
|
-
const result = await this.promptInitServerRestore(baseFlags);
|
|
26
|
-
if (result.restored)
|
|
27
|
-
return;
|
|
28
|
-
serverYaml = result.serverYaml;
|
|
29
|
-
}
|
|
30
23
|
const existing = await loadYaml();
|
|
31
24
|
if (!(await confirmOverwrite(ctx, baseFlags, existing)))
|
|
32
25
|
return;
|
|
@@ -40,9 +33,8 @@ export default class Init extends BaseCommand {
|
|
|
40
33
|
const generatedEmails = generateResponse.data?.emails || [];
|
|
41
34
|
const emailConfigs = buildEmailConfigs(analysisPayload, generatedEmails);
|
|
42
35
|
const yamlConfig = buildYamlConfig(analysisPayload, emailConfigs, productUrl);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
preserveUserFields(yamlConfig, fieldSource);
|
|
36
|
+
if (existing)
|
|
37
|
+
preserveUserFields(yamlConfig, existing);
|
|
46
38
|
await saveYaml(yamlConfig);
|
|
47
39
|
await applyMonthlyCap(ctx, yamlConfig);
|
|
48
40
|
await saveAllTemplates(analysisPayload.recommendedEmails, generatedEmails);
|
|
@@ -27,7 +27,9 @@ export default class Login extends BaseCommand {
|
|
|
27
27
|
if (!envKey) {
|
|
28
28
|
const existing = await loadConfig();
|
|
29
29
|
if (existing?.apiKey) {
|
|
30
|
-
|
|
30
|
+
const existingClient = new ApiClient(existing.apiKey);
|
|
31
|
+
const yamlRestored = await this.recoverYamlAfterLogin(existingClient);
|
|
32
|
+
logAlreadyLoggedIn(ctx, existing, yamlRestored, { json: flags.json });
|
|
31
33
|
return;
|
|
32
34
|
}
|
|
33
35
|
}
|
|
@@ -40,7 +42,8 @@ export default class Login extends BaseCommand {
|
|
|
40
42
|
}
|
|
41
43
|
const { email, totalFreeRemaining, paidEmailsRemaining, plan } = response.data;
|
|
42
44
|
await saveConfig({ apiKey: trimmedKey, email, totalFreeRemaining });
|
|
43
|
-
|
|
45
|
+
const yamlRestored = await this.recoverYamlAfterLogin(client);
|
|
46
|
+
logLoginSuccess(ctx, { email, paidEmailsRemaining, plan, totalFreeRemaining }, yamlRestored, { json: flags.json });
|
|
44
47
|
}
|
|
45
48
|
makeCtx() {
|
|
46
49
|
return {
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -8,8 +8,6 @@ export interface ApiRequestDebugInfo {
|
|
|
8
8
|
causeCode?: string;
|
|
9
9
|
/** Fully resolved request URL (origin, path, and query string). */
|
|
10
10
|
fullUrl: string;
|
|
11
|
-
/** Truncated JSON summary of the request body sent, when present. */
|
|
12
|
-
requestBody?: string;
|
|
13
11
|
/** Truncated JSON summary of a non-empty error response body, when available. */
|
|
14
12
|
responseSummary?: string;
|
|
15
13
|
}
|
package/dist/lib/api-client.js
CHANGED
|
@@ -82,12 +82,12 @@ export class ApiClient {
|
|
|
82
82
|
const data = await response.json().catch(() => ({}));
|
|
83
83
|
if (!response.ok) {
|
|
84
84
|
const errorData = data;
|
|
85
|
+
const summary = summarizeResponseBody(data);
|
|
85
86
|
return {
|
|
86
87
|
data: data,
|
|
87
88
|
debug: {
|
|
88
89
|
...debug,
|
|
89
|
-
|
|
90
|
-
responseSummary: summarizeResponseBody(data),
|
|
90
|
+
responseSummary: summary,
|
|
91
91
|
},
|
|
92
92
|
error: errorData?.message ||
|
|
93
93
|
errorData?.error ||
|
|
@@ -68,22 +68,25 @@ export declare abstract class BaseCommand extends Command {
|
|
|
68
68
|
* settings and all email sequence definitions.
|
|
69
69
|
*/
|
|
70
70
|
protected ensureYaml(): Promise<MailmodoYaml>;
|
|
71
|
-
protected isBlankDirectory(): boolean;
|
|
72
|
-
protected fetchYamlText(): Promise<null | string>;
|
|
73
|
-
private promptBlankDirRestore;
|
|
74
|
-
protected promptInitServerRestore(flags: {
|
|
75
|
-
json?: boolean;
|
|
76
|
-
yes?: boolean;
|
|
77
|
-
}): Promise<{
|
|
78
|
-
restored: boolean;
|
|
79
|
-
serverYaml: MailmodoYaml | null;
|
|
80
|
-
}>;
|
|
81
71
|
private fetchAndWriteYaml;
|
|
82
72
|
/**
|
|
83
73
|
* Attempts to fetch mailmodo.yaml from the server and save it locally.
|
|
84
74
|
* Returns null silently on any failure so callers can fall through to an error.
|
|
85
75
|
*/
|
|
86
76
|
private restoreYamlFromServer;
|
|
77
|
+
/**
|
|
78
|
+
* If `mailmodo.yaml` is absent from the current directory, attempts to restore
|
|
79
|
+
* it from the server using the given client. Returns `true` if the file was
|
|
80
|
+
* successfully written, `false` otherwise (file already present, server 404,
|
|
81
|
+
* or any network error). Silent — never throws.
|
|
82
|
+
*
|
|
83
|
+
* After ensuring the YAML is available, also silently restores any template
|
|
84
|
+
* files that are missing from the local mailmodo/ folder, so a returning user
|
|
85
|
+
* gets both their config and templates back without having to run init again.
|
|
86
|
+
*
|
|
87
|
+
* Used by `mailmodo login` right after the API key is validated.
|
|
88
|
+
*/
|
|
89
|
+
protected recoverYamlAfterLogin(client: ApiClient): Promise<boolean>;
|
|
87
90
|
/**
|
|
88
91
|
* Uploads the current local mailmodo.yaml to the server as a backup.
|
|
89
92
|
* Best-effort: silently ignores all errors so the originating command
|
package/dist/lib/base-command.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { input
|
|
4
|
+
import { input } from '@inquirer/prompts';
|
|
5
5
|
import { Command, Flags } from '@oclif/core';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import ora from 'ora';
|
|
8
8
|
import { ApiClient } from './api-client.js';
|
|
9
9
|
import { loadConfig } from './config.js';
|
|
10
|
-
import { API_ENDPOINTS, IS_DEV_MODE,
|
|
10
|
+
import { API_ENDPOINTS, IS_DEV_MODE, YAML_FILE } from './constants.js';
|
|
11
11
|
import { syncTemplatesToServer, syncTemplateToServer, fetchTemplatesFromServer, fetchTemplateFromServer, } from './templates/sync.js';
|
|
12
12
|
import { getMissingTemplateIds } from './templates/missing-templates.js';
|
|
13
|
-
import {
|
|
14
|
-
import { loadYaml,
|
|
13
|
+
import { ERRORS, INFO, PROMPTS, quotaExhaustedMessage, recordLabel, VALIDATION, } from './messages.js';
|
|
14
|
+
import { loadYaml, saveYaml } from './yaml-config.js';
|
|
15
15
|
export const FREE_TIER = 'free';
|
|
16
16
|
/**
|
|
17
17
|
* Abstract base command providing shared functionality for all Mailmodo CLI commands.
|
|
@@ -92,90 +92,11 @@ export class BaseCommand extends Command {
|
|
|
92
92
|
const config = await loadYaml();
|
|
93
93
|
if (config)
|
|
94
94
|
return config;
|
|
95
|
-
if (this.isBlankDirectory())
|
|
96
|
-
return this.promptBlankDirRestore();
|
|
97
95
|
const restored = await this.restoreYamlFromServer();
|
|
98
96
|
if (restored)
|
|
99
97
|
return restored;
|
|
100
98
|
this.error(ERRORS.NO_YAML);
|
|
101
99
|
}
|
|
102
|
-
isBlankDirectory() {
|
|
103
|
-
return (!existsSync(join(process.cwd(), YAML_FILE)) &&
|
|
104
|
-
!existsSync(join(process.cwd(), TEMPLATES_DIR)));
|
|
105
|
-
}
|
|
106
|
-
async fetchYamlText() {
|
|
107
|
-
try {
|
|
108
|
-
let client = this.apiClient;
|
|
109
|
-
if (!client) {
|
|
110
|
-
const apiKey = process.env.MAILMODO_API_KEY ?? (await loadConfig())?.apiKey;
|
|
111
|
-
if (!apiKey)
|
|
112
|
-
return null;
|
|
113
|
-
client = new ApiClient(apiKey);
|
|
114
|
-
}
|
|
115
|
-
const response = await client.getRawText(API_ENDPOINTS.ASSETS_YAML);
|
|
116
|
-
return response.ok && response.data ? response.data : null;
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
async promptBlankDirRestore() {
|
|
123
|
-
const yamlText = await this.fetchYamlText();
|
|
124
|
-
if (!yamlText)
|
|
125
|
-
this.error(ERRORS.NO_YAML);
|
|
126
|
-
const autoRestore = this.argv.includes('--yes') || this.argv.includes('--json');
|
|
127
|
-
if (!autoRestore) {
|
|
128
|
-
this.log(`\n ${BLANK_DIR.PROMPT}\n`);
|
|
129
|
-
const choice = await select({
|
|
130
|
-
choices: [
|
|
131
|
-
{ name: BLANK_DIR.CHOICE_RESTORE, value: 'restore' },
|
|
132
|
-
{ name: BLANK_DIR.CHOICE_SKIP, value: 'skip' },
|
|
133
|
-
],
|
|
134
|
-
message: 'How would you like to proceed?',
|
|
135
|
-
});
|
|
136
|
-
if (choice === 'skip') {
|
|
137
|
-
this.log(`\n ${BLANK_DIR.SKIP_HINT}\n`);
|
|
138
|
-
this.exit(0);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
await writeFile(join(process.cwd(), YAML_FILE), yamlText);
|
|
142
|
-
this.logToStderr(INFO.YAML_RESTORED_FROM_SERVER);
|
|
143
|
-
const yaml = await loadYaml();
|
|
144
|
-
if (!yaml)
|
|
145
|
-
this.error(ERRORS.NO_YAML);
|
|
146
|
-
const client = this.apiClient;
|
|
147
|
-
if (client)
|
|
148
|
-
await this.fetchMissingTemplates(client, yaml);
|
|
149
|
-
return yaml;
|
|
150
|
-
}
|
|
151
|
-
async promptInitServerRestore(flags) {
|
|
152
|
-
const yamlText = await this.fetchYamlText();
|
|
153
|
-
if (!yamlText)
|
|
154
|
-
return { restored: false, serverYaml: null };
|
|
155
|
-
let shouldRestore = Boolean(flags.yes || flags.json);
|
|
156
|
-
if (!shouldRestore) {
|
|
157
|
-
const choice = await select({
|
|
158
|
-
choices: [
|
|
159
|
-
{ name: BLANK_DIR.CHOICE_RESTORE_INIT, value: 'restore' },
|
|
160
|
-
{ name: BLANK_DIR.CHOICE_FRESH, value: 'fresh' },
|
|
161
|
-
],
|
|
162
|
-
message: BLANK_DIR.PROMPT_INIT,
|
|
163
|
-
});
|
|
164
|
-
shouldRestore = choice === 'restore';
|
|
165
|
-
}
|
|
166
|
-
if (!shouldRestore) {
|
|
167
|
-
return { restored: false, serverYaml: parseYamlText(yamlText) };
|
|
168
|
-
}
|
|
169
|
-
await writeFile(join(process.cwd(), YAML_FILE), yamlText);
|
|
170
|
-
const yaml = await loadYaml();
|
|
171
|
-
if (!yaml)
|
|
172
|
-
return { restored: false, serverYaml: null };
|
|
173
|
-
const client = this.apiClient;
|
|
174
|
-
if (client)
|
|
175
|
-
await this.fetchMissingTemplates(client, yaml);
|
|
176
|
-
this.log(`\n ${BLANK_DIR.RESTORED_INIT}\n`);
|
|
177
|
-
return { restored: true, serverYaml: yaml };
|
|
178
|
-
}
|
|
179
100
|
async fetchAndWriteYaml(client) {
|
|
180
101
|
try {
|
|
181
102
|
const response = await client.getRawText(API_ENDPOINTS.ASSETS_YAML);
|
|
@@ -212,6 +133,29 @@ export class BaseCommand extends Command {
|
|
|
212
133
|
return null;
|
|
213
134
|
}
|
|
214
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* If `mailmodo.yaml` is absent from the current directory, attempts to restore
|
|
138
|
+
* it from the server using the given client. Returns `true` if the file was
|
|
139
|
+
* successfully written, `false` otherwise (file already present, server 404,
|
|
140
|
+
* or any network error). Silent — never throws.
|
|
141
|
+
*
|
|
142
|
+
* After ensuring the YAML is available, also silently restores any template
|
|
143
|
+
* files that are missing from the local mailmodo/ folder, so a returning user
|
|
144
|
+
* gets both their config and templates back without having to run init again.
|
|
145
|
+
*
|
|
146
|
+
* Used by `mailmodo login` right after the API key is validated.
|
|
147
|
+
*/
|
|
148
|
+
async recoverYamlAfterLogin(client) {
|
|
149
|
+
let yamlRestored = false;
|
|
150
|
+
if (!existsSync(join(process.cwd(), YAML_FILE))) {
|
|
151
|
+
yamlRestored = await this.fetchAndWriteYaml(client);
|
|
152
|
+
}
|
|
153
|
+
// Always check for missing templates, whether YAML was just restored or already present
|
|
154
|
+
const yaml = await loadYaml();
|
|
155
|
+
if (yaml)
|
|
156
|
+
await this.fetchMissingTemplates(client, yaml);
|
|
157
|
+
return yamlRestored;
|
|
158
|
+
}
|
|
215
159
|
/**
|
|
216
160
|
* Uploads the current local mailmodo.yaml to the server as a backup.
|
|
217
161
|
* Best-effort: silently ignores all errors so the originating command
|
|
@@ -558,9 +502,6 @@ export class BaseCommand extends Command {
|
|
|
558
502
|
status > 0
|
|
559
503
|
? chalk.dim(` Status: ${String(status)}`)
|
|
560
504
|
: chalk.dim(' Status: (No HTTP response — network or client error)'),
|
|
561
|
-
...(debug.requestBody
|
|
562
|
-
? [chalk.dim(` Payload: ${debug.requestBody}`)]
|
|
563
|
-
: []),
|
|
564
505
|
...(debug.responseSummary
|
|
565
506
|
? [chalk.dim(` Response: ${debug.responseSummary}`)]
|
|
566
507
|
: []),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { LoginCtx, ValidateResponse } from './types.js';
|
|
2
2
|
import type { MailmodoConfig } from '../../config.js';
|
|
3
|
-
export declare function logAlreadyLoggedIn(ctx: LoginCtx, existing: MailmodoConfig, opts: {
|
|
3
|
+
export declare function logAlreadyLoggedIn(ctx: LoginCtx, existing: MailmodoConfig, yamlRestored: boolean, opts: {
|
|
4
4
|
json: boolean;
|
|
5
5
|
}): void;
|
|
6
|
-
export declare function logLoginSuccess(ctx: LoginCtx, data: Pick<ValidateResponse, 'email' | 'paidEmailsRemaining' | 'plan' | 'totalFreeRemaining'>, opts: {
|
|
6
|
+
export declare function logLoginSuccess(ctx: LoginCtx, data: Pick<ValidateResponse, 'email' | 'paidEmailsRemaining' | 'plan' | 'totalFreeRemaining'>, yamlRestored: boolean, opts: {
|
|
7
7
|
json: boolean;
|
|
8
8
|
}): void;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
|
|
2
|
+
import { INFO } from '../../messages.js';
|
|
3
|
+
export function logAlreadyLoggedIn(ctx, existing, yamlRestored, opts) {
|
|
3
4
|
if (opts.json) {
|
|
4
5
|
ctx.log(JSON.stringify({
|
|
5
6
|
email: existing.email ?? null,
|
|
6
7
|
status: 'already_logged_in',
|
|
7
8
|
totalFreeRemaining: existing.totalFreeRemaining ?? null,
|
|
9
|
+
yamlRestored,
|
|
8
10
|
}, null, 2));
|
|
9
11
|
return;
|
|
10
12
|
}
|
|
@@ -13,10 +15,15 @@ export function logAlreadyLoggedIn(ctx, existing, opts) {
|
|
|
13
15
|
? chalk.green(existing.email.trim())
|
|
14
16
|
: chalk.dim('(unknown)');
|
|
15
17
|
ctx.log(` Email: ${emailDisplay}\n`);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
if (yamlRestored) {
|
|
19
|
+
ctx.log(` ${INFO.YAML_RESTORED_ON_LOGIN}`);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
ctx.log(` ${chalk.dim('1.')} Run ${chalk.cyan('mailmodo init')} to generate an email sequence.`);
|
|
23
|
+
}
|
|
24
|
+
ctx.log(` ${chalk.dim(yamlRestored ? '1.' : '2.')} Run ${chalk.cyan('mailmodo logout')} to log in with another account.\n`);
|
|
18
25
|
}
|
|
19
|
-
export function logLoginSuccess(ctx, data, opts) {
|
|
26
|
+
export function logLoginSuccess(ctx, data, yamlRestored, opts) {
|
|
20
27
|
const { email, plan, totalFreeRemaining, paidEmailsRemaining } = data;
|
|
21
28
|
if (opts.json) {
|
|
22
29
|
ctx.log(JSON.stringify({
|
|
@@ -25,6 +32,7 @@ export function logLoginSuccess(ctx, data, opts) {
|
|
|
25
32
|
plan,
|
|
26
33
|
status: 'authenticated',
|
|
27
34
|
totalFreeRemaining,
|
|
35
|
+
yamlRestored,
|
|
28
36
|
}, null, 2));
|
|
29
37
|
return;
|
|
30
38
|
}
|
|
@@ -36,5 +44,10 @@ export function logLoginSuccess(ctx, data, opts) {
|
|
|
36
44
|
if (plan === 'paid') {
|
|
37
45
|
ctx.log(` Current paid block: ${chalk.cyan(String(paidEmailsRemaining))} emails remaining\n`);
|
|
38
46
|
}
|
|
39
|
-
|
|
47
|
+
if (yamlRestored) {
|
|
48
|
+
ctx.log(` ${INFO.YAML_RESTORED_ON_LOGIN}\n`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
ctx.log(` Next: Run ${chalk.cyan("'mailmodo init'")} to generate your email sequences.\n`);
|
|
52
|
+
}
|
|
40
53
|
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -28,8 +28,6 @@ export declare function saveConfig(config: MailmodoConfig): Promise<void>;
|
|
|
28
28
|
export declare function clearConfig(): Promise<void>;
|
|
29
29
|
/**
|
|
30
30
|
* Returns the absolute path to the Mailmodo config directory (~/.mailmodo).
|
|
31
|
-
* Honors MAILMODO_CONFIG_DIR env var when set (used in tests to redirect away
|
|
32
|
-
* from the user's real config).
|
|
33
31
|
*
|
|
34
32
|
* @returns {string} The config directory path.
|
|
35
33
|
*/
|
package/dist/lib/config.js
CHANGED
|
@@ -2,12 +2,8 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
function configFile() {
|
|
9
|
-
return join(configDir(), 'config');
|
|
10
|
-
}
|
|
5
|
+
const CONFIG_DIR = join(homedir(), '.mailmodo');
|
|
6
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config');
|
|
11
7
|
/**
|
|
12
8
|
* Loads the Mailmodo CLI configuration from ~/.mailmodo/config.
|
|
13
9
|
* The config file stores the API key and account metadata as JSON.
|
|
@@ -17,11 +13,10 @@ function configFile() {
|
|
|
17
13
|
* or null if the config file does not exist or is corrupted.
|
|
18
14
|
*/
|
|
19
15
|
export async function loadConfig() {
|
|
20
|
-
|
|
21
|
-
if (!existsSync(file))
|
|
16
|
+
if (!existsSync(CONFIG_FILE))
|
|
22
17
|
return null;
|
|
23
18
|
try {
|
|
24
|
-
const content = await readFile(
|
|
19
|
+
const content = await readFile(CONFIG_FILE, 'utf8');
|
|
25
20
|
return JSON.parse(content);
|
|
26
21
|
}
|
|
27
22
|
catch {
|
|
@@ -37,22 +32,20 @@ export async function loadConfig() {
|
|
|
37
32
|
* at minimum an apiKey. Optional fields: email, totalFreeRemaining.
|
|
38
33
|
*/
|
|
39
34
|
export async function saveConfig(config) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
await mkdir(dir, { recursive: true });
|
|
35
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
36
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
43
37
|
}
|
|
44
|
-
await writeFile(
|
|
38
|
+
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
45
39
|
}
|
|
46
40
|
/**
|
|
47
41
|
* Deletes the saved CLI config file (~/.mailmodo/config), removing the stored API key.
|
|
48
42
|
* No-op if the file does not exist.
|
|
49
43
|
*/
|
|
50
44
|
export async function clearConfig() {
|
|
51
|
-
|
|
52
|
-
if (!existsSync(file))
|
|
45
|
+
if (!existsSync(CONFIG_FILE))
|
|
53
46
|
return;
|
|
54
47
|
try {
|
|
55
|
-
await unlink(
|
|
48
|
+
await unlink(CONFIG_FILE);
|
|
56
49
|
}
|
|
57
50
|
catch {
|
|
58
51
|
// Ignore missing file or permission errors; caller can treat as best-effort sign-out.
|
|
@@ -60,11 +53,9 @@ export async function clearConfig() {
|
|
|
60
53
|
}
|
|
61
54
|
/**
|
|
62
55
|
* Returns the absolute path to the Mailmodo config directory (~/.mailmodo).
|
|
63
|
-
* Honors MAILMODO_CONFIG_DIR env var when set (used in tests to redirect away
|
|
64
|
-
* from the user's real config).
|
|
65
56
|
*
|
|
66
57
|
* @returns {string} The config directory path.
|
|
67
58
|
*/
|
|
68
59
|
export function getConfigDir() {
|
|
69
|
-
return
|
|
60
|
+
return CONFIG_DIR;
|
|
70
61
|
}
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* when this is true so end users never see internal request metadata.
|
|
5
5
|
*/
|
|
6
6
|
export declare const IS_DEV_MODE: boolean;
|
|
7
|
-
export declare const API_BASE_URL
|
|
7
|
+
export declare const API_BASE_URL = "https://app-vertex-debug.azurewebsites.net";
|
|
8
8
|
export declare const API_ENDPOINTS: Readonly<{
|
|
9
9
|
ANALYTICS: "/analytics";
|
|
10
10
|
ANALYZE: "/analyze";
|
|
@@ -26,13 +26,12 @@ export declare const API_ENDPOINTS: Readonly<{
|
|
|
26
26
|
GENERATE: "/email/generate";
|
|
27
27
|
LOGS: "/logs";
|
|
28
28
|
PREVIEW: "/preview";
|
|
29
|
-
REPORTS: "/reports";
|
|
30
29
|
SEQUENCES: "/sequences";
|
|
31
30
|
SEQUENCES_DEPLOY: "/sequences/deploy";
|
|
32
31
|
SEQUENCES_SDK: "/sequences/sdk";
|
|
33
32
|
SEQUENCES_VALIDATE: "/sequences/validate";
|
|
34
33
|
}>;
|
|
35
|
-
export declare const LOGIN_URL
|
|
34
|
+
export declare const LOGIN_URL = "https://app-vertex-debug.azurewebsites.net/signup.html";
|
|
36
35
|
export declare const PREVIEW_PORT = 3421;
|
|
37
36
|
export declare const DEFAULT_BRAND_COLOR = "#1A56DB";
|
|
38
37
|
export declare const TEMPLATES_DIR = "mailmodo";
|
package/dist/lib/constants.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/** Set by `bin/dev.js` when running the CLI locally (tsx bootstrap). */
|
|
2
2
|
const DEV_API_BASE_URL = 'https://app-vertex-debug.azurewebsites.net';
|
|
3
|
-
const PRODUCTION_API_BASE_URL = 'https://api.mailmodo.dev';
|
|
4
3
|
/**
|
|
5
4
|
* True when the CLI is running in a dev/debug context. Verbose request
|
|
6
5
|
* diagnostics (URL, status, response body, error code) are only surfaced
|
|
7
6
|
* when this is true so end users never see internal request metadata.
|
|
8
7
|
*/
|
|
9
8
|
export const IS_DEV_MODE = Boolean(process.env.MAILMODO_DEV_TSX || process.env.MAILMODO_DEBUG);
|
|
9
|
+
// const PRODUCTION_API_BASE_URL = 'https://api.mailmodo.com';
|
|
10
|
+
const PRODUCTION_API_BASE_URL = 'https://app-vertex-debug.azurewebsites.net';
|
|
10
11
|
export const API_BASE_URL = process.env.MAILMODO_DEV_TSX
|
|
11
12
|
? DEV_API_BASE_URL
|
|
12
13
|
: PRODUCTION_API_BASE_URL;
|
|
@@ -31,14 +32,14 @@ export const API_ENDPOINTS = Object.freeze({
|
|
|
31
32
|
GENERATE: '/email/generate',
|
|
32
33
|
LOGS: '/logs',
|
|
33
34
|
PREVIEW: '/preview',
|
|
34
|
-
REPORTS: '/reports',
|
|
35
35
|
SEQUENCES: '/sequences',
|
|
36
36
|
SEQUENCES_DEPLOY: '/sequences/deploy',
|
|
37
37
|
SEQUENCES_SDK: '/sequences/sdk',
|
|
38
38
|
SEQUENCES_VALIDATE: '/sequences/validate',
|
|
39
39
|
});
|
|
40
|
-
const DEV_LOGIN_URL = 'https://app-vertex-debug.azurewebsites.net/signup';
|
|
41
|
-
const PRODUCTION_LOGIN_URL = 'https://
|
|
40
|
+
const DEV_LOGIN_URL = 'https://app-vertex-debug.azurewebsites.net/signup.html';
|
|
41
|
+
// const PRODUCTION_LOGIN_URL = 'https://mailmodo.com/cli';
|
|
42
|
+
const PRODUCTION_LOGIN_URL = 'https://app-vertex-debug.azurewebsites.net/signup.html';
|
|
42
43
|
export const LOGIN_URL = process.env.MAILMODO_DEV_TSX
|
|
43
44
|
? DEV_LOGIN_URL
|
|
44
45
|
: PRODUCTION_LOGIN_URL;
|
package/dist/lib/messages.d.ts
CHANGED
|
@@ -13,16 +13,6 @@ export declare const PROMPTS: {
|
|
|
13
13
|
readonly REPLY_TO: "Reply-to address (optional, press Enter to use sender email):";
|
|
14
14
|
readonly SENDER_EMAIL: "Sender email address:";
|
|
15
15
|
};
|
|
16
|
-
export declare const BLANK_DIR: {
|
|
17
|
-
readonly CHOICE_FRESH: "Start fresh — new emails will be generated (your domain, sender, and address settings will be kept)";
|
|
18
|
-
readonly CHOICE_RESTORE: "Restore project files from server";
|
|
19
|
-
readonly CHOICE_RESTORE_INIT: "Restore existing project files from server";
|
|
20
|
-
readonly CHOICE_SKIP: "Skip — I'll navigate to my project directory manually";
|
|
21
|
-
readonly PROMPT: "No mailmodo.yaml or templates found in this directory.";
|
|
22
|
-
readonly PROMPT_INIT: "A project was found on the server. What would you like to do?";
|
|
23
|
-
readonly RESTORED_INIT: `Project restored from server. Run ${string} to re-deploy, or ${string} to review your emails.`;
|
|
24
|
-
readonly SKIP_HINT: `Navigate to your project directory and run your command there, or run ${string} to start a new project here.`;
|
|
25
|
-
};
|
|
26
16
|
export declare const MISSING_TEMPLATES: {
|
|
27
17
|
readonly ABORT_HINT: `Restore the missing files from version control, then run ${string} again.`;
|
|
28
18
|
readonly CHOICE_ABORT: "Abort (restore from version control)";
|
|
@@ -64,13 +54,6 @@ export declare const INFO: {
|
|
|
64
54
|
readonly YAML_RESTORED_FROM_SERVER: string;
|
|
65
55
|
readonly YAML_RESTORED_ON_LOGIN: ` mailmodo.yaml restored from server. Run ${string} to re-deploy your sequences.`;
|
|
66
56
|
};
|
|
67
|
-
export declare const REPORTS: {
|
|
68
|
-
readonly FETCH_SPINNER: " Fetching report...";
|
|
69
|
-
readonly FROM_TO_REQUIRED: "--from and --to must both be provided together.";
|
|
70
|
-
readonly NO_DATA: "No data found for the given filters and time range.";
|
|
71
|
-
readonly TIME_RANGE_REQUIRED: "A time range is required. Use --preset or --from/--to.";
|
|
72
|
-
readonly TOO_MANY_ITEMS: "Filter arrays cannot exceed 100 items each.";
|
|
73
|
-
};
|
|
74
57
|
export declare const DEPLOY: {
|
|
75
58
|
readonly CHANGES_HEADER: "Changes vs. last deployment:";
|
|
76
59
|
readonly DEPLOYING_HEADER: "Deploying:";
|
|
@@ -80,7 +63,6 @@ export declare const DEPLOY: {
|
|
|
80
63
|
readonly SDK_ONBOARDING_HEADER: string;
|
|
81
64
|
readonly SUCCESS: `${string} Emails are live.`;
|
|
82
65
|
};
|
|
83
|
-
export declare function restoredFromServerHint(ids: string[]): string;
|
|
84
66
|
export declare function pauseSuccess(sequenceId: string): string;
|
|
85
67
|
export declare function pauseAlready(sequenceId: string): string;
|
|
86
68
|
export declare function resumeSuccess(sequenceId: string): string;
|
package/dist/lib/messages.js
CHANGED
|
@@ -14,16 +14,6 @@ export const PROMPTS = {
|
|
|
14
14
|
REPLY_TO: 'Reply-to address (optional, press Enter to use sender email):',
|
|
15
15
|
SENDER_EMAIL: 'Sender email address:',
|
|
16
16
|
};
|
|
17
|
-
export const BLANK_DIR = {
|
|
18
|
-
CHOICE_FRESH: 'Start fresh — new emails will be generated (your domain, sender, and address settings will be kept)',
|
|
19
|
-
CHOICE_RESTORE: 'Restore project files from server',
|
|
20
|
-
CHOICE_RESTORE_INIT: 'Restore existing project files from server',
|
|
21
|
-
CHOICE_SKIP: "Skip — I'll navigate to my project directory manually",
|
|
22
|
-
PROMPT: 'No mailmodo.yaml or templates found in this directory.',
|
|
23
|
-
PROMPT_INIT: 'A project was found on the server. What would you like to do?',
|
|
24
|
-
RESTORED_INIT: `Project restored from server. Run ${chalk.cyan('mailmodo deploy')} to re-deploy, or ${chalk.cyan('mailmodo emails')} to review your emails.`,
|
|
25
|
-
SKIP_HINT: `Navigate to your project directory and run your command there, or run ${chalk.cyan('mailmodo init')} to start a new project here.`,
|
|
26
|
-
};
|
|
27
17
|
export const MISSING_TEMPLATES = {
|
|
28
18
|
ABORT_HINT: `Restore the missing files from version control, then run ${chalk.cyan('mailmodo deploy')} again.`,
|
|
29
19
|
CHOICE_ABORT: 'Abort (restore from version control)',
|
|
@@ -64,13 +54,6 @@ export const INFO = {
|
|
|
64
54
|
YAML_RESTORED_FROM_SERVER: chalk.dim(' mailmodo.yaml not found locally — restored from server.'),
|
|
65
55
|
YAML_RESTORED_ON_LOGIN: ` mailmodo.yaml restored from server. Run ${chalk.cyan("'mailmodo deploy'")} to re-deploy your sequences.`,
|
|
66
56
|
};
|
|
67
|
-
export const REPORTS = {
|
|
68
|
-
FETCH_SPINNER: ' Fetching report...',
|
|
69
|
-
FROM_TO_REQUIRED: '--from and --to must both be provided together.',
|
|
70
|
-
NO_DATA: 'No data found for the given filters and time range.',
|
|
71
|
-
TIME_RANGE_REQUIRED: 'A time range is required. Use --preset or --from/--to.',
|
|
72
|
-
TOO_MANY_ITEMS: 'Filter arrays cannot exceed 100 items each.',
|
|
73
|
-
};
|
|
74
57
|
export const DEPLOY = {
|
|
75
58
|
CHANGES_HEADER: 'Changes vs. last deployment:',
|
|
76
59
|
DEPLOYING_HEADER: 'Deploying:',
|
|
@@ -80,27 +63,6 @@ export const DEPLOY = {
|
|
|
80
63
|
SDK_ONBOARDING_HEADER: chalk.bold('ADD THIS TO YOUR APP (one-time only):'),
|
|
81
64
|
SUCCESS: `${chalk.green('Deployed.')} Emails are live.`,
|
|
82
65
|
};
|
|
83
|
-
export function restoredFromServerHint(ids) {
|
|
84
|
-
if (ids.length === 1) {
|
|
85
|
-
const [id] = ids;
|
|
86
|
-
return (`Template ${chalk.cyan(id)} was not found locally and has been refreshed from the server.\n` +
|
|
87
|
-
` Review it with ${chalk.cyan(`mailmodo preview ${id}`)}, then run ${chalk.cyan('mailmodo deploy')} again.`);
|
|
88
|
-
}
|
|
89
|
-
const header = 'Template ID';
|
|
90
|
-
const colWidth = Math.max(header.length, ...ids.map((id) => id.length));
|
|
91
|
-
const pad = (s) => s.padEnd(colWidth);
|
|
92
|
-
const hr = '─'.repeat(colWidth + 2);
|
|
93
|
-
const table = [
|
|
94
|
-
` ┌${hr}┐`,
|
|
95
|
-
` │ ${chalk.bold(pad(header))} │`,
|
|
96
|
-
` ├${hr}┤`,
|
|
97
|
-
...ids.map((id) => ` │ ${chalk.cyan(pad(id))} │`),
|
|
98
|
-
` └${hr}┘`,
|
|
99
|
-
].join('\n');
|
|
100
|
-
return (`${ids.length} templates were not found locally and have been refreshed from the server:\n\n` +
|
|
101
|
-
`${table}\n\n` +
|
|
102
|
-
` Review each with ${chalk.cyan('mailmodo preview <id>')}, then run ${chalk.cyan('mailmodo deploy')} again.`);
|
|
103
|
-
}
|
|
104
66
|
export function pauseSuccess(sequenceId) {
|
|
105
67
|
return `Sequence ${chalk.cyan(sequenceId)} paused. Run ${chalk.cyan(`mailmodo deploy --resume ${sequenceId}`)} to resume.`;
|
|
106
68
|
}
|
|
@@ -16,4 +16,4 @@ export declare function getMissingTemplateIds(yamlConfig: MailmodoYaml): string[
|
|
|
16
16
|
* Returns true if the situation was resolved (restored or regenerated), false
|
|
17
17
|
* if the user chose to abort.
|
|
18
18
|
*/
|
|
19
|
-
export declare function handleMissingTemplates(ctx: RegenCtx, yamlConfig: MailmodoYaml, missingIds: string[], flags: DeployFlags): Promise<
|
|
19
|
+
export declare function handleMissingTemplates(ctx: RegenCtx, yamlConfig: MailmodoYaml, missingIds: string[], flags: DeployFlags): Promise<boolean>;
|
|
@@ -39,7 +39,7 @@ export async function handleMissingTemplates(ctx, yamlConfig, missingIds, flags)
|
|
|
39
39
|
// Try to silently recover from server before interrupting the user
|
|
40
40
|
const stillMissing = await silentlyRestoreFromServer(ctx, missingIds);
|
|
41
41
|
if (stillMissing.length === 0)
|
|
42
|
-
return
|
|
42
|
+
return true;
|
|
43
43
|
if (flags.json) {
|
|
44
44
|
ctx.log(JSON.stringify({
|
|
45
45
|
error: 'missing_templates',
|
|
@@ -69,5 +69,5 @@ export async function handleMissingTemplates(ctx, yamlConfig, missingIds, flags)
|
|
|
69
69
|
return false;
|
|
70
70
|
}
|
|
71
71
|
await regenerateMissingTemplates(ctx, yamlConfig, stillMissing, flags);
|
|
72
|
-
return
|
|
72
|
+
return true;
|
|
73
73
|
}
|
|
@@ -44,7 +44,6 @@ export interface MailmodoYaml {
|
|
|
44
44
|
* formatted Error with the line number if the file contains invalid YAML syntax.
|
|
45
45
|
*/
|
|
46
46
|
export declare function loadYaml(cwd?: string): Promise<MailmodoYaml | null>;
|
|
47
|
-
export declare function parseYamlText(text: string): MailmodoYaml | null;
|
|
48
47
|
/**
|
|
49
48
|
* Serializes and writes the mailmodo.yaml configuration to disk.
|
|
50
49
|
*
|
package/dist/lib/yaml-config.js
CHANGED
|
@@ -25,14 +25,6 @@ export async function loadYaml(cwd) {
|
|
|
25
25
|
throw new Error(yamlParseError(error.message));
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
export function parseYamlText(text) {
|
|
29
|
-
try {
|
|
30
|
-
return load(text);
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
28
|
/**
|
|
37
29
|
* Serializes and writes the mailmodo.yaml configuration to disk.
|
|
38
30
|
*
|