@wraps.dev/cli 1.5.0 → 1.5.2
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/README.md +63 -0
- package/dist/cli.js +1273 -674
- package/dist/cli.js.map +1 -1
- package/dist/lambda/event-processor/.bundled +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -146,7 +146,7 @@ var require_package = __commonJS({
|
|
|
146
146
|
"package.json"(exports, module) {
|
|
147
147
|
module.exports = {
|
|
148
148
|
name: "@wraps.dev/cli",
|
|
149
|
-
version: "1.5.
|
|
149
|
+
version: "1.5.2",
|
|
150
150
|
description: "CLI for deploying Wraps email infrastructure to your AWS account",
|
|
151
151
|
type: "module",
|
|
152
152
|
main: "./dist/cli.js",
|
|
@@ -448,6 +448,21 @@ function trackCommand(command, metadata) {
|
|
|
448
448
|
sanitized.email = void 0;
|
|
449
449
|
client.track(`command:${command}`, sanitized);
|
|
450
450
|
}
|
|
451
|
+
function trackServiceInit(service, success, metadata) {
|
|
452
|
+
const client = getTelemetryClient();
|
|
453
|
+
client.track("service:init", {
|
|
454
|
+
service,
|
|
455
|
+
success,
|
|
456
|
+
...metadata
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
function trackServiceDeployed(service, metadata) {
|
|
460
|
+
const client = getTelemetryClient();
|
|
461
|
+
client.track("service:deployed", {
|
|
462
|
+
service,
|
|
463
|
+
...metadata
|
|
464
|
+
});
|
|
465
|
+
}
|
|
451
466
|
function trackError(errorCode, command, metadata) {
|
|
452
467
|
const client = getTelemetryClient();
|
|
453
468
|
client.track("error:occurred", {
|
|
@@ -456,6 +471,24 @@ function trackError(errorCode, command, metadata) {
|
|
|
456
471
|
...metadata
|
|
457
472
|
});
|
|
458
473
|
}
|
|
474
|
+
function trackFeature(feature, metadata) {
|
|
475
|
+
const client = getTelemetryClient();
|
|
476
|
+
client.track(`feature:${feature}`, metadata || {});
|
|
477
|
+
}
|
|
478
|
+
function trackServiceUpgrade(service, metadata) {
|
|
479
|
+
const client = getTelemetryClient();
|
|
480
|
+
client.track("service:upgraded", {
|
|
481
|
+
service,
|
|
482
|
+
...metadata
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
function trackServiceRemoved(service, metadata) {
|
|
486
|
+
const client = getTelemetryClient();
|
|
487
|
+
client.track("service:removed", {
|
|
488
|
+
service,
|
|
489
|
+
...metadata
|
|
490
|
+
});
|
|
491
|
+
}
|
|
459
492
|
var init_events = __esm({
|
|
460
493
|
"src/telemetry/events.ts"() {
|
|
461
494
|
"use strict";
|
|
@@ -519,7 +552,7 @@ var init_errors = __esm({
|
|
|
519
552
|
stackExists: (stackName) => new WrapsError(
|
|
520
553
|
`Stack "${stackName}" already exists`,
|
|
521
554
|
"STACK_EXISTS",
|
|
522
|
-
`To update: wraps upgrade
|
|
555
|
+
`To update: wraps email upgrade
|
|
523
556
|
To remove: wraps destroy --stack ${stackName}`,
|
|
524
557
|
"https://wraps.dev/docs/cli/upgrade"
|
|
525
558
|
),
|
|
@@ -1559,7 +1592,7 @@ function getPresetInfo(preset) {
|
|
|
1559
1592
|
volume: "10k-500k emails/month",
|
|
1560
1593
|
features: [
|
|
1561
1594
|
"Everything in Starter",
|
|
1562
|
-
"Reputation
|
|
1595
|
+
"Reputation tracking",
|
|
1563
1596
|
"Real-time event tracking (EventBridge)",
|
|
1564
1597
|
"90-day email history storage",
|
|
1565
1598
|
"Optional: Email archiving with rendered viewer",
|
|
@@ -1768,16 +1801,16 @@ async function promptProvider() {
|
|
|
1768
1801
|
const provider = await clack4.select({
|
|
1769
1802
|
message: "Where is your app hosted?",
|
|
1770
1803
|
options: [
|
|
1771
|
-
{
|
|
1772
|
-
value: "vercel",
|
|
1773
|
-
label: "Vercel",
|
|
1774
|
-
hint: "Uses OIDC (no AWS credentials needed)"
|
|
1775
|
-
},
|
|
1776
1804
|
{
|
|
1777
1805
|
value: "aws",
|
|
1778
1806
|
label: "AWS (Lambda/ECS/EC2)",
|
|
1779
1807
|
hint: "Uses IAM roles automatically"
|
|
1780
1808
|
},
|
|
1809
|
+
{
|
|
1810
|
+
value: "vercel",
|
|
1811
|
+
label: "Vercel",
|
|
1812
|
+
hint: "Uses OIDC (no AWS credentials needed)"
|
|
1813
|
+
},
|
|
1781
1814
|
{
|
|
1782
1815
|
value: "railway",
|
|
1783
1816
|
label: "Railway",
|
|
@@ -2171,6 +2204,14 @@ async function promptEmailArchiving() {
|
|
|
2171
2204
|
async function promptCustomConfig(existingConfig) {
|
|
2172
2205
|
clack4.log.info("Custom configuration builder");
|
|
2173
2206
|
clack4.log.info("Configure each feature individually");
|
|
2207
|
+
const reputationMetrics = await clack4.confirm({
|
|
2208
|
+
message: "Enable reputation tracking (recommended)?",
|
|
2209
|
+
initialValue: existingConfig?.reputationMetrics ?? true
|
|
2210
|
+
});
|
|
2211
|
+
if (clack4.isCancel(reputationMetrics)) {
|
|
2212
|
+
clack4.cancel("Operation cancelled.");
|
|
2213
|
+
process.exit(0);
|
|
2214
|
+
}
|
|
2174
2215
|
const trackingEnabled = await clack4.confirm({
|
|
2175
2216
|
message: "Enable open & click tracking?",
|
|
2176
2217
|
initialValue: existingConfig?.tracking?.enabled ?? true
|
|
@@ -2180,49 +2221,38 @@ async function promptCustomConfig(existingConfig) {
|
|
|
2180
2221
|
process.exit(0);
|
|
2181
2222
|
}
|
|
2182
2223
|
const eventTrackingEnabled = await clack4.confirm({
|
|
2183
|
-
message: "
|
|
2224
|
+
message: "Store email events in DynamoDB?",
|
|
2184
2225
|
initialValue: existingConfig?.eventTracking?.enabled ?? true
|
|
2185
2226
|
});
|
|
2186
2227
|
if (clack4.isCancel(eventTrackingEnabled)) {
|
|
2187
2228
|
clack4.cancel("Operation cancelled.");
|
|
2188
2229
|
process.exit(0);
|
|
2189
2230
|
}
|
|
2190
|
-
let dynamoDBHistory = false;
|
|
2191
2231
|
let archiveRetention = "90days";
|
|
2192
2232
|
if (eventTrackingEnabled) {
|
|
2193
|
-
|
|
2194
|
-
message: "
|
|
2195
|
-
|
|
2233
|
+
archiveRetention = await clack4.select({
|
|
2234
|
+
message: "Event history retention period:",
|
|
2235
|
+
options: [
|
|
2236
|
+
{ value: "7days", label: "7 days", hint: "Minimal storage cost" },
|
|
2237
|
+
{ value: "30days", label: "30 days", hint: "Development/testing" },
|
|
2238
|
+
{
|
|
2239
|
+
value: "90days",
|
|
2240
|
+
label: "90 days (recommended)",
|
|
2241
|
+
hint: "Standard retention"
|
|
2242
|
+
},
|
|
2243
|
+
{ value: "1year", label: "1 year", hint: "Compliance requirements" },
|
|
2244
|
+
{
|
|
2245
|
+
value: "indefinite",
|
|
2246
|
+
label: "Indefinite",
|
|
2247
|
+
hint: "Higher storage cost"
|
|
2248
|
+
}
|
|
2249
|
+
],
|
|
2250
|
+
initialValue: existingConfig?.eventTracking?.archiveRetention || "90days"
|
|
2196
2251
|
});
|
|
2197
|
-
if (clack4.isCancel(
|
|
2252
|
+
if (clack4.isCancel(archiveRetention)) {
|
|
2198
2253
|
clack4.cancel("Operation cancelled.");
|
|
2199
2254
|
process.exit(0);
|
|
2200
2255
|
}
|
|
2201
|
-
if (dynamoDBHistory) {
|
|
2202
|
-
archiveRetention = await clack4.select({
|
|
2203
|
-
message: "Email history retention period:",
|
|
2204
|
-
options: [
|
|
2205
|
-
{ value: "7days", label: "7 days", hint: "Minimal storage cost" },
|
|
2206
|
-
{ value: "30days", label: "30 days", hint: "Development/testing" },
|
|
2207
|
-
{
|
|
2208
|
-
value: "90days",
|
|
2209
|
-
label: "90 days (recommended)",
|
|
2210
|
-
hint: "Standard retention"
|
|
2211
|
-
},
|
|
2212
|
-
{ value: "1year", label: "1 year", hint: "Compliance requirements" },
|
|
2213
|
-
{
|
|
2214
|
-
value: "indefinite",
|
|
2215
|
-
label: "Indefinite",
|
|
2216
|
-
hint: "Higher storage cost"
|
|
2217
|
-
}
|
|
2218
|
-
],
|
|
2219
|
-
initialValue: existingConfig?.eventTracking?.archiveRetention || "90days"
|
|
2220
|
-
});
|
|
2221
|
-
if (clack4.isCancel(archiveRetention)) {
|
|
2222
|
-
clack4.cancel("Operation cancelled.");
|
|
2223
|
-
process.exit(0);
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
2256
|
}
|
|
2227
2257
|
const tlsRequired = await clack4.confirm({
|
|
2228
2258
|
message: "Require TLS encryption for all emails?",
|
|
@@ -2232,14 +2262,40 @@ async function promptCustomConfig(existingConfig) {
|
|
|
2232
2262
|
clack4.cancel("Operation cancelled.");
|
|
2233
2263
|
process.exit(0);
|
|
2234
2264
|
}
|
|
2235
|
-
const
|
|
2236
|
-
message: "
|
|
2237
|
-
initialValue: existingConfig?.
|
|
2265
|
+
const customMailFrom = await clack4.confirm({
|
|
2266
|
+
message: "Configure custom MAIL FROM domain? (improves DMARC alignment)",
|
|
2267
|
+
initialValue: existingConfig?.mailFromDomain !== void 0
|
|
2238
2268
|
});
|
|
2239
|
-
if (clack4.isCancel(
|
|
2269
|
+
if (clack4.isCancel(customMailFrom)) {
|
|
2240
2270
|
clack4.cancel("Operation cancelled.");
|
|
2241
2271
|
process.exit(0);
|
|
2242
2272
|
}
|
|
2273
|
+
let mailFromSubdomain = "mail";
|
|
2274
|
+
if (customMailFrom) {
|
|
2275
|
+
mailFromSubdomain = await clack4.text({
|
|
2276
|
+
message: "MAIL FROM subdomain:",
|
|
2277
|
+
placeholder: "mail",
|
|
2278
|
+
initialValue: existingConfig?.mailFromDomain?.split(".")[0] || "mail",
|
|
2279
|
+
validate: (value) => {
|
|
2280
|
+
if (!value || value.trim() === "") {
|
|
2281
|
+
return "Subdomain is required";
|
|
2282
|
+
}
|
|
2283
|
+
if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i.test(value)) {
|
|
2284
|
+
return "Invalid subdomain format";
|
|
2285
|
+
}
|
|
2286
|
+
return void 0;
|
|
2287
|
+
}
|
|
2288
|
+
});
|
|
2289
|
+
if (clack4.isCancel(mailFromSubdomain)) {
|
|
2290
|
+
clack4.cancel("Operation cancelled.");
|
|
2291
|
+
process.exit(0);
|
|
2292
|
+
}
|
|
2293
|
+
clack4.log.info(
|
|
2294
|
+
pc5.dim(
|
|
2295
|
+
`MAIL FROM will be set to ${mailFromSubdomain}.yourdomain.com`
|
|
2296
|
+
)
|
|
2297
|
+
);
|
|
2298
|
+
}
|
|
2243
2299
|
const dedicatedIp = await clack4.confirm({
|
|
2244
2300
|
message: "Request dedicated IP address? (requires 100k+ emails/day)",
|
|
2245
2301
|
initialValue: existingConfig?.dedicatedIp ?? false
|
|
@@ -2303,6 +2359,7 @@ async function promptCustomConfig(existingConfig) {
|
|
|
2303
2359
|
} : { enabled: false },
|
|
2304
2360
|
tlsRequired,
|
|
2305
2361
|
reputationMetrics,
|
|
2362
|
+
mailFromSubdomain: customMailFrom ? typeof mailFromSubdomain === "string" ? mailFromSubdomain : "mail" : void 0,
|
|
2306
2363
|
suppressionList: {
|
|
2307
2364
|
enabled: true,
|
|
2308
2365
|
reasons: ["BOUNCE", "COMPLAINT"]
|
|
@@ -2320,7 +2377,7 @@ async function promptCustomConfig(existingConfig) {
|
|
|
2320
2377
|
"REJECT",
|
|
2321
2378
|
"RENDERING_FAILURE"
|
|
2322
2379
|
],
|
|
2323
|
-
dynamoDBHistory:
|
|
2380
|
+
dynamoDBHistory: true,
|
|
2324
2381
|
archiveRetention: typeof archiveRetention === "string" ? archiveRetention : "90days"
|
|
2325
2382
|
} : { enabled: false },
|
|
2326
2383
|
emailArchiving: emailArchivingEnabled ? {
|
|
@@ -2661,9 +2718,9 @@ init_esm_shims();
|
|
|
2661
2718
|
import { readFileSync } from "fs";
|
|
2662
2719
|
import { dirname as dirname2, join as join4 } from "path";
|
|
2663
2720
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
2664
|
-
import * as
|
|
2721
|
+
import * as clack16 from "@clack/prompts";
|
|
2665
2722
|
import args from "args";
|
|
2666
|
-
import
|
|
2723
|
+
import pc17 from "picocolors";
|
|
2667
2724
|
|
|
2668
2725
|
// src/commands/dashboard/update-role.ts
|
|
2669
2726
|
init_esm_shims();
|
|
@@ -2954,13 +3011,15 @@ Verification should complete within a few minutes.`,
|
|
|
2954
3011
|
)
|
|
2955
3012
|
];
|
|
2956
3013
|
if (domain) {
|
|
3014
|
+
const dmarcRuaDomain = outputs.mailFromDomain || domain;
|
|
2957
3015
|
dnsLines.push(
|
|
2958
3016
|
"",
|
|
2959
3017
|
pc2.bold("SPF Record (TXT):"),
|
|
2960
3018
|
` ${pc2.cyan(domain)} ${pc2.dim("TXT")} "v=spf1 include:amazonses.com ~all"`,
|
|
3019
|
+
pc2.dim(" Note: If you have an existing SPF record, add 'include:amazonses.com' to it"),
|
|
2961
3020
|
"",
|
|
2962
3021
|
pc2.bold("DMARC Record (TXT):"),
|
|
2963
|
-
` ${pc2.cyan(`_dmarc.${domain}`)} ${pc2.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${
|
|
3022
|
+
` ${pc2.cyan(`_dmarc.${domain}`)} ${pc2.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}"`
|
|
2964
3023
|
);
|
|
2965
3024
|
if (outputs.mailFromDomain) {
|
|
2966
3025
|
dnsLines.push(
|
|
@@ -3018,7 +3077,7 @@ Verification should complete within a few minutes.`,
|
|
|
3018
3077
|
if (outputs.customTrackingDomain) {
|
|
3019
3078
|
console.log(
|
|
3020
3079
|
`
|
|
3021
|
-
${pc2.dim("Run:")} ${pc2.yellow(`wraps verify --domain ${outputs.customTrackingDomain}`)} ${pc2.dim(
|
|
3080
|
+
${pc2.dim("Run:")} ${pc2.yellow(`wraps email verify --domain ${outputs.customTrackingDomain}`)} ${pc2.dim(
|
|
3022
3081
|
"(after DNS propagates)"
|
|
3023
3082
|
)}
|
|
3024
3083
|
`
|
|
@@ -3070,7 +3129,7 @@ ${domainStrings.join("\n")}`);
|
|
|
3070
3129
|
);
|
|
3071
3130
|
} else {
|
|
3072
3131
|
featureLines.push(
|
|
3073
|
-
` ${pc2.dim("\u25CB")} Email Tracking ${pc2.dim("(run 'wraps upgrade' to enable)")}`
|
|
3132
|
+
` ${pc2.dim("\u25CB")} Email Tracking ${pc2.dim("(run 'wraps email upgrade' to enable)")}`
|
|
3074
3133
|
);
|
|
3075
3134
|
}
|
|
3076
3135
|
if (status2.resources.lambdaFunctions && status2.resources.lambdaFunctions > 0) {
|
|
@@ -3079,7 +3138,7 @@ ${domainStrings.join("\n")}`);
|
|
|
3079
3138
|
);
|
|
3080
3139
|
} else {
|
|
3081
3140
|
featureLines.push(
|
|
3082
|
-
` ${pc2.dim("\u25CB")} Bounce/Complaint Handling ${pc2.dim("(run 'wraps upgrade' to enable)")}`
|
|
3141
|
+
` ${pc2.dim("\u25CB")} Bounce/Complaint Handling ${pc2.dim("(run 'wraps email upgrade' to enable)")}`
|
|
3083
3142
|
);
|
|
3084
3143
|
}
|
|
3085
3144
|
if (status2.resources.archivingEnabled) {
|
|
@@ -3096,7 +3155,7 @@ ${domainStrings.join("\n")}`);
|
|
|
3096
3155
|
);
|
|
3097
3156
|
} else {
|
|
3098
3157
|
featureLines.push(
|
|
3099
|
-
` ${pc2.dim("\u25CB")} Email Archiving ${pc2.dim("(run 'wraps upgrade' to enable)")}`
|
|
3158
|
+
` ${pc2.dim("\u25CB")} Email Archiving ${pc2.dim("(run 'wraps email upgrade' to enable)")}`
|
|
3100
3159
|
);
|
|
3101
3160
|
}
|
|
3102
3161
|
if (status2.tracking?.customTrackingDomain) {
|
|
@@ -3109,7 +3168,7 @@ ${domainStrings.join("\n")}`);
|
|
|
3109
3168
|
featureLines.push(` ${pc2.cyan(status2.tracking.customTrackingDomain)}`);
|
|
3110
3169
|
} else {
|
|
3111
3170
|
featureLines.push(
|
|
3112
|
-
` ${pc2.dim("\u25CB")} Custom Tracking Domain ${pc2.dim("(run 'wraps upgrade' to enable)")}`
|
|
3171
|
+
` ${pc2.dim("\u25CB")} Custom Tracking Domain ${pc2.dim("(run 'wraps email upgrade' to enable)")}`
|
|
3113
3172
|
);
|
|
3114
3173
|
}
|
|
3115
3174
|
featureLines.push(
|
|
@@ -3157,6 +3216,7 @@ ${domainStrings.join("\n")}`);
|
|
|
3157
3216
|
for (const domain of domainsNeedingDNS) {
|
|
3158
3217
|
const dnsLines = [];
|
|
3159
3218
|
if (domain.status === "pending" && domain.dkimTokens && domain.dkimTokens.length > 0) {
|
|
3219
|
+
const dmarcRuaDomain = domain.mailFromDomain || domain.domain;
|
|
3160
3220
|
dnsLines.push(
|
|
3161
3221
|
pc2.bold("DKIM Records (CNAME):"),
|
|
3162
3222
|
...domain.dkimTokens.map(
|
|
@@ -3165,9 +3225,10 @@ ${domainStrings.join("\n")}`);
|
|
|
3165
3225
|
"",
|
|
3166
3226
|
pc2.bold("SPF Record (TXT):"),
|
|
3167
3227
|
` ${pc2.cyan(domain.domain)} ${pc2.dim("TXT")} "v=spf1 include:amazonses.com ~all"`,
|
|
3228
|
+
pc2.dim(" Note: If you have an existing SPF record, add 'include:amazonses.com' to it"),
|
|
3168
3229
|
"",
|
|
3169
3230
|
pc2.bold("DMARC Record (TXT):"),
|
|
3170
|
-
` ${pc2.cyan(`_dmarc.${domain.domain}`)} ${pc2.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${
|
|
3231
|
+
` ${pc2.cyan(`_dmarc.${domain.domain}`)} ${pc2.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}"`
|
|
3171
3232
|
);
|
|
3172
3233
|
}
|
|
3173
3234
|
if (domain.mailFromDomain && domain.mailFromStatus !== "SUCCESS") {
|
|
@@ -3187,7 +3248,7 @@ ${domainStrings.join("\n")}`);
|
|
|
3187
3248
|
const exampleDomain = domainsNeedingDNS[0].domain;
|
|
3188
3249
|
console.log(
|
|
3189
3250
|
`
|
|
3190
|
-
${pc2.dim("Run:")} ${pc2.yellow(`wraps verify --domain ${exampleDomain}`)} ${pc2.dim(
|
|
3251
|
+
${pc2.dim("Run:")} ${pc2.yellow(`wraps email verify --domain ${exampleDomain}`)} ${pc2.dim(
|
|
3191
3252
|
"(after DNS propagates)"
|
|
3192
3253
|
)}
|
|
3193
3254
|
`
|
|
@@ -4133,20 +4194,22 @@ async function createSESResources(config2) {
|
|
|
4133
4194
|
dkimTokens = domainIdentity.dkimSigningAttributes.apply(
|
|
4134
4195
|
(attrs) => attrs?.tokens || []
|
|
4135
4196
|
);
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4197
|
+
if (config2.mailFromDomain) {
|
|
4198
|
+
mailFromDomain = config2.mailFromDomain;
|
|
4199
|
+
new aws5.sesv2.EmailIdentityMailFromAttributes(
|
|
4200
|
+
"wraps-email-mail-from",
|
|
4201
|
+
{
|
|
4202
|
+
emailIdentity: config2.domain,
|
|
4203
|
+
mailFromDomain,
|
|
4204
|
+
behaviorOnMxFailure: "USE_DEFAULT_VALUE"
|
|
4205
|
+
// Fallback to amazonses.com if MX record fails
|
|
4206
|
+
},
|
|
4207
|
+
{
|
|
4208
|
+
dependsOn: [domainIdentity]
|
|
4209
|
+
// Ensure domain identity exists first
|
|
4210
|
+
}
|
|
4211
|
+
);
|
|
4212
|
+
}
|
|
4150
4213
|
}
|
|
4151
4214
|
return {
|
|
4152
4215
|
configSet,
|
|
@@ -4282,8 +4345,8 @@ async function deployEmailStack(config2) {
|
|
|
4282
4345
|
let cloudFrontResources;
|
|
4283
4346
|
let acmResources;
|
|
4284
4347
|
if (emailConfig.tracking?.enabled && emailConfig.tracking.customRedirectDomain && emailConfig.tracking.httpsEnabled) {
|
|
4285
|
-
const { findHostedZone:
|
|
4286
|
-
const hostedZone = await
|
|
4348
|
+
const { findHostedZone: findHostedZone3 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
4349
|
+
const hostedZone = await findHostedZone3(
|
|
4287
4350
|
emailConfig.tracking.customRedirectDomain,
|
|
4288
4351
|
config2.region
|
|
4289
4352
|
);
|
|
@@ -4309,9 +4372,13 @@ async function deployEmailStack(config2) {
|
|
|
4309
4372
|
"wraps-email-eventbridge",
|
|
4310
4373
|
config2.region
|
|
4311
4374
|
);
|
|
4375
|
+
let mailFromDomain = emailConfig.mailFromDomain;
|
|
4376
|
+
if (!mailFromDomain && emailConfig.mailFromSubdomain && emailConfig.domain) {
|
|
4377
|
+
mailFromDomain = `${emailConfig.mailFromSubdomain}.${emailConfig.domain}`;
|
|
4378
|
+
}
|
|
4312
4379
|
sesResources = await createSESResources({
|
|
4313
4380
|
domain: emailConfig.domain,
|
|
4314
|
-
mailFromDomain
|
|
4381
|
+
mailFromDomain,
|
|
4315
4382
|
region: config2.region,
|
|
4316
4383
|
trackingConfig: emailConfig.tracking,
|
|
4317
4384
|
eventTypes: emailConfig.eventTracking?.events,
|
|
@@ -4384,6 +4451,7 @@ async function deployEmailStack(config2) {
|
|
|
4384
4451
|
}
|
|
4385
4452
|
|
|
4386
4453
|
// src/commands/email/config.ts
|
|
4454
|
+
init_events();
|
|
4387
4455
|
init_aws();
|
|
4388
4456
|
init_errors();
|
|
4389
4457
|
|
|
@@ -4418,6 +4486,7 @@ async function ensurePulumiInstalled() {
|
|
|
4418
4486
|
|
|
4419
4487
|
// src/commands/email/config.ts
|
|
4420
4488
|
async function config(options) {
|
|
4489
|
+
const startTime = Date.now();
|
|
4421
4490
|
clack3.intro(
|
|
4422
4491
|
pc4.bold(
|
|
4423
4492
|
options.preview ? "Wraps Config Preview" : "Wraps Config - Apply CLI Updates to Infrastructure"
|
|
@@ -4566,8 +4635,14 @@ ${pc4.bold("Current Configuration:")}
|
|
|
4566
4635
|
clack3.outro(
|
|
4567
4636
|
pc4.green("Preview complete. Run without --preview to update.")
|
|
4568
4637
|
);
|
|
4638
|
+
trackCommand("email:config", {
|
|
4639
|
+
success: true,
|
|
4640
|
+
preview: true,
|
|
4641
|
+
duration_ms: Date.now() - startTime
|
|
4642
|
+
});
|
|
4569
4643
|
return;
|
|
4570
4644
|
} catch (error) {
|
|
4645
|
+
trackError("PREVIEW_FAILED", "email:config", { step: "preview" });
|
|
4571
4646
|
if (error.message?.includes("stack is currently locked")) {
|
|
4572
4647
|
throw errors.stackLocked();
|
|
4573
4648
|
}
|
|
@@ -4629,9 +4704,15 @@ ${pc4.bold("Current Configuration:")}
|
|
|
4629
4704
|
}
|
|
4630
4705
|
);
|
|
4631
4706
|
} catch (error) {
|
|
4707
|
+
trackCommand("email:config", {
|
|
4708
|
+
success: false,
|
|
4709
|
+
duration_ms: Date.now() - startTime
|
|
4710
|
+
});
|
|
4632
4711
|
if (error.message?.includes("stack is currently locked")) {
|
|
4712
|
+
trackError("STACK_LOCKED", "email:config", { step: "update" });
|
|
4633
4713
|
throw errors.stackLocked();
|
|
4634
4714
|
}
|
|
4715
|
+
trackError("UPDATE_FAILED", "email:config", { step: "update" });
|
|
4635
4716
|
throw new Error(`Pulumi update failed: ${error.message}`);
|
|
4636
4717
|
}
|
|
4637
4718
|
metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4662,6 +4743,10 @@ ${pc4.green("\u2713")} ${pc4.bold("Update complete!")}
|
|
|
4662
4743
|
` ${pc4.cyan("3.")} View analytics at ${pc4.cyan("wraps console")}
|
|
4663
4744
|
`
|
|
4664
4745
|
);
|
|
4746
|
+
trackCommand("email:config", {
|
|
4747
|
+
success: true,
|
|
4748
|
+
duration_ms: Date.now() - startTime
|
|
4749
|
+
});
|
|
4665
4750
|
}
|
|
4666
4751
|
|
|
4667
4752
|
// src/commands/email/connect.ts
|
|
@@ -4669,6 +4754,7 @@ init_esm_shims();
|
|
|
4669
4754
|
import * as clack5 from "@clack/prompts";
|
|
4670
4755
|
import * as pulumi6 from "@pulumi/pulumi";
|
|
4671
4756
|
import pc6 from "picocolors";
|
|
4757
|
+
init_events();
|
|
4672
4758
|
init_presets();
|
|
4673
4759
|
init_aws();
|
|
4674
4760
|
init_errors();
|
|
@@ -4902,6 +4988,7 @@ async function scanAWSResources(region) {
|
|
|
4902
4988
|
|
|
4903
4989
|
// src/commands/email/connect.ts
|
|
4904
4990
|
async function connect(options) {
|
|
4991
|
+
const startTime = Date.now();
|
|
4905
4992
|
clack5.intro(
|
|
4906
4993
|
pc6.bold(
|
|
4907
4994
|
options.preview ? "Wraps Connect Preview" : "Wraps Connect - Link Existing Infrastructure"
|
|
@@ -5042,8 +5129,16 @@ async function connect(options) {
|
|
|
5042
5129
|
clack5.outro(
|
|
5043
5130
|
pc6.green("Preview complete. Run without --preview to connect.")
|
|
5044
5131
|
);
|
|
5132
|
+
trackServiceInit("email", true, {
|
|
5133
|
+
preset,
|
|
5134
|
+
provider,
|
|
5135
|
+
preview: true,
|
|
5136
|
+
duration_ms: Date.now() - startTime,
|
|
5137
|
+
existing_identities: selectedIdentities.length
|
|
5138
|
+
});
|
|
5045
5139
|
return;
|
|
5046
5140
|
} catch (error) {
|
|
5141
|
+
trackError("PREVIEW_FAILED", "email:connect", { step: "preview" });
|
|
5047
5142
|
if (error.message?.includes("stack is currently locked")) {
|
|
5048
5143
|
throw errors.stackLocked();
|
|
5049
5144
|
}
|
|
@@ -5103,14 +5198,21 @@ async function connect(options) {
|
|
|
5103
5198
|
}
|
|
5104
5199
|
);
|
|
5105
5200
|
} catch (error) {
|
|
5201
|
+
trackServiceInit("email", false, {
|
|
5202
|
+
preset,
|
|
5203
|
+
provider,
|
|
5204
|
+
duration_ms: Date.now() - startTime
|
|
5205
|
+
});
|
|
5106
5206
|
if (error.message?.includes("stack is currently locked")) {
|
|
5207
|
+
trackError("STACK_LOCKED", "email:connect", { step: "deploy" });
|
|
5107
5208
|
throw errors.stackLocked();
|
|
5108
5209
|
}
|
|
5210
|
+
trackError("DEPLOYMENT_FAILED", "email:connect", { step: "deploy" });
|
|
5109
5211
|
throw new Error(`Pulumi deployment failed: ${error.message}`);
|
|
5110
5212
|
}
|
|
5111
5213
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
5112
|
-
const { findHostedZone:
|
|
5113
|
-
const hostedZone = await
|
|
5214
|
+
const { findHostedZone: findHostedZone3, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
5215
|
+
const hostedZone = await findHostedZone3(outputs.domain, region);
|
|
5114
5216
|
if (hostedZone) {
|
|
5115
5217
|
try {
|
|
5116
5218
|
progress.start("Creating DNS records in Route53");
|
|
@@ -5172,105 +5274,407 @@ ${pc6.dim("Example:")}`);
|
|
|
5172
5274
|
);
|
|
5173
5275
|
console.log("");
|
|
5174
5276
|
}
|
|
5277
|
+
const duration = Date.now() - startTime;
|
|
5278
|
+
const enabledFeatures = [];
|
|
5279
|
+
if (emailConfig.tracking?.enabled) enabledFeatures.push("tracking");
|
|
5280
|
+
if (emailConfig.suppressionList?.enabled)
|
|
5281
|
+
enabledFeatures.push("suppression_list");
|
|
5282
|
+
if (emailConfig.eventTracking?.enabled)
|
|
5283
|
+
enabledFeatures.push("event_tracking");
|
|
5284
|
+
if (emailConfig.eventTracking?.dynamoDBHistory)
|
|
5285
|
+
enabledFeatures.push("dynamodb_history");
|
|
5286
|
+
trackServiceInit("email", true, {
|
|
5287
|
+
preset,
|
|
5288
|
+
provider,
|
|
5289
|
+
features: enabledFeatures,
|
|
5290
|
+
duration_ms: duration,
|
|
5291
|
+
existing_identities: selectedIdentities.length
|
|
5292
|
+
});
|
|
5293
|
+
trackServiceDeployed("email", {
|
|
5294
|
+
duration_ms: duration,
|
|
5295
|
+
features: enabledFeatures,
|
|
5296
|
+
preset
|
|
5297
|
+
});
|
|
5175
5298
|
}
|
|
5176
5299
|
|
|
5177
|
-
// src/commands/email/
|
|
5300
|
+
// src/commands/email/destroy.ts
|
|
5178
5301
|
init_esm_shims();
|
|
5302
|
+
init_events();
|
|
5179
5303
|
init_aws();
|
|
5180
|
-
import { Resolver } from "dns/promises";
|
|
5181
|
-
import { GetEmailIdentityCommand, SESv2Client as SESv2Client2 } from "@aws-sdk/client-sesv2";
|
|
5182
5304
|
import * as clack6 from "@clack/prompts";
|
|
5305
|
+
import * as pulumi7 from "@pulumi/pulumi";
|
|
5183
5306
|
import pc7 from "picocolors";
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5307
|
+
|
|
5308
|
+
// src/utils/route53.ts
|
|
5309
|
+
init_esm_shims();
|
|
5310
|
+
import {
|
|
5311
|
+
ChangeResourceRecordSetsCommand as ChangeResourceRecordSetsCommand2,
|
|
5312
|
+
ListHostedZonesByNameCommand as ListHostedZonesByNameCommand2,
|
|
5313
|
+
ListResourceRecordSetsCommand,
|
|
5314
|
+
Route53Client as Route53Client2
|
|
5315
|
+
} from "@aws-sdk/client-route-53";
|
|
5316
|
+
async function findHostedZone2(domain, region) {
|
|
5317
|
+
const client = new Route53Client2({ region });
|
|
5192
5318
|
try {
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
);
|
|
5199
|
-
return response;
|
|
5200
|
-
}
|
|
5319
|
+
const response = await client.send(
|
|
5320
|
+
new ListHostedZonesByNameCommand2({
|
|
5321
|
+
DNSName: domain,
|
|
5322
|
+
MaxItems: 1
|
|
5323
|
+
})
|
|
5201
5324
|
);
|
|
5202
|
-
|
|
5203
|
-
|
|
5325
|
+
const zone = response.HostedZones?.[0];
|
|
5326
|
+
if (zone && zone.Name === `${domain}.` && zone.Id) {
|
|
5327
|
+
return {
|
|
5328
|
+
id: zone.Id.replace("/hostedzone/", ""),
|
|
5329
|
+
name: zone.Name
|
|
5330
|
+
};
|
|
5331
|
+
}
|
|
5332
|
+
return null;
|
|
5204
5333
|
} catch (_error) {
|
|
5205
|
-
|
|
5206
|
-
clack6.log.error(`Domain ${options.domain} not found in SES`);
|
|
5207
|
-
console.log(
|
|
5208
|
-
`
|
|
5209
|
-
Run ${pc7.cyan(`wraps email init --domain ${options.domain}`)} to add this domain.
|
|
5210
|
-
`
|
|
5211
|
-
);
|
|
5212
|
-
process.exit(1);
|
|
5213
|
-
return;
|
|
5334
|
+
return null;
|
|
5214
5335
|
}
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
const
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5336
|
+
}
|
|
5337
|
+
async function deleteDNSRecords(hostedZoneId, domain, dkimTokens, region, customTrackingDomain, mailFromDomain) {
|
|
5338
|
+
const client = new Route53Client2({ region });
|
|
5339
|
+
const response = await client.send(
|
|
5340
|
+
new ListResourceRecordSetsCommand({
|
|
5341
|
+
HostedZoneId: hostedZoneId,
|
|
5342
|
+
MaxItems: 500
|
|
5343
|
+
})
|
|
5344
|
+
);
|
|
5345
|
+
const recordSets = response.ResourceRecordSets || [];
|
|
5346
|
+
const changes = [];
|
|
5347
|
+
const addDeletionIfExists = (name, type) => {
|
|
5348
|
+
const normalizedName = name.endsWith(".") ? name : `${name}.`;
|
|
5349
|
+
const record = recordSets.find(
|
|
5350
|
+
(rs) => rs.Name === normalizedName && rs.Type === type
|
|
5351
|
+
);
|
|
5352
|
+
if (record && record.ResourceRecords) {
|
|
5353
|
+
changes.push({
|
|
5354
|
+
Action: "DELETE",
|
|
5355
|
+
ResourceRecordSet: record
|
|
5235
5356
|
});
|
|
5236
5357
|
}
|
|
5358
|
+
};
|
|
5359
|
+
for (const token of dkimTokens) {
|
|
5360
|
+
addDeletionIfExists(`${token}._domainkey.${domain}`, "CNAME");
|
|
5237
5361
|
}
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
} catch (_error) {
|
|
5249
|
-
dnsResults.push({
|
|
5250
|
-
name: options.domain,
|
|
5251
|
-
type: "TXT (SPF)",
|
|
5252
|
-
status: "missing"
|
|
5253
|
-
});
|
|
5362
|
+
addDeletionIfExists(`_dmarc.${domain}`, "TXT");
|
|
5363
|
+
if (customTrackingDomain) {
|
|
5364
|
+
addDeletionIfExists(customTrackingDomain, "CNAME");
|
|
5365
|
+
}
|
|
5366
|
+
if (mailFromDomain) {
|
|
5367
|
+
addDeletionIfExists(mailFromDomain, "MX");
|
|
5368
|
+
addDeletionIfExists(mailFromDomain, "TXT");
|
|
5369
|
+
}
|
|
5370
|
+
if (changes.length === 0) {
|
|
5371
|
+
return;
|
|
5254
5372
|
}
|
|
5373
|
+
await client.send(
|
|
5374
|
+
new ChangeResourceRecordSetsCommand2({
|
|
5375
|
+
HostedZoneId: hostedZoneId,
|
|
5376
|
+
ChangeBatch: {
|
|
5377
|
+
Changes: changes
|
|
5378
|
+
}
|
|
5379
|
+
})
|
|
5380
|
+
);
|
|
5381
|
+
}
|
|
5382
|
+
|
|
5383
|
+
// src/commands/email/destroy.ts
|
|
5384
|
+
async function getDkimTokensFromSES(domain, region) {
|
|
5255
5385
|
try {
|
|
5256
|
-
const
|
|
5257
|
-
const
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
records: dmarcRecord ? [dmarcRecord] : void 0
|
|
5263
|
-
});
|
|
5386
|
+
const { SESv2Client: SESv2Client5, GetEmailIdentityCommand: GetEmailIdentityCommand4 } = await import("@aws-sdk/client-sesv2");
|
|
5387
|
+
const ses = new SESv2Client5({ region });
|
|
5388
|
+
const response = await ses.send(
|
|
5389
|
+
new GetEmailIdentityCommand4({ EmailIdentity: domain })
|
|
5390
|
+
);
|
|
5391
|
+
return response.DkimAttributes?.Tokens || [];
|
|
5264
5392
|
} catch (_error) {
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5393
|
+
return [];
|
|
5394
|
+
}
|
|
5395
|
+
}
|
|
5396
|
+
async function emailDestroy(options) {
|
|
5397
|
+
const startTime = Date.now();
|
|
5398
|
+
clack6.intro(
|
|
5399
|
+
pc7.bold(
|
|
5400
|
+
options.preview ? "Email Infrastructure Destruction Preview" : "Email Infrastructure Teardown"
|
|
5401
|
+
)
|
|
5402
|
+
);
|
|
5403
|
+
const progress = new DeploymentProgress();
|
|
5404
|
+
const identity = await progress.execute(
|
|
5405
|
+
"Validating AWS credentials",
|
|
5406
|
+
async () => validateAWSCredentials()
|
|
5407
|
+
);
|
|
5408
|
+
const region = await getAWSRegion();
|
|
5409
|
+
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
5410
|
+
const emailService = metadata?.services?.email;
|
|
5411
|
+
const emailConfig = emailService?.config;
|
|
5412
|
+
const domain = emailConfig?.domain;
|
|
5413
|
+
const storedStackName = emailService?.pulumiStackName;
|
|
5414
|
+
if (!(options.force || options.preview)) {
|
|
5415
|
+
const confirmed = await clack6.confirm({
|
|
5416
|
+
message: pc7.red(
|
|
5417
|
+
"Are you sure you want to destroy all email infrastructure?"
|
|
5418
|
+
),
|
|
5419
|
+
initialValue: false
|
|
5269
5420
|
});
|
|
5421
|
+
if (clack6.isCancel(confirmed) || !confirmed) {
|
|
5422
|
+
clack6.cancel("Destruction cancelled.");
|
|
5423
|
+
process.exit(0);
|
|
5424
|
+
}
|
|
5270
5425
|
}
|
|
5271
|
-
|
|
5426
|
+
let shouldCleanDNS = false;
|
|
5427
|
+
let hostedZone = null;
|
|
5428
|
+
let dkimTokens = [];
|
|
5429
|
+
if (domain && !options.preview) {
|
|
5430
|
+
hostedZone = await findHostedZone2(domain, region);
|
|
5431
|
+
if (hostedZone) {
|
|
5432
|
+
dkimTokens = await getDkimTokensFromSES(domain, region);
|
|
5433
|
+
if (!options.force) {
|
|
5434
|
+
const cleanDNS = await clack6.confirm({
|
|
5435
|
+
message: `Found Route53 hosted zone for ${pc7.cyan(domain)}. Delete DNS records (DKIM, DMARC, MAIL FROM)?`,
|
|
5436
|
+
initialValue: true
|
|
5437
|
+
});
|
|
5438
|
+
if (clack6.isCancel(cleanDNS)) {
|
|
5439
|
+
clack6.cancel("Destruction cancelled.");
|
|
5440
|
+
process.exit(0);
|
|
5441
|
+
}
|
|
5442
|
+
shouldCleanDNS = cleanDNS;
|
|
5443
|
+
} else {
|
|
5444
|
+
shouldCleanDNS = true;
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
if (options.preview) {
|
|
5272
5449
|
try {
|
|
5273
|
-
const
|
|
5450
|
+
const previewResult = await progress.execute(
|
|
5451
|
+
"Generating destruction preview",
|
|
5452
|
+
async () => {
|
|
5453
|
+
await ensurePulumiWorkDir();
|
|
5454
|
+
const stackName = storedStackName || `wraps-email-${identity.accountId}-${region}`;
|
|
5455
|
+
let stack;
|
|
5456
|
+
try {
|
|
5457
|
+
stack = await pulumi7.automation.LocalWorkspace.selectStack({
|
|
5458
|
+
stackName,
|
|
5459
|
+
workDir: getPulumiWorkDir()
|
|
5460
|
+
});
|
|
5461
|
+
} catch (_error) {
|
|
5462
|
+
throw new Error("No email infrastructure found to preview");
|
|
5463
|
+
}
|
|
5464
|
+
const result = await stack.preview({ diff: true });
|
|
5465
|
+
return result;
|
|
5466
|
+
}
|
|
5467
|
+
);
|
|
5468
|
+
displayPreview({
|
|
5469
|
+
changeSummary: previewResult.changeSummary,
|
|
5470
|
+
costEstimate: "Monthly cost after destruction: $0.00",
|
|
5471
|
+
commandName: "wraps email destroy"
|
|
5472
|
+
});
|
|
5473
|
+
if (domain) {
|
|
5474
|
+
const previewHostedZone = await findHostedZone2(domain, region);
|
|
5475
|
+
if (previewHostedZone) {
|
|
5476
|
+
clack6.log.info(
|
|
5477
|
+
`DNS records in Route53 for ${pc7.cyan(domain)} will also be deleted`
|
|
5478
|
+
);
|
|
5479
|
+
}
|
|
5480
|
+
}
|
|
5481
|
+
clack6.outro(
|
|
5482
|
+
pc7.green("Preview complete. Run without --preview to destroy.")
|
|
5483
|
+
);
|
|
5484
|
+
trackServiceRemoved("email", {
|
|
5485
|
+
preview: true,
|
|
5486
|
+
duration_ms: Date.now() - startTime
|
|
5487
|
+
});
|
|
5488
|
+
return;
|
|
5489
|
+
} catch (error) {
|
|
5490
|
+
progress.stop();
|
|
5491
|
+
if (error.message.includes("No email infrastructure found")) {
|
|
5492
|
+
clack6.log.warn("No email infrastructure found to preview");
|
|
5493
|
+
process.exit(0);
|
|
5494
|
+
}
|
|
5495
|
+
trackError("PREVIEW_FAILED", "email destroy", { step: "preview" });
|
|
5496
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
5497
|
+
}
|
|
5498
|
+
}
|
|
5499
|
+
if (shouldCleanDNS && hostedZone && domain && dkimTokens.length > 0) {
|
|
5500
|
+
try {
|
|
5501
|
+
await progress.execute(
|
|
5502
|
+
`Deleting DNS records for ${domain}`,
|
|
5503
|
+
async () => {
|
|
5504
|
+
await deleteDNSRecords(
|
|
5505
|
+
hostedZone.id,
|
|
5506
|
+
domain,
|
|
5507
|
+
dkimTokens,
|
|
5508
|
+
region,
|
|
5509
|
+
emailConfig?.tracking?.customRedirectDomain,
|
|
5510
|
+
emailConfig?.mailFromDomain
|
|
5511
|
+
);
|
|
5512
|
+
}
|
|
5513
|
+
);
|
|
5514
|
+
} catch (error) {
|
|
5515
|
+
clack6.log.warn(`Could not delete DNS records: ${error.message}`);
|
|
5516
|
+
clack6.log.info("You may need to delete them manually from Route53");
|
|
5517
|
+
}
|
|
5518
|
+
}
|
|
5519
|
+
try {
|
|
5520
|
+
await progress.execute(
|
|
5521
|
+
"Destroying email infrastructure (this may take 2-3 minutes)",
|
|
5522
|
+
async () => {
|
|
5523
|
+
await ensurePulumiWorkDir();
|
|
5524
|
+
const stackName = storedStackName || `wraps-email-${identity.accountId}-${region}`;
|
|
5525
|
+
let stack;
|
|
5526
|
+
try {
|
|
5527
|
+
stack = await pulumi7.automation.LocalWorkspace.selectStack({
|
|
5528
|
+
stackName,
|
|
5529
|
+
workDir: getPulumiWorkDir()
|
|
5530
|
+
});
|
|
5531
|
+
} catch (_error) {
|
|
5532
|
+
throw new Error("No email infrastructure found to destroy");
|
|
5533
|
+
}
|
|
5534
|
+
await stack.destroy({ onOutput: () => {
|
|
5535
|
+
} });
|
|
5536
|
+
await stack.workspace.removeStack(stackName);
|
|
5537
|
+
}
|
|
5538
|
+
);
|
|
5539
|
+
} catch (error) {
|
|
5540
|
+
progress.stop();
|
|
5541
|
+
if (error.message.includes("No email infrastructure found")) {
|
|
5542
|
+
clack6.log.warn("No email infrastructure found");
|
|
5543
|
+
await deleteConnectionMetadata(identity.accountId, region);
|
|
5544
|
+
process.exit(0);
|
|
5545
|
+
}
|
|
5546
|
+
trackError("DESTROY_FAILED", "email destroy", { step: "destroy" });
|
|
5547
|
+
clack6.log.error("Email infrastructure destruction failed");
|
|
5548
|
+
throw error;
|
|
5549
|
+
}
|
|
5550
|
+
await deleteConnectionMetadata(identity.accountId, region);
|
|
5551
|
+
progress.stop();
|
|
5552
|
+
const deletedItems = ["AWS infrastructure"];
|
|
5553
|
+
if (shouldCleanDNS && hostedZone) {
|
|
5554
|
+
deletedItems.push("Route53 DNS records");
|
|
5555
|
+
}
|
|
5556
|
+
clack6.outro(pc7.green(`Email infrastructure has been removed`));
|
|
5557
|
+
if (domain) {
|
|
5558
|
+
console.log(`
|
|
5559
|
+
${pc7.bold("Cleaned up:")}`);
|
|
5560
|
+
for (const item of deletedItems) {
|
|
5561
|
+
console.log(` ${pc7.green("\u2713")} ${item}`);
|
|
5562
|
+
}
|
|
5563
|
+
console.log(
|
|
5564
|
+
`
|
|
5565
|
+
${pc7.dim("Note: SPF record was not deleted. Remove 'include:amazonses.com' manually if needed.")}`
|
|
5566
|
+
);
|
|
5567
|
+
}
|
|
5568
|
+
console.log(
|
|
5569
|
+
`
|
|
5570
|
+
Run ${pc7.cyan("wraps email init")} to deploy infrastructure again.
|
|
5571
|
+
`
|
|
5572
|
+
);
|
|
5573
|
+
trackServiceRemoved("email", {
|
|
5574
|
+
reason: "user_initiated",
|
|
5575
|
+
duration_ms: Date.now() - startTime,
|
|
5576
|
+
dns_cleaned: shouldCleanDNS
|
|
5577
|
+
});
|
|
5578
|
+
}
|
|
5579
|
+
|
|
5580
|
+
// src/commands/email/domains.ts
|
|
5581
|
+
init_esm_shims();
|
|
5582
|
+
init_events();
|
|
5583
|
+
init_aws();
|
|
5584
|
+
import { Resolver } from "dns/promises";
|
|
5585
|
+
import { GetEmailIdentityCommand, SESv2Client as SESv2Client2 } from "@aws-sdk/client-sesv2";
|
|
5586
|
+
import * as clack7 from "@clack/prompts";
|
|
5587
|
+
import pc8 from "picocolors";
|
|
5588
|
+
async function verifyDomain(options) {
|
|
5589
|
+
clack7.intro(pc8.bold(`Verifying ${options.domain}`));
|
|
5590
|
+
const progress = new DeploymentProgress();
|
|
5591
|
+
const region = await getAWSRegion();
|
|
5592
|
+
const sesClient = new SESv2Client2({ region });
|
|
5593
|
+
let identity;
|
|
5594
|
+
let dkimTokens = [];
|
|
5595
|
+
let mailFromDomain;
|
|
5596
|
+
try {
|
|
5597
|
+
identity = await progress.execute(
|
|
5598
|
+
"Checking SES verification status",
|
|
5599
|
+
async () => {
|
|
5600
|
+
const response = await sesClient.send(
|
|
5601
|
+
new GetEmailIdentityCommand({ EmailIdentity: options.domain })
|
|
5602
|
+
);
|
|
5603
|
+
return response;
|
|
5604
|
+
}
|
|
5605
|
+
);
|
|
5606
|
+
dkimTokens = identity.DkimAttributes?.Tokens || [];
|
|
5607
|
+
mailFromDomain = identity.MailFromAttributes?.MailFromDomain;
|
|
5608
|
+
} catch (_error) {
|
|
5609
|
+
progress.stop();
|
|
5610
|
+
clack7.log.error(`Domain ${options.domain} not found in SES`);
|
|
5611
|
+
console.log(
|
|
5612
|
+
`
|
|
5613
|
+
Run ${pc8.cyan(`wraps email init --domain ${options.domain}`)} to add this domain.
|
|
5614
|
+
`
|
|
5615
|
+
);
|
|
5616
|
+
process.exit(1);
|
|
5617
|
+
return;
|
|
5618
|
+
}
|
|
5619
|
+
const resolver = new Resolver();
|
|
5620
|
+
resolver.setServers(["8.8.8.8", "1.1.1.1"]);
|
|
5621
|
+
const dnsResults = [];
|
|
5622
|
+
for (const token of dkimTokens) {
|
|
5623
|
+
const dkimRecord = `${token}._domainkey.${options.domain}`;
|
|
5624
|
+
try {
|
|
5625
|
+
const records = await resolver.resolveCname(dkimRecord);
|
|
5626
|
+
const expected = `${token}.dkim.amazonses.com`;
|
|
5627
|
+
const found = records.some((r) => r === expected || r === `${expected}.`);
|
|
5628
|
+
dnsResults.push({
|
|
5629
|
+
name: dkimRecord,
|
|
5630
|
+
type: "CNAME",
|
|
5631
|
+
status: found ? "verified" : "incorrect",
|
|
5632
|
+
records
|
|
5633
|
+
});
|
|
5634
|
+
} catch (_error) {
|
|
5635
|
+
dnsResults.push({
|
|
5636
|
+
name: dkimRecord,
|
|
5637
|
+
type: "CNAME",
|
|
5638
|
+
status: "missing"
|
|
5639
|
+
});
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5642
|
+
try {
|
|
5643
|
+
const records = await resolver.resolveTxt(options.domain);
|
|
5644
|
+
const spfRecord = records.flat().find((r) => r.startsWith("v=spf1"));
|
|
5645
|
+
const hasAmazonSES = spfRecord?.includes("include:amazonses.com");
|
|
5646
|
+
dnsResults.push({
|
|
5647
|
+
name: options.domain,
|
|
5648
|
+
type: "TXT (SPF)",
|
|
5649
|
+
status: hasAmazonSES ? "verified" : spfRecord ? "incorrect" : "missing",
|
|
5650
|
+
records: spfRecord ? [spfRecord] : void 0
|
|
5651
|
+
});
|
|
5652
|
+
} catch (_error) {
|
|
5653
|
+
dnsResults.push({
|
|
5654
|
+
name: options.domain,
|
|
5655
|
+
type: "TXT (SPF)",
|
|
5656
|
+
status: "missing"
|
|
5657
|
+
});
|
|
5658
|
+
}
|
|
5659
|
+
try {
|
|
5660
|
+
const records = await resolver.resolveTxt(`_dmarc.${options.domain}`);
|
|
5661
|
+
const dmarcRecord = records.flat().find((r) => r.startsWith("v=DMARC1"));
|
|
5662
|
+
dnsResults.push({
|
|
5663
|
+
name: `_dmarc.${options.domain}`,
|
|
5664
|
+
type: "TXT (DMARC)",
|
|
5665
|
+
status: dmarcRecord ? "verified" : "missing",
|
|
5666
|
+
records: dmarcRecord ? [dmarcRecord] : void 0
|
|
5667
|
+
});
|
|
5668
|
+
} catch (_error) {
|
|
5669
|
+
dnsResults.push({
|
|
5670
|
+
name: `_dmarc.${options.domain}`,
|
|
5671
|
+
type: "TXT (DMARC)",
|
|
5672
|
+
status: "missing"
|
|
5673
|
+
});
|
|
5674
|
+
}
|
|
5675
|
+
if (mailFromDomain) {
|
|
5676
|
+
try {
|
|
5677
|
+
const mxRecords = await resolver.resolveMx(mailFromDomain);
|
|
5274
5678
|
const expectedMx = `feedback-smtp.${region}.amazonses.com`;
|
|
5275
5679
|
const hasMx = mxRecords.some(
|
|
5276
5680
|
(r) => r.exchange === expectedMx || r.exchange === `${expectedMx}.`
|
|
@@ -5311,63 +5715,69 @@ Run ${pc7.cyan(`wraps email init --domain ${options.domain}`)} to add this domai
|
|
|
5311
5715
|
const dkimStatus = identity.DkimAttributes?.Status || "PENDING";
|
|
5312
5716
|
const mailFromStatus = identity.MailFromAttributes?.MailFromDomainStatus || "NOT_CONFIGURED";
|
|
5313
5717
|
const statusLines = [
|
|
5314
|
-
`${
|
|
5315
|
-
`${
|
|
5316
|
-
`${
|
|
5718
|
+
`${pc8.bold("Domain:")} ${options.domain}`,
|
|
5719
|
+
`${pc8.bold("Verification Status:")} ${verificationStatus === "verified" ? pc8.green("\u2713 Verified") : pc8.yellow("\u23F1 Pending")}`,
|
|
5720
|
+
`${pc8.bold("DKIM Status:")} ${dkimStatus === "SUCCESS" ? pc8.green("\u2713 Success") : pc8.yellow(`\u23F1 ${dkimStatus}`)}`
|
|
5317
5721
|
];
|
|
5318
5722
|
if (mailFromDomain) {
|
|
5319
5723
|
statusLines.push(
|
|
5320
|
-
`${
|
|
5321
|
-
`${
|
|
5724
|
+
`${pc8.bold("MAIL FROM Domain:")} ${mailFromDomain}`,
|
|
5725
|
+
`${pc8.bold("MAIL FROM Status:")} ${mailFromStatus === "SUCCESS" ? pc8.green("\u2713 Success") : mailFromStatus === "NOT_CONFIGURED" ? pc8.yellow("\u23F1 Not Configured") : pc8.yellow(`\u23F1 ${mailFromStatus}`)}`
|
|
5322
5726
|
);
|
|
5323
5727
|
}
|
|
5324
|
-
|
|
5728
|
+
clack7.note(statusLines.join("\n"), "SES Status");
|
|
5325
5729
|
const dnsLines = dnsResults.map((record) => {
|
|
5326
5730
|
let statusIcon;
|
|
5327
5731
|
let statusColor;
|
|
5328
5732
|
if (record.status === "verified") {
|
|
5329
5733
|
statusIcon = "\u2713";
|
|
5330
|
-
statusColor =
|
|
5734
|
+
statusColor = pc8.green;
|
|
5331
5735
|
} else if (record.status === "incorrect") {
|
|
5332
5736
|
statusIcon = "\u2717";
|
|
5333
|
-
statusColor =
|
|
5737
|
+
statusColor = pc8.red;
|
|
5334
5738
|
} else {
|
|
5335
5739
|
statusIcon = "\u2717";
|
|
5336
|
-
statusColor =
|
|
5740
|
+
statusColor = pc8.red;
|
|
5337
5741
|
}
|
|
5338
5742
|
const recordInfo = record.records ? ` \u2192 ${record.records.join(", ")}` : "";
|
|
5339
5743
|
return ` ${statusColor(statusIcon)} ${record.name} (${record.type}) ${statusColor(
|
|
5340
5744
|
record.status
|
|
5341
5745
|
)}${recordInfo}`;
|
|
5342
5746
|
});
|
|
5343
|
-
|
|
5747
|
+
clack7.note(dnsLines.join("\n"), "DNS Records");
|
|
5344
5748
|
const allVerified = dnsResults.every((r) => r.status === "verified");
|
|
5345
5749
|
const someIncorrect = dnsResults.some((r) => r.status === "incorrect");
|
|
5346
5750
|
if (verificationStatus === "verified" && allVerified) {
|
|
5347
|
-
|
|
5348
|
-
|
|
5751
|
+
clack7.outro(
|
|
5752
|
+
pc8.green("\u2713 Domain is fully verified and ready to send emails!")
|
|
5349
5753
|
);
|
|
5754
|
+
trackFeature("domain_verified", { dns_auto_detected: true });
|
|
5350
5755
|
} else if (someIncorrect) {
|
|
5351
|
-
|
|
5352
|
-
|
|
5756
|
+
clack7.outro(
|
|
5757
|
+
pc8.red("\u2717 Some DNS records are incorrect. Please update them.")
|
|
5353
5758
|
);
|
|
5354
5759
|
console.log(
|
|
5355
5760
|
`
|
|
5356
|
-
Run ${
|
|
5761
|
+
Run ${pc8.cyan("wraps email status")} to see the correct DNS records.
|
|
5357
5762
|
`
|
|
5358
5763
|
);
|
|
5359
5764
|
} else {
|
|
5360
|
-
|
|
5361
|
-
|
|
5765
|
+
clack7.outro(
|
|
5766
|
+
pc8.yellow("\u23F1 Waiting for DNS propagation and SES verification")
|
|
5362
5767
|
);
|
|
5363
5768
|
console.log("\nDNS records can take up to 48 hours to propagate.");
|
|
5364
5769
|
console.log(
|
|
5365
5770
|
"SES verification usually completes within 72 hours after DNS propagation.\n"
|
|
5366
5771
|
);
|
|
5367
5772
|
}
|
|
5773
|
+
trackCommand("email:domains:verify", {
|
|
5774
|
+
success: true,
|
|
5775
|
+
verified: verificationStatus === "verified" && allVerified,
|
|
5776
|
+
dkim_status: dkimStatus
|
|
5777
|
+
});
|
|
5368
5778
|
}
|
|
5369
5779
|
async function addDomain(options) {
|
|
5370
|
-
|
|
5780
|
+
clack7.intro(pc8.bold(`Adding domain ${options.domain} to SES`));
|
|
5371
5781
|
const progress = new DeploymentProgress();
|
|
5372
5782
|
const region = await getAWSRegion();
|
|
5373
5783
|
const sesClient = new SESv2Client2({ region });
|
|
@@ -5377,10 +5787,10 @@ async function addDomain(options) {
|
|
|
5377
5787
|
new GetEmailIdentityCommand({ EmailIdentity: options.domain })
|
|
5378
5788
|
);
|
|
5379
5789
|
progress.stop();
|
|
5380
|
-
|
|
5790
|
+
clack7.log.warn(`Domain ${options.domain} already exists in SES`);
|
|
5381
5791
|
console.log(
|
|
5382
5792
|
`
|
|
5383
|
-
Run ${
|
|
5793
|
+
Run ${pc8.cyan(`wraps email domains verify --domain ${options.domain}`)} to check verification status.
|
|
5384
5794
|
`
|
|
5385
5795
|
);
|
|
5386
5796
|
return;
|
|
@@ -5405,30 +5815,37 @@ Run ${pc7.cyan(`wraps email domains verify --domain ${options.domain}`)} to chec
|
|
|
5405
5815
|
);
|
|
5406
5816
|
const dkimTokens = identity.DkimAttributes?.Tokens || [];
|
|
5407
5817
|
progress.stop();
|
|
5408
|
-
|
|
5818
|
+
clack7.outro(pc8.green(`\u2713 Domain ${options.domain} added successfully!`));
|
|
5409
5819
|
console.log(`
|
|
5410
|
-
${
|
|
5820
|
+
${pc8.bold("Next steps:")}
|
|
5411
5821
|
`);
|
|
5412
5822
|
console.log("1. Add the following DKIM records to your DNS:\n");
|
|
5413
5823
|
for (const token of dkimTokens) {
|
|
5414
|
-
console.log(` ${
|
|
5824
|
+
console.log(` ${pc8.cyan(`${token}._domainkey.${options.domain}`)}`);
|
|
5415
5825
|
console.log(
|
|
5416
|
-
` ${
|
|
5826
|
+
` ${pc8.dim("Type:")} CNAME ${pc8.dim("Value:")} ${token}.dkim.amazonses.com
|
|
5417
5827
|
`
|
|
5418
5828
|
);
|
|
5419
5829
|
}
|
|
5420
5830
|
console.log(
|
|
5421
|
-
`2. Verify DNS propagation: ${
|
|
5831
|
+
`2. Verify DNS propagation: ${pc8.cyan(`wraps email domains verify --domain ${options.domain}`)}`
|
|
5422
5832
|
);
|
|
5423
|
-
console.log(`3. Check status: ${
|
|
5833
|
+
console.log(`3. Check status: ${pc8.cyan("wraps email status")}
|
|
5424
5834
|
`);
|
|
5835
|
+
trackCommand("email:domains:add", {
|
|
5836
|
+
success: true
|
|
5837
|
+
});
|
|
5838
|
+
trackFeature("domain_added", {});
|
|
5425
5839
|
} catch (error) {
|
|
5426
5840
|
progress.stop();
|
|
5841
|
+
trackCommand("email:domains:add", {
|
|
5842
|
+
success: false
|
|
5843
|
+
});
|
|
5427
5844
|
throw error;
|
|
5428
5845
|
}
|
|
5429
5846
|
}
|
|
5430
5847
|
async function listDomains() {
|
|
5431
|
-
|
|
5848
|
+
clack7.intro(pc8.bold("SES Email Domains"));
|
|
5432
5849
|
const progress = new DeploymentProgress();
|
|
5433
5850
|
const region = await getAWSRegion();
|
|
5434
5851
|
const sesClient = new SESv2Client2({ region });
|
|
@@ -5448,10 +5865,10 @@ async function listDomains() {
|
|
|
5448
5865
|
);
|
|
5449
5866
|
progress.stop();
|
|
5450
5867
|
if (domains.length === 0) {
|
|
5451
|
-
|
|
5868
|
+
clack7.outro("No domains found in SES");
|
|
5452
5869
|
console.log(
|
|
5453
5870
|
`
|
|
5454
|
-
Run ${
|
|
5871
|
+
Run ${pc8.cyan("wraps email domains add <domain>")} to add a domain.
|
|
5455
5872
|
`
|
|
5456
5873
|
);
|
|
5457
5874
|
return;
|
|
@@ -5479,26 +5896,31 @@ Run ${pc7.cyan("wraps email domains add <domain>")} to add a domain.
|
|
|
5479
5896
|
})
|
|
5480
5897
|
);
|
|
5481
5898
|
const domainLines = domainDetails.map((domain) => {
|
|
5482
|
-
const statusIcon = domain.verified ?
|
|
5483
|
-
const dkimIcon = domain.dkimStatus === "SUCCESS" ?
|
|
5484
|
-
return ` ${statusIcon} ${
|
|
5899
|
+
const statusIcon = domain.verified ? pc8.green("\u2713") : pc8.yellow("\u23F1");
|
|
5900
|
+
const dkimIcon = domain.dkimStatus === "SUCCESS" ? pc8.green("\u2713") : pc8.yellow("\u23F1");
|
|
5901
|
+
return ` ${statusIcon} ${pc8.bold(domain.name)} DKIM: ${dkimIcon} ${domain.dkimStatus}`;
|
|
5485
5902
|
});
|
|
5486
|
-
|
|
5903
|
+
clack7.note(
|
|
5487
5904
|
domainLines.join("\n"),
|
|
5488
5905
|
`${domains.length} domain(s) in ${region}`
|
|
5489
5906
|
);
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
`Run ${
|
|
5907
|
+
clack7.outro(
|
|
5908
|
+
pc8.dim(
|
|
5909
|
+
`Run ${pc8.cyan("wraps email domains verify --domain <domain>")} for details`
|
|
5493
5910
|
)
|
|
5494
5911
|
);
|
|
5912
|
+
trackCommand("email:domains:list", {
|
|
5913
|
+
success: true,
|
|
5914
|
+
domain_count: domains.length
|
|
5915
|
+
});
|
|
5495
5916
|
} catch (error) {
|
|
5496
5917
|
progress.stop();
|
|
5918
|
+
trackCommand("email:domains:list", { success: false });
|
|
5497
5919
|
throw error;
|
|
5498
5920
|
}
|
|
5499
5921
|
}
|
|
5500
5922
|
async function getDkim(options) {
|
|
5501
|
-
|
|
5923
|
+
clack7.intro(pc8.bold(`DKIM Tokens for ${options.domain}`));
|
|
5502
5924
|
const progress = new DeploymentProgress();
|
|
5503
5925
|
const region = await getAWSRegion();
|
|
5504
5926
|
const sesClient = new SESv2Client2({ region });
|
|
@@ -5516,33 +5938,38 @@ async function getDkim(options) {
|
|
|
5516
5938
|
const dkimStatus = identity.DkimAttributes?.Status || "PENDING";
|
|
5517
5939
|
progress.stop();
|
|
5518
5940
|
if (dkimTokens.length === 0) {
|
|
5519
|
-
|
|
5941
|
+
clack7.outro(pc8.yellow("No DKIM tokens found for this domain"));
|
|
5520
5942
|
return;
|
|
5521
5943
|
}
|
|
5522
|
-
const statusLine = `${
|
|
5523
|
-
|
|
5944
|
+
const statusLine = `${pc8.bold("DKIM Status:")} ${dkimStatus === "SUCCESS" ? pc8.green("\u2713 Verified") : pc8.yellow(`\u23F1 ${dkimStatus}`)}`;
|
|
5945
|
+
clack7.note(statusLine, "Status");
|
|
5524
5946
|
console.log(`
|
|
5525
|
-
${
|
|
5947
|
+
${pc8.bold("DNS Records to add:")}
|
|
5526
5948
|
`);
|
|
5527
5949
|
for (const token of dkimTokens) {
|
|
5528
|
-
console.log(`${
|
|
5529
|
-
console.log(` ${
|
|
5530
|
-
console.log(` ${
|
|
5950
|
+
console.log(`${pc8.cyan(`${token}._domainkey.${options.domain}`)}`);
|
|
5951
|
+
console.log(` ${pc8.dim("Type:")} CNAME`);
|
|
5952
|
+
console.log(` ${pc8.dim("Value:")} ${token}.dkim.amazonses.com
|
|
5531
5953
|
`);
|
|
5532
5954
|
}
|
|
5533
5955
|
if (dkimStatus !== "SUCCESS") {
|
|
5534
5956
|
console.log(
|
|
5535
|
-
`${
|
|
5957
|
+
`${pc8.dim("After adding these records, run:")} ${pc8.cyan(`wraps email domains verify --domain ${options.domain}`)}
|
|
5536
5958
|
`
|
|
5537
5959
|
);
|
|
5538
5960
|
}
|
|
5961
|
+
trackCommand("email:domains:get-dkim", {
|
|
5962
|
+
success: true,
|
|
5963
|
+
dkim_status: dkimStatus
|
|
5964
|
+
});
|
|
5539
5965
|
} catch (error) {
|
|
5540
5966
|
progress.stop();
|
|
5967
|
+
trackCommand("email:domains:get-dkim", { success: false });
|
|
5541
5968
|
if (error.name === "NotFoundException") {
|
|
5542
|
-
|
|
5969
|
+
clack7.log.error(`Domain ${options.domain} not found in SES`);
|
|
5543
5970
|
console.log(
|
|
5544
5971
|
`
|
|
5545
|
-
Run ${
|
|
5972
|
+
Run ${pc8.cyan(`wraps email domains add ${options.domain}`)} to add this domain.
|
|
5546
5973
|
`
|
|
5547
5974
|
);
|
|
5548
5975
|
process.exit(1);
|
|
@@ -5552,7 +5979,7 @@ Run ${pc7.cyan(`wraps email domains add ${options.domain}`)} to add this domain.
|
|
|
5552
5979
|
}
|
|
5553
5980
|
}
|
|
5554
5981
|
async function removeDomain(options) {
|
|
5555
|
-
|
|
5982
|
+
clack7.intro(pc8.bold(`Remove domain ${options.domain} from SES`));
|
|
5556
5983
|
const progress = new DeploymentProgress();
|
|
5557
5984
|
const region = await getAWSRegion();
|
|
5558
5985
|
const sesClient = new SESv2Client2({ region });
|
|
@@ -5564,12 +5991,12 @@ async function removeDomain(options) {
|
|
|
5564
5991
|
});
|
|
5565
5992
|
progress.stop();
|
|
5566
5993
|
if (!options.force) {
|
|
5567
|
-
const shouldContinue = await
|
|
5568
|
-
message: `Are you sure you want to remove ${
|
|
5994
|
+
const shouldContinue = await clack7.confirm({
|
|
5995
|
+
message: `Are you sure you want to remove ${pc8.red(options.domain)} from SES?`,
|
|
5569
5996
|
initialValue: false
|
|
5570
5997
|
});
|
|
5571
|
-
if (
|
|
5572
|
-
|
|
5998
|
+
if (clack7.isCancel(shouldContinue) || !shouldContinue) {
|
|
5999
|
+
clack7.cancel("Operation cancelled");
|
|
5573
6000
|
process.exit(0);
|
|
5574
6001
|
}
|
|
5575
6002
|
}
|
|
@@ -5582,11 +6009,16 @@ async function removeDomain(options) {
|
|
|
5582
6009
|
);
|
|
5583
6010
|
});
|
|
5584
6011
|
progress.stop();
|
|
5585
|
-
|
|
6012
|
+
clack7.outro(pc8.green(`\u2713 Domain ${options.domain} removed successfully`));
|
|
6013
|
+
trackCommand("email:domains:remove", {
|
|
6014
|
+
success: true
|
|
6015
|
+
});
|
|
6016
|
+
trackFeature("domain_removed", {});
|
|
5586
6017
|
} catch (error) {
|
|
5587
6018
|
progress.stop();
|
|
6019
|
+
trackCommand("email:domains:remove", { success: false });
|
|
5588
6020
|
if (error.name === "NotFoundException") {
|
|
5589
|
-
|
|
6021
|
+
clack7.log.error(`Domain ${options.domain} not found in SES`);
|
|
5590
6022
|
process.exit(1);
|
|
5591
6023
|
return;
|
|
5592
6024
|
}
|
|
@@ -5596,17 +6028,19 @@ async function removeDomain(options) {
|
|
|
5596
6028
|
|
|
5597
6029
|
// src/commands/email/init.ts
|
|
5598
6030
|
init_esm_shims();
|
|
5599
|
-
import * as
|
|
5600
|
-
import * as
|
|
5601
|
-
import
|
|
6031
|
+
import * as clack8 from "@clack/prompts";
|
|
6032
|
+
import * as pulumi8 from "@pulumi/pulumi";
|
|
6033
|
+
import pc9 from "picocolors";
|
|
6034
|
+
init_events();
|
|
5602
6035
|
init_costs();
|
|
5603
6036
|
init_presets();
|
|
5604
6037
|
init_aws();
|
|
5605
6038
|
init_errors();
|
|
5606
6039
|
init_prompts();
|
|
5607
6040
|
async function init(options) {
|
|
5608
|
-
|
|
5609
|
-
|
|
6041
|
+
const startTime = Date.now();
|
|
6042
|
+
clack8.intro(
|
|
6043
|
+
pc9.bold(
|
|
5610
6044
|
options.preview ? "Wraps Email Infrastructure Preview" : "Wraps Email Infrastructure Setup"
|
|
5611
6045
|
)
|
|
5612
6046
|
);
|
|
@@ -5622,7 +6056,7 @@ async function init(options) {
|
|
|
5622
6056
|
"Validating AWS credentials",
|
|
5623
6057
|
async () => validateAWSCredentials()
|
|
5624
6058
|
);
|
|
5625
|
-
progress.info(`Connected to AWS account: ${
|
|
6059
|
+
progress.info(`Connected to AWS account: ${pc9.cyan(identity.accountId)}`);
|
|
5626
6060
|
let provider = options.provider;
|
|
5627
6061
|
if (!provider) {
|
|
5628
6062
|
provider = await promptProvider();
|
|
@@ -5645,12 +6079,12 @@ async function init(options) {
|
|
|
5645
6079
|
region
|
|
5646
6080
|
);
|
|
5647
6081
|
if (existingConnection) {
|
|
5648
|
-
|
|
5649
|
-
`Connection already exists for account ${
|
|
6082
|
+
clack8.log.warn(
|
|
6083
|
+
`Connection already exists for account ${pc9.cyan(identity.accountId)} in region ${pc9.cyan(region)}`
|
|
5650
6084
|
);
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
6085
|
+
clack8.log.info(`Created: ${existingConnection.timestamp}`);
|
|
6086
|
+
clack8.log.info(`Use ${pc9.cyan("wraps status")} to view current setup`);
|
|
6087
|
+
clack8.log.info(`Use ${pc9.cyan("wraps upgrade")} to add more features`);
|
|
5654
6088
|
process.exit(0);
|
|
5655
6089
|
}
|
|
5656
6090
|
let preset = options.preset;
|
|
@@ -5671,15 +6105,15 @@ async function init(options) {
|
|
|
5671
6105
|
}
|
|
5672
6106
|
const estimatedVolume = await promptEstimatedVolume();
|
|
5673
6107
|
progress.info(`
|
|
5674
|
-
${
|
|
6108
|
+
${pc9.bold("Cost Estimate:")}`);
|
|
5675
6109
|
const costSummary = getCostSummary(emailConfig, estimatedVolume);
|
|
5676
|
-
|
|
6110
|
+
clack8.log.info(costSummary);
|
|
5677
6111
|
const warnings = validateConfig(emailConfig);
|
|
5678
6112
|
if (warnings.length > 0) {
|
|
5679
6113
|
progress.info(`
|
|
5680
|
-
${
|
|
6114
|
+
${pc9.yellow(pc9.bold("Configuration Warnings:"))}`);
|
|
5681
6115
|
for (const warning of warnings) {
|
|
5682
|
-
|
|
6116
|
+
clack8.log.warn(warning);
|
|
5683
6117
|
}
|
|
5684
6118
|
}
|
|
5685
6119
|
const metadata = createConnectionMetadata(
|
|
@@ -5695,7 +6129,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5695
6129
|
if (!(options.yes || options.preview)) {
|
|
5696
6130
|
const confirmed = await confirmDeploy();
|
|
5697
6131
|
if (!confirmed) {
|
|
5698
|
-
|
|
6132
|
+
clack8.cancel("Deployment cancelled.");
|
|
5699
6133
|
process.exit(0);
|
|
5700
6134
|
}
|
|
5701
6135
|
}
|
|
@@ -5711,7 +6145,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5711
6145
|
"Generating infrastructure preview",
|
|
5712
6146
|
async () => {
|
|
5713
6147
|
await ensurePulumiWorkDir();
|
|
5714
|
-
const stack = await
|
|
6148
|
+
const stack = await pulumi8.automation.LocalWorkspace.createOrSelectStack(
|
|
5715
6149
|
{
|
|
5716
6150
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
5717
6151
|
projectName: "wraps-email",
|
|
@@ -5752,11 +6186,18 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5752
6186
|
costEstimate: costSummary,
|
|
5753
6187
|
commandName: "wraps email init"
|
|
5754
6188
|
});
|
|
5755
|
-
|
|
5756
|
-
|
|
6189
|
+
clack8.outro(
|
|
6190
|
+
pc9.green("Preview complete. Run without --preview to deploy.")
|
|
5757
6191
|
);
|
|
6192
|
+
trackServiceInit("email", true, {
|
|
6193
|
+
preset,
|
|
6194
|
+
provider,
|
|
6195
|
+
preview: true,
|
|
6196
|
+
duration_ms: Date.now() - startTime
|
|
6197
|
+
});
|
|
5758
6198
|
return;
|
|
5759
6199
|
} catch (error) {
|
|
6200
|
+
trackError("PREVIEW_FAILED", "email:init", { step: "preview" });
|
|
5760
6201
|
if (error.message?.includes("stack is currently locked")) {
|
|
5761
6202
|
throw errors.stackLocked();
|
|
5762
6203
|
}
|
|
@@ -5769,7 +6210,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5769
6210
|
"Deploying infrastructure (this may take 2-3 minutes)",
|
|
5770
6211
|
async () => {
|
|
5771
6212
|
await ensurePulumiWorkDir();
|
|
5772
|
-
const stack = await
|
|
6213
|
+
const stack = await pulumi8.automation.LocalWorkspace.createOrSelectStack(
|
|
5773
6214
|
{
|
|
5774
6215
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
5775
6216
|
projectName: "wraps-email",
|
|
@@ -5826,9 +6267,16 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5826
6267
|
}
|
|
5827
6268
|
);
|
|
5828
6269
|
} catch (error) {
|
|
6270
|
+
trackServiceInit("email", false, {
|
|
6271
|
+
preset,
|
|
6272
|
+
provider,
|
|
6273
|
+
duration_ms: Date.now() - startTime
|
|
6274
|
+
});
|
|
5829
6275
|
if (error.message?.includes("stack is currently locked")) {
|
|
6276
|
+
trackError("STACK_LOCKED", "email:init", { step: "deploy" });
|
|
5830
6277
|
throw errors.stackLocked();
|
|
5831
6278
|
}
|
|
6279
|
+
trackError("DEPLOYMENT_FAILED", "email:init", { step: "deploy" });
|
|
5832
6280
|
throw new Error(`Pulumi deployment failed: ${error.message}`);
|
|
5833
6281
|
}
|
|
5834
6282
|
if (metadata.services.email) {
|
|
@@ -5838,8 +6286,8 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5838
6286
|
progress.info("Connection metadata saved for upgrade and restore capability");
|
|
5839
6287
|
let dnsAutoCreated = false;
|
|
5840
6288
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
5841
|
-
const { findHostedZone:
|
|
5842
|
-
const hostedZone = await
|
|
6289
|
+
const { findHostedZone: findHostedZone3, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
6290
|
+
const hostedZone = await findHostedZone3(outputs.domain, region);
|
|
5843
6291
|
if (hostedZone) {
|
|
5844
6292
|
try {
|
|
5845
6293
|
progress.start("Creating DNS records in Route53");
|
|
@@ -5855,7 +6303,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5855
6303
|
dnsAutoCreated = true;
|
|
5856
6304
|
} catch (error) {
|
|
5857
6305
|
progress.fail("Failed to create DNS records in Route53");
|
|
5858
|
-
|
|
6306
|
+
clack8.log.warn(`Could not auto-create DNS records: ${error.message}`);
|
|
5859
6307
|
}
|
|
5860
6308
|
}
|
|
5861
6309
|
}
|
|
@@ -5879,24 +6327,49 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5879
6327
|
domain: outputs.domain,
|
|
5880
6328
|
mailFromDomain: outputs.mailFromDomain
|
|
5881
6329
|
});
|
|
6330
|
+
const duration = Date.now() - startTime;
|
|
6331
|
+
const enabledFeatures = [];
|
|
6332
|
+
if (emailConfig.tracking?.enabled) enabledFeatures.push("tracking");
|
|
6333
|
+
if (emailConfig.suppressionList?.enabled)
|
|
6334
|
+
enabledFeatures.push("suppression_list");
|
|
6335
|
+
if (emailConfig.eventTracking?.enabled)
|
|
6336
|
+
enabledFeatures.push("event_tracking");
|
|
6337
|
+
if (emailConfig.eventTracking?.dynamoDBHistory)
|
|
6338
|
+
enabledFeatures.push("dynamodb_history");
|
|
6339
|
+
if (emailConfig.dedicatedIp) enabledFeatures.push("dedicated_ip");
|
|
6340
|
+
if (emailConfig.emailArchiving?.enabled)
|
|
6341
|
+
enabledFeatures.push("email_archiving");
|
|
6342
|
+
trackServiceInit("email", true, {
|
|
6343
|
+
preset,
|
|
6344
|
+
provider,
|
|
6345
|
+
features: enabledFeatures,
|
|
6346
|
+
duration_ms: duration
|
|
6347
|
+
});
|
|
6348
|
+
trackServiceDeployed("email", {
|
|
6349
|
+
duration_ms: duration,
|
|
6350
|
+
features: enabledFeatures,
|
|
6351
|
+
preset
|
|
6352
|
+
});
|
|
5882
6353
|
}
|
|
5883
6354
|
|
|
5884
6355
|
// src/commands/email/restore.ts
|
|
5885
6356
|
init_esm_shims();
|
|
6357
|
+
init_events();
|
|
5886
6358
|
init_aws();
|
|
5887
|
-
import * as
|
|
5888
|
-
import * as
|
|
5889
|
-
import
|
|
6359
|
+
import * as clack9 from "@clack/prompts";
|
|
6360
|
+
import * as pulumi9 from "@pulumi/pulumi";
|
|
6361
|
+
import pc10 from "picocolors";
|
|
5890
6362
|
async function restore(options) {
|
|
5891
|
-
|
|
5892
|
-
|
|
6363
|
+
const startTime = Date.now();
|
|
6364
|
+
clack9.intro(
|
|
6365
|
+
pc10.bold(
|
|
5893
6366
|
options.preview ? "Wraps Restore Preview" : "Wraps Restore - Remove Wraps Infrastructure"
|
|
5894
6367
|
)
|
|
5895
6368
|
);
|
|
5896
|
-
|
|
5897
|
-
`${
|
|
6369
|
+
clack9.log.info(
|
|
6370
|
+
`${pc10.yellow("Note:")} This will remove all Wraps-managed infrastructure.`
|
|
5898
6371
|
);
|
|
5899
|
-
|
|
6372
|
+
clack9.log.info(
|
|
5900
6373
|
"Your original AWS resources remain untouched (Wraps never modifies them).\n"
|
|
5901
6374
|
);
|
|
5902
6375
|
const progress = new DeploymentProgress();
|
|
@@ -5904,7 +6377,7 @@ async function restore(options) {
|
|
|
5904
6377
|
"Validating AWS credentials",
|
|
5905
6378
|
async () => validateAWSCredentials()
|
|
5906
6379
|
);
|
|
5907
|
-
progress.info(`Connected to AWS account: ${
|
|
6380
|
+
progress.info(`Connected to AWS account: ${pc10.cyan(identity.accountId)}`);
|
|
5908
6381
|
let region = options.region;
|
|
5909
6382
|
if (!region) {
|
|
5910
6383
|
const defaultRegion = await getAWSRegion();
|
|
@@ -5912,40 +6385,40 @@ async function restore(options) {
|
|
|
5912
6385
|
}
|
|
5913
6386
|
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
5914
6387
|
if (!metadata) {
|
|
5915
|
-
|
|
5916
|
-
`No Wraps connection found for account ${
|
|
6388
|
+
clack9.log.error(
|
|
6389
|
+
`No Wraps connection found for account ${pc10.cyan(identity.accountId)} in region ${pc10.cyan(region)}`
|
|
5917
6390
|
);
|
|
5918
|
-
|
|
5919
|
-
`Use ${
|
|
6391
|
+
clack9.log.info(
|
|
6392
|
+
`Use ${pc10.cyan("wraps email init")} or ${pc10.cyan("wraps email connect")} to create a connection first.`
|
|
5920
6393
|
);
|
|
5921
6394
|
process.exit(1);
|
|
5922
6395
|
}
|
|
5923
6396
|
progress.info(`Found connection created: ${metadata.timestamp}`);
|
|
5924
6397
|
console.log(
|
|
5925
6398
|
`
|
|
5926
|
-
${
|
|
6399
|
+
${pc10.bold("The following Wraps resources will be removed:")}
|
|
5927
6400
|
`
|
|
5928
6401
|
);
|
|
5929
6402
|
if (metadata.services.email?.config.tracking?.enabled) {
|
|
5930
|
-
console.log(` ${
|
|
6403
|
+
console.log(` ${pc10.cyan("\u2713")} Configuration Set (wraps-email-tracking)`);
|
|
5931
6404
|
}
|
|
5932
6405
|
if (metadata.services.email?.config.eventTracking?.dynamoDBHistory) {
|
|
5933
|
-
console.log(` ${
|
|
6406
|
+
console.log(` ${pc10.cyan("\u2713")} DynamoDB Table (wraps-email-history)`);
|
|
5934
6407
|
}
|
|
5935
6408
|
if (metadata.services.email?.config.eventTracking?.enabled) {
|
|
5936
|
-
console.log(` ${
|
|
5937
|
-
console.log(` ${
|
|
5938
|
-
console.log(` ${
|
|
6409
|
+
console.log(` ${pc10.cyan("\u2713")} EventBridge Rules`);
|
|
6410
|
+
console.log(` ${pc10.cyan("\u2713")} SQS Queues`);
|
|
6411
|
+
console.log(` ${pc10.cyan("\u2713")} Lambda Functions`);
|
|
5939
6412
|
}
|
|
5940
|
-
console.log(` ${
|
|
6413
|
+
console.log(` ${pc10.cyan("\u2713")} IAM Role (wraps-email-role)`);
|
|
5941
6414
|
console.log("");
|
|
5942
6415
|
if (!(options.force || options.preview)) {
|
|
5943
|
-
const confirmed = await
|
|
6416
|
+
const confirmed = await clack9.confirm({
|
|
5944
6417
|
message: "Proceed with removal? This cannot be undone.",
|
|
5945
6418
|
initialValue: false
|
|
5946
6419
|
});
|
|
5947
|
-
if (
|
|
5948
|
-
|
|
6420
|
+
if (clack9.isCancel(confirmed) || !confirmed) {
|
|
6421
|
+
clack9.cancel("Removal cancelled.");
|
|
5949
6422
|
process.exit(0);
|
|
5950
6423
|
}
|
|
5951
6424
|
}
|
|
@@ -5955,7 +6428,7 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
5955
6428
|
const previewResult = await progress.execute(
|
|
5956
6429
|
"Generating removal preview",
|
|
5957
6430
|
async () => {
|
|
5958
|
-
const stack = await
|
|
6431
|
+
const stack = await pulumi9.automation.LocalWorkspace.selectStack(
|
|
5959
6432
|
{
|
|
5960
6433
|
stackName: metadata.services.email.pulumiStackName,
|
|
5961
6434
|
projectName: "wraps-email",
|
|
@@ -5981,13 +6454,18 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
5981
6454
|
costEstimate: "Monthly cost after removal: $0.00",
|
|
5982
6455
|
commandName: "wraps email restore"
|
|
5983
6456
|
});
|
|
5984
|
-
|
|
5985
|
-
|
|
6457
|
+
clack9.outro(
|
|
6458
|
+
pc10.green(
|
|
5986
6459
|
"Preview complete. Run without --preview to remove infrastructure."
|
|
5987
6460
|
)
|
|
5988
6461
|
);
|
|
6462
|
+
trackServiceRemoved("email", {
|
|
6463
|
+
preview: true,
|
|
6464
|
+
duration_ms: Date.now() - startTime
|
|
6465
|
+
});
|
|
5989
6466
|
return;
|
|
5990
6467
|
} catch (error) {
|
|
6468
|
+
trackError("PREVIEW_FAILED", "email:restore", { step: "preview" });
|
|
5991
6469
|
throw new Error(`Preview failed: ${error.message}`);
|
|
5992
6470
|
}
|
|
5993
6471
|
}
|
|
@@ -5999,7 +6477,7 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
5999
6477
|
if (!metadata.services.email?.pulumiStackName) {
|
|
6000
6478
|
throw new Error("No Pulumi stack name found in metadata");
|
|
6001
6479
|
}
|
|
6002
|
-
const stack = await
|
|
6480
|
+
const stack = await pulumi9.automation.LocalWorkspace.selectStack(
|
|
6003
6481
|
{
|
|
6004
6482
|
stackName: metadata.services.email.pulumiStackName,
|
|
6005
6483
|
projectName: "wraps-email",
|
|
@@ -6022,6 +6500,7 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
6022
6500
|
metadata.services.email.pulumiStackName
|
|
6023
6501
|
);
|
|
6024
6502
|
} catch (error) {
|
|
6503
|
+
trackError("DESTROY_FAILED", "email:restore", { step: "destroy" });
|
|
6025
6504
|
throw new Error(`Failed to destroy Pulumi stack: ${error.message}`);
|
|
6026
6505
|
}
|
|
6027
6506
|
});
|
|
@@ -6030,29 +6509,127 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
6030
6509
|
progress.info("Connection metadata deleted");
|
|
6031
6510
|
console.log(
|
|
6032
6511
|
`
|
|
6033
|
-
${
|
|
6512
|
+
${pc10.green("\u2713")} ${pc10.bold("Infrastructure removed successfully!")}
|
|
6034
6513
|
`
|
|
6035
6514
|
);
|
|
6036
6515
|
console.log(
|
|
6037
|
-
`${
|
|
6516
|
+
`${pc10.dim("All Wraps resources have been deleted from your AWS account.")}`
|
|
6038
6517
|
);
|
|
6039
|
-
console.log(`${
|
|
6518
|
+
console.log(`${pc10.dim("Your original AWS resources remain unchanged.")}
|
|
6040
6519
|
`);
|
|
6520
|
+
trackServiceRemoved("email", {
|
|
6521
|
+
reason: "user_initiated",
|
|
6522
|
+
duration_ms: Date.now() - startTime
|
|
6523
|
+
});
|
|
6524
|
+
}
|
|
6525
|
+
|
|
6526
|
+
// src/commands/email/status.ts
|
|
6527
|
+
init_esm_shims();
|
|
6528
|
+
init_events();
|
|
6529
|
+
init_aws();
|
|
6530
|
+
import * as clack10 from "@clack/prompts";
|
|
6531
|
+
import * as pulumi10 from "@pulumi/pulumi";
|
|
6532
|
+
import pc11 from "picocolors";
|
|
6533
|
+
async function emailStatus(_options) {
|
|
6534
|
+
const startTime = Date.now();
|
|
6535
|
+
const progress = new DeploymentProgress();
|
|
6536
|
+
clack10.intro(pc11.bold("Wraps Email Status"));
|
|
6537
|
+
const identity = await progress.execute(
|
|
6538
|
+
"Loading email infrastructure status",
|
|
6539
|
+
async () => validateAWSCredentials()
|
|
6540
|
+
);
|
|
6541
|
+
const region = await getAWSRegion();
|
|
6542
|
+
let stackOutputs = {};
|
|
6543
|
+
try {
|
|
6544
|
+
await ensurePulumiWorkDir();
|
|
6545
|
+
const stack = await pulumi10.automation.LocalWorkspace.selectStack({
|
|
6546
|
+
stackName: `wraps-${identity.accountId}-${region}`,
|
|
6547
|
+
workDir: getPulumiWorkDir()
|
|
6548
|
+
});
|
|
6549
|
+
stackOutputs = await stack.outputs();
|
|
6550
|
+
} catch (_error) {
|
|
6551
|
+
progress.stop();
|
|
6552
|
+
clack10.log.error("No email infrastructure found");
|
|
6553
|
+
console.log(
|
|
6554
|
+
`
|
|
6555
|
+
Run ${pc11.cyan("wraps email init")} to deploy email infrastructure.
|
|
6556
|
+
`
|
|
6557
|
+
);
|
|
6558
|
+
process.exit(1);
|
|
6559
|
+
}
|
|
6560
|
+
const domains = await listSESDomains(region);
|
|
6561
|
+
const { SESv2Client: SESv2Client5, GetEmailIdentityCommand: GetEmailIdentityCommand4 } = await import("@aws-sdk/client-sesv2");
|
|
6562
|
+
const sesv2Client = new SESv2Client5({ region });
|
|
6563
|
+
const domainsWithTokens = await Promise.all(
|
|
6564
|
+
domains.map(async (d) => {
|
|
6565
|
+
try {
|
|
6566
|
+
const identity2 = await sesv2Client.send(
|
|
6567
|
+
new GetEmailIdentityCommand4({ EmailIdentity: d.domain })
|
|
6568
|
+
);
|
|
6569
|
+
return {
|
|
6570
|
+
domain: d.domain,
|
|
6571
|
+
status: d.verified ? "verified" : "pending",
|
|
6572
|
+
dkimTokens: identity2.DkimAttributes?.Tokens || [],
|
|
6573
|
+
mailFromDomain: identity2.MailFromAttributes?.MailFromDomain,
|
|
6574
|
+
mailFromStatus: identity2.MailFromAttributes?.MailFromDomainStatus
|
|
6575
|
+
};
|
|
6576
|
+
} catch (_error) {
|
|
6577
|
+
return {
|
|
6578
|
+
domain: d.domain,
|
|
6579
|
+
status: d.verified ? "verified" : "pending",
|
|
6580
|
+
dkimTokens: void 0,
|
|
6581
|
+
mailFromDomain: void 0,
|
|
6582
|
+
mailFromStatus: void 0
|
|
6583
|
+
};
|
|
6584
|
+
}
|
|
6585
|
+
})
|
|
6586
|
+
);
|
|
6587
|
+
const integrationLevel = stackOutputs.configSetName ? "enhanced" : "dashboard-only";
|
|
6588
|
+
progress.stop();
|
|
6589
|
+
displayStatus({
|
|
6590
|
+
integrationLevel,
|
|
6591
|
+
region,
|
|
6592
|
+
domains: domainsWithTokens,
|
|
6593
|
+
resources: {
|
|
6594
|
+
roleArn: stackOutputs.roleArn?.value,
|
|
6595
|
+
configSetName: stackOutputs.configSetName?.value,
|
|
6596
|
+
tableName: stackOutputs.tableName?.value,
|
|
6597
|
+
lambdaFunctions: stackOutputs.lambdaFunctions?.value?.length || 0,
|
|
6598
|
+
snsTopics: integrationLevel === "enhanced" ? 1 : 0,
|
|
6599
|
+
archiveArn: stackOutputs.archiveArn?.value,
|
|
6600
|
+
archivingEnabled: stackOutputs.archivingEnabled?.value,
|
|
6601
|
+
archiveRetention: stackOutputs.archiveRetention?.value
|
|
6602
|
+
},
|
|
6603
|
+
tracking: stackOutputs.customTrackingDomain?.value ? {
|
|
6604
|
+
customTrackingDomain: stackOutputs.customTrackingDomain?.value,
|
|
6605
|
+
httpsEnabled: stackOutputs.httpsTrackingEnabled?.value,
|
|
6606
|
+
cloudFrontDomain: stackOutputs.cloudFrontDomain?.value
|
|
6607
|
+
} : void 0
|
|
6608
|
+
});
|
|
6609
|
+
trackCommand("email:status", {
|
|
6610
|
+
success: true,
|
|
6611
|
+
domain_count: domainsWithTokens.length,
|
|
6612
|
+
integration_level: integrationLevel,
|
|
6613
|
+
duration_ms: Date.now() - startTime
|
|
6614
|
+
});
|
|
6041
6615
|
}
|
|
6042
6616
|
|
|
6043
6617
|
// src/commands/email/upgrade.ts
|
|
6044
6618
|
init_esm_shims();
|
|
6045
|
-
import * as
|
|
6046
|
-
import * as
|
|
6047
|
-
import
|
|
6619
|
+
import * as clack11 from "@clack/prompts";
|
|
6620
|
+
import * as pulumi11 from "@pulumi/pulumi";
|
|
6621
|
+
import pc12 from "picocolors";
|
|
6622
|
+
init_events();
|
|
6048
6623
|
init_costs();
|
|
6049
6624
|
init_presets();
|
|
6050
6625
|
init_aws();
|
|
6051
6626
|
init_errors();
|
|
6052
6627
|
init_prompts();
|
|
6053
6628
|
async function upgrade(options) {
|
|
6054
|
-
|
|
6055
|
-
|
|
6629
|
+
const startTime = Date.now();
|
|
6630
|
+
let upgradeAction = "";
|
|
6631
|
+
clack11.intro(
|
|
6632
|
+
pc12.bold(
|
|
6056
6633
|
options.preview ? "Wraps Upgrade Preview" : "Wraps Upgrade - Enhance Your Email Infrastructure"
|
|
6057
6634
|
)
|
|
6058
6635
|
);
|
|
@@ -6068,7 +6645,7 @@ async function upgrade(options) {
|
|
|
6068
6645
|
"Validating AWS credentials",
|
|
6069
6646
|
async () => validateAWSCredentials()
|
|
6070
6647
|
);
|
|
6071
|
-
progress.info(`Connected to AWS account: ${
|
|
6648
|
+
progress.info(`Connected to AWS account: ${pc12.cyan(identity.accountId)}`);
|
|
6072
6649
|
let region = options.region;
|
|
6073
6650
|
if (!region) {
|
|
6074
6651
|
const defaultRegion = await getAWSRegion();
|
|
@@ -6076,55 +6653,55 @@ async function upgrade(options) {
|
|
|
6076
6653
|
}
|
|
6077
6654
|
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
6078
6655
|
if (!metadata) {
|
|
6079
|
-
|
|
6080
|
-
`No Wraps connection found for account ${
|
|
6656
|
+
clack11.log.error(
|
|
6657
|
+
`No Wraps connection found for account ${pc12.cyan(identity.accountId)} in region ${pc12.cyan(region)}`
|
|
6081
6658
|
);
|
|
6082
|
-
|
|
6083
|
-
`Use ${
|
|
6659
|
+
clack11.log.info(
|
|
6660
|
+
`Use ${pc12.cyan("wraps email init")} to create new infrastructure or ${pc12.cyan("wraps email connect")} to connect existing.`
|
|
6084
6661
|
);
|
|
6085
6662
|
process.exit(1);
|
|
6086
6663
|
}
|
|
6087
6664
|
progress.info(`Found existing connection created: ${metadata.timestamp}`);
|
|
6088
6665
|
console.log(`
|
|
6089
|
-
${
|
|
6666
|
+
${pc12.bold("Current Configuration:")}
|
|
6090
6667
|
`);
|
|
6091
6668
|
if (metadata.services.email?.preset) {
|
|
6092
|
-
console.log(` Preset: ${
|
|
6669
|
+
console.log(` Preset: ${pc12.cyan(metadata.services.email?.preset)}`);
|
|
6093
6670
|
} else {
|
|
6094
|
-
console.log(` Preset: ${
|
|
6671
|
+
console.log(` Preset: ${pc12.cyan("custom")}`);
|
|
6095
6672
|
}
|
|
6096
6673
|
const config2 = metadata.services.email?.config;
|
|
6097
6674
|
if (!config2) {
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
`Use ${
|
|
6675
|
+
clack11.log.error("No email configuration found in metadata");
|
|
6676
|
+
clack11.log.info(
|
|
6677
|
+
`Use ${pc12.cyan("wraps email init")} to create new infrastructure.`
|
|
6101
6678
|
);
|
|
6102
6679
|
process.exit(1);
|
|
6103
6680
|
}
|
|
6104
6681
|
if (config2.domain) {
|
|
6105
|
-
console.log(` Sending Domain: ${
|
|
6682
|
+
console.log(` Sending Domain: ${pc12.cyan(config2.domain)}`);
|
|
6106
6683
|
}
|
|
6107
6684
|
if (config2.tracking?.enabled) {
|
|
6108
|
-
console.log(` ${
|
|
6685
|
+
console.log(` ${pc12.green("\u2713")} Open & Click Tracking`);
|
|
6109
6686
|
if (config2.tracking.customRedirectDomain) {
|
|
6110
6687
|
console.log(
|
|
6111
|
-
` ${
|
|
6688
|
+
` ${pc12.dim("\u2514\u2500")} Custom domain: ${pc12.cyan(config2.tracking.customRedirectDomain)}`
|
|
6112
6689
|
);
|
|
6113
6690
|
}
|
|
6114
6691
|
}
|
|
6115
6692
|
if (config2.suppressionList?.enabled) {
|
|
6116
|
-
console.log(` ${
|
|
6693
|
+
console.log(` ${pc12.green("\u2713")} Bounce/Complaint Suppression`);
|
|
6117
6694
|
}
|
|
6118
6695
|
if (config2.eventTracking?.enabled) {
|
|
6119
|
-
console.log(` ${
|
|
6696
|
+
console.log(` ${pc12.green("\u2713")} Event Tracking (EventBridge)`);
|
|
6120
6697
|
if (config2.eventTracking.dynamoDBHistory) {
|
|
6121
6698
|
console.log(
|
|
6122
|
-
` ${
|
|
6699
|
+
` ${pc12.dim("\u2514\u2500")} Email History: ${pc12.cyan(config2.eventTracking.archiveRetention || "90days")}`
|
|
6123
6700
|
);
|
|
6124
6701
|
}
|
|
6125
6702
|
}
|
|
6126
6703
|
if (config2.dedicatedIp) {
|
|
6127
|
-
console.log(` ${
|
|
6704
|
+
console.log(` ${pc12.green("\u2713")} Dedicated IP Address`);
|
|
6128
6705
|
}
|
|
6129
6706
|
if (config2.emailArchiving?.enabled) {
|
|
6130
6707
|
const retentionLabel = {
|
|
@@ -6149,15 +6726,15 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6149
6726
|
indefinite: "indefinite",
|
|
6150
6727
|
permanent: "permanent"
|
|
6151
6728
|
}[config2.emailArchiving.retention] || "90 days";
|
|
6152
|
-
console.log(` ${
|
|
6729
|
+
console.log(` ${pc12.green("\u2713")} Email Archiving (${retentionLabel})`);
|
|
6153
6730
|
}
|
|
6154
6731
|
const currentCostData = calculateCosts(config2, 5e4);
|
|
6155
6732
|
console.log(
|
|
6156
6733
|
`
|
|
6157
|
-
Estimated Cost: ${
|
|
6734
|
+
Estimated Cost: ${pc12.cyan(`~${formatCost(currentCostData.total.monthly)}/mo`)}`
|
|
6158
6735
|
);
|
|
6159
6736
|
console.log("");
|
|
6160
|
-
|
|
6737
|
+
upgradeAction = await clack11.select({
|
|
6161
6738
|
message: "What would you like to do?",
|
|
6162
6739
|
options: [
|
|
6163
6740
|
{
|
|
@@ -6197,8 +6774,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6197
6774
|
}
|
|
6198
6775
|
]
|
|
6199
6776
|
});
|
|
6200
|
-
if (
|
|
6201
|
-
|
|
6777
|
+
if (clack11.isCancel(upgradeAction)) {
|
|
6778
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6202
6779
|
process.exit(0);
|
|
6203
6780
|
}
|
|
6204
6781
|
let updatedConfig = { ...config2 };
|
|
@@ -6216,15 +6793,15 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6216
6793
|
disabled: currentPresetIdx >= 0 && idx <= currentPresetIdx ? "Current or lower tier" : void 0
|
|
6217
6794
|
})).filter((p) => !p.disabled);
|
|
6218
6795
|
if (availablePresets.length === 0) {
|
|
6219
|
-
|
|
6796
|
+
clack11.log.warn("Already on highest preset (Enterprise)");
|
|
6220
6797
|
process.exit(0);
|
|
6221
6798
|
}
|
|
6222
|
-
const selectedPreset = await
|
|
6799
|
+
const selectedPreset = await clack11.select({
|
|
6223
6800
|
message: "Select new preset:",
|
|
6224
6801
|
options: availablePresets
|
|
6225
6802
|
});
|
|
6226
|
-
if (
|
|
6227
|
-
|
|
6803
|
+
if (clack11.isCancel(selectedPreset)) {
|
|
6804
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6228
6805
|
process.exit(0);
|
|
6229
6806
|
}
|
|
6230
6807
|
const presetConfig = getPreset(selectedPreset);
|
|
@@ -6234,7 +6811,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6234
6811
|
}
|
|
6235
6812
|
case "archiving": {
|
|
6236
6813
|
if (config2.emailArchiving?.enabled) {
|
|
6237
|
-
const archivingAction = await
|
|
6814
|
+
const archivingAction = await clack11.select({
|
|
6238
6815
|
message: "What would you like to do with email archiving?",
|
|
6239
6816
|
options: [
|
|
6240
6817
|
{
|
|
@@ -6249,17 +6826,17 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6249
6826
|
}
|
|
6250
6827
|
]
|
|
6251
6828
|
});
|
|
6252
|
-
if (
|
|
6253
|
-
|
|
6829
|
+
if (clack11.isCancel(archivingAction)) {
|
|
6830
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6254
6831
|
process.exit(0);
|
|
6255
6832
|
}
|
|
6256
6833
|
if (archivingAction === "disable") {
|
|
6257
|
-
const confirmDisable = await
|
|
6834
|
+
const confirmDisable = await clack11.confirm({
|
|
6258
6835
|
message: "Are you sure? Existing archived emails will remain, but new emails won't be archived.",
|
|
6259
6836
|
initialValue: false
|
|
6260
6837
|
});
|
|
6261
|
-
if (
|
|
6262
|
-
|
|
6838
|
+
if (clack11.isCancel(confirmDisable) || !confirmDisable) {
|
|
6839
|
+
clack11.cancel("Archiving not disabled.");
|
|
6263
6840
|
process.exit(0);
|
|
6264
6841
|
}
|
|
6265
6842
|
updatedConfig = {
|
|
@@ -6270,7 +6847,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6270
6847
|
}
|
|
6271
6848
|
};
|
|
6272
6849
|
} else {
|
|
6273
|
-
const retention = await
|
|
6850
|
+
const retention = await clack11.select({
|
|
6274
6851
|
message: "Email archive retention period:",
|
|
6275
6852
|
options: [
|
|
6276
6853
|
{
|
|
@@ -6306,8 +6883,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6306
6883
|
],
|
|
6307
6884
|
initialValue: config2.emailArchiving.retention
|
|
6308
6885
|
});
|
|
6309
|
-
if (
|
|
6310
|
-
|
|
6886
|
+
if (clack11.isCancel(retention)) {
|
|
6887
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6311
6888
|
process.exit(0);
|
|
6312
6889
|
}
|
|
6313
6890
|
updatedConfig = {
|
|
@@ -6319,19 +6896,19 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6319
6896
|
};
|
|
6320
6897
|
}
|
|
6321
6898
|
} else {
|
|
6322
|
-
const enableArchiving = await
|
|
6899
|
+
const enableArchiving = await clack11.confirm({
|
|
6323
6900
|
message: "Enable email archiving? (Store full email content with HTML for viewing)",
|
|
6324
6901
|
initialValue: true
|
|
6325
6902
|
});
|
|
6326
|
-
if (
|
|
6327
|
-
|
|
6903
|
+
if (clack11.isCancel(enableArchiving)) {
|
|
6904
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6328
6905
|
process.exit(0);
|
|
6329
6906
|
}
|
|
6330
6907
|
if (!enableArchiving) {
|
|
6331
|
-
|
|
6908
|
+
clack11.log.info("Email archiving not enabled.");
|
|
6332
6909
|
process.exit(0);
|
|
6333
6910
|
}
|
|
6334
|
-
const retention = await
|
|
6911
|
+
const retention = await clack11.select({
|
|
6335
6912
|
message: "Email archive retention period:",
|
|
6336
6913
|
options: [
|
|
6337
6914
|
{
|
|
@@ -6367,17 +6944,17 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6367
6944
|
],
|
|
6368
6945
|
initialValue: "90days"
|
|
6369
6946
|
});
|
|
6370
|
-
if (
|
|
6371
|
-
|
|
6947
|
+
if (clack11.isCancel(retention)) {
|
|
6948
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6372
6949
|
process.exit(0);
|
|
6373
6950
|
}
|
|
6374
|
-
|
|
6375
|
-
|
|
6951
|
+
clack11.log.info(
|
|
6952
|
+
pc12.dim(
|
|
6376
6953
|
"Archiving stores full RFC 822 emails with HTML, attachments, and headers"
|
|
6377
6954
|
)
|
|
6378
6955
|
);
|
|
6379
|
-
|
|
6380
|
-
|
|
6956
|
+
clack11.log.info(
|
|
6957
|
+
pc12.dim(
|
|
6381
6958
|
"Cost: $2/GB ingestion + $0.19/GB/month storage (~50KB per email)"
|
|
6382
6959
|
)
|
|
6383
6960
|
);
|
|
@@ -6394,11 +6971,11 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6394
6971
|
}
|
|
6395
6972
|
case "tracking-domain": {
|
|
6396
6973
|
if (!config2.domain) {
|
|
6397
|
-
|
|
6974
|
+
clack11.log.error(
|
|
6398
6975
|
"No sending domain configured. You must configure a sending domain before adding a custom tracking domain."
|
|
6399
6976
|
);
|
|
6400
|
-
|
|
6401
|
-
`Use ${
|
|
6977
|
+
clack11.log.info(
|
|
6978
|
+
`Use ${pc12.cyan("wraps email init")} to set up a sending domain first.`
|
|
6402
6979
|
);
|
|
6403
6980
|
process.exit(1);
|
|
6404
6981
|
}
|
|
@@ -6409,21 +6986,21 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6409
6986
|
);
|
|
6410
6987
|
const sendingDomain = domains.find((d) => d.domain === config2.domain);
|
|
6411
6988
|
if (!sendingDomain?.verified) {
|
|
6412
|
-
|
|
6413
|
-
`Sending domain ${
|
|
6989
|
+
clack11.log.error(
|
|
6990
|
+
`Sending domain ${pc12.cyan(config2.domain)} is not verified.`
|
|
6414
6991
|
);
|
|
6415
|
-
|
|
6992
|
+
clack11.log.info(
|
|
6416
6993
|
"You must verify your sending domain before adding a custom tracking domain."
|
|
6417
6994
|
);
|
|
6418
|
-
|
|
6419
|
-
`Use ${
|
|
6995
|
+
clack11.log.info(
|
|
6996
|
+
`Use ${pc12.cyan("wraps email verify")} to check DNS records and complete verification.`
|
|
6420
6997
|
);
|
|
6421
6998
|
process.exit(1);
|
|
6422
6999
|
}
|
|
6423
7000
|
progress.info(
|
|
6424
|
-
`Sending domain ${
|
|
7001
|
+
`Sending domain ${pc12.cyan(config2.domain)} is verified ${pc12.green("\u2713")}`
|
|
6425
7002
|
);
|
|
6426
|
-
const trackingDomain = await
|
|
7003
|
+
const trackingDomain = await clack11.text({
|
|
6427
7004
|
message: "Custom tracking redirect domain:",
|
|
6428
7005
|
placeholder: "track.yourdomain.com",
|
|
6429
7006
|
initialValue: config2.tracking?.customRedirectDomain || "",
|
|
@@ -6433,62 +7010,62 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6433
7010
|
}
|
|
6434
7011
|
}
|
|
6435
7012
|
});
|
|
6436
|
-
if (
|
|
6437
|
-
|
|
7013
|
+
if (clack11.isCancel(trackingDomain)) {
|
|
7014
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6438
7015
|
process.exit(0);
|
|
6439
7016
|
}
|
|
6440
|
-
const enableHttps = await
|
|
7017
|
+
const enableHttps = await clack11.confirm({
|
|
6441
7018
|
message: "Enable HTTPS tracking with CloudFront + SSL certificate?",
|
|
6442
7019
|
initialValue: true
|
|
6443
7020
|
});
|
|
6444
|
-
if (
|
|
6445
|
-
|
|
7021
|
+
if (clack11.isCancel(enableHttps)) {
|
|
7022
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6446
7023
|
process.exit(0);
|
|
6447
7024
|
}
|
|
6448
7025
|
if (enableHttps) {
|
|
6449
|
-
|
|
6450
|
-
|
|
7026
|
+
clack11.log.info(
|
|
7027
|
+
pc12.dim(
|
|
6451
7028
|
"HTTPS tracking creates a CloudFront distribution with an SSL certificate."
|
|
6452
7029
|
)
|
|
6453
7030
|
);
|
|
6454
|
-
|
|
6455
|
-
|
|
7031
|
+
clack11.log.info(
|
|
7032
|
+
pc12.dim(
|
|
6456
7033
|
"This ensures all tracking links use secure HTTPS connections."
|
|
6457
7034
|
)
|
|
6458
7035
|
);
|
|
6459
|
-
const { findHostedZone:
|
|
7036
|
+
const { findHostedZone: findHostedZone3 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
6460
7037
|
const hostedZone = await progress.execute(
|
|
6461
7038
|
"Checking for Route53 hosted zone",
|
|
6462
|
-
async () => await
|
|
7039
|
+
async () => await findHostedZone3(trackingDomain || config2.domain, region)
|
|
6463
7040
|
);
|
|
6464
7041
|
if (hostedZone) {
|
|
6465
7042
|
progress.info(
|
|
6466
|
-
`Found Route53 hosted zone: ${
|
|
7043
|
+
`Found Route53 hosted zone: ${pc12.cyan(hostedZone.name)} ${pc12.green("\u2713")}`
|
|
6467
7044
|
);
|
|
6468
|
-
|
|
6469
|
-
|
|
7045
|
+
clack11.log.info(
|
|
7046
|
+
pc12.dim(
|
|
6470
7047
|
"DNS records (SSL certificate validation + CloudFront) will be created automatically."
|
|
6471
7048
|
)
|
|
6472
7049
|
);
|
|
6473
7050
|
} else {
|
|
6474
|
-
|
|
6475
|
-
`No Route53 hosted zone found for ${
|
|
7051
|
+
clack11.log.warn(
|
|
7052
|
+
`No Route53 hosted zone found for ${pc12.cyan(trackingDomain || config2.domain)}`
|
|
6476
7053
|
);
|
|
6477
|
-
|
|
6478
|
-
|
|
7054
|
+
clack11.log.info(
|
|
7055
|
+
pc12.dim(
|
|
6479
7056
|
"You'll need to manually create DNS records for SSL certificate validation and CloudFront."
|
|
6480
7057
|
)
|
|
6481
7058
|
);
|
|
6482
|
-
|
|
6483
|
-
|
|
7059
|
+
clack11.log.info(
|
|
7060
|
+
pc12.dim("DNS record details will be shown after deployment.")
|
|
6484
7061
|
);
|
|
6485
7062
|
}
|
|
6486
|
-
const confirmHttps = await
|
|
7063
|
+
const confirmHttps = await clack11.confirm({
|
|
6487
7064
|
message: hostedZone ? "Proceed with automatic HTTPS setup?" : "Proceed with manual HTTPS setup (requires DNS configuration)?",
|
|
6488
7065
|
initialValue: true
|
|
6489
7066
|
});
|
|
6490
|
-
if (
|
|
6491
|
-
|
|
7067
|
+
if (clack11.isCancel(confirmHttps) || !confirmHttps) {
|
|
7068
|
+
clack11.log.info("HTTPS tracking not enabled. Using HTTP tracking.");
|
|
6492
7069
|
updatedConfig = {
|
|
6493
7070
|
...config2,
|
|
6494
7071
|
tracking: {
|
|
@@ -6510,8 +7087,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6510
7087
|
};
|
|
6511
7088
|
}
|
|
6512
7089
|
} else {
|
|
6513
|
-
|
|
6514
|
-
|
|
7090
|
+
clack11.log.info(
|
|
7091
|
+
pc12.dim(
|
|
6515
7092
|
"Using HTTP tracking (standard). Links will use http:// protocol."
|
|
6516
7093
|
)
|
|
6517
7094
|
);
|
|
@@ -6529,7 +7106,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6529
7106
|
break;
|
|
6530
7107
|
}
|
|
6531
7108
|
case "retention": {
|
|
6532
|
-
const retention = await
|
|
7109
|
+
const retention = await clack11.select({
|
|
6533
7110
|
message: "Email history retention period (event data in DynamoDB):",
|
|
6534
7111
|
options: [
|
|
6535
7112
|
{ value: "7days", label: "7 days", hint: "Minimal storage cost" },
|
|
@@ -6553,17 +7130,17 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6553
7130
|
],
|
|
6554
7131
|
initialValue: config2.eventTracking?.archiveRetention || "90days"
|
|
6555
7132
|
});
|
|
6556
|
-
if (
|
|
6557
|
-
|
|
7133
|
+
if (clack11.isCancel(retention)) {
|
|
7134
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6558
7135
|
process.exit(0);
|
|
6559
7136
|
}
|
|
6560
|
-
|
|
6561
|
-
|
|
7137
|
+
clack11.log.info(
|
|
7138
|
+
pc12.dim(
|
|
6562
7139
|
"Note: This is for event data (sent, delivered, opened, etc.) stored in DynamoDB."
|
|
6563
7140
|
)
|
|
6564
7141
|
);
|
|
6565
|
-
|
|
6566
|
-
|
|
7142
|
+
clack11.log.info(
|
|
7143
|
+
pc12.dim(
|
|
6567
7144
|
"For full email content storage, use 'Enable email archiving' option."
|
|
6568
7145
|
)
|
|
6569
7146
|
);
|
|
@@ -6580,7 +7157,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6580
7157
|
break;
|
|
6581
7158
|
}
|
|
6582
7159
|
case "events": {
|
|
6583
|
-
const selectedEvents = await
|
|
7160
|
+
const selectedEvents = await clack11.multiselect({
|
|
6584
7161
|
message: "Select SES event types to track:",
|
|
6585
7162
|
options: [
|
|
6586
7163
|
{ value: "SEND", label: "Send", hint: "Email sent to SES" },
|
|
@@ -6624,8 +7201,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6624
7201
|
],
|
|
6625
7202
|
required: true
|
|
6626
7203
|
});
|
|
6627
|
-
if (
|
|
6628
|
-
|
|
7204
|
+
if (clack11.isCancel(selectedEvents)) {
|
|
7205
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6629
7206
|
process.exit(0);
|
|
6630
7207
|
}
|
|
6631
7208
|
updatedConfig = {
|
|
@@ -6640,16 +7217,16 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6640
7217
|
break;
|
|
6641
7218
|
}
|
|
6642
7219
|
case "dedicated-ip": {
|
|
6643
|
-
const confirmed = await
|
|
7220
|
+
const confirmed = await clack11.confirm({
|
|
6644
7221
|
message: "Enable dedicated IP? (Requires 100k+ emails/day, adds ~$50-100/mo)",
|
|
6645
7222
|
initialValue: false
|
|
6646
7223
|
});
|
|
6647
|
-
if (
|
|
6648
|
-
|
|
7224
|
+
if (clack11.isCancel(confirmed)) {
|
|
7225
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6649
7226
|
process.exit(0);
|
|
6650
7227
|
}
|
|
6651
7228
|
if (!confirmed) {
|
|
6652
|
-
|
|
7229
|
+
clack11.log.info("Dedicated IP not enabled.");
|
|
6653
7230
|
process.exit(0);
|
|
6654
7231
|
}
|
|
6655
7232
|
updatedConfig = {
|
|
@@ -6670,28 +7247,28 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6670
7247
|
const newCostData = calculateCosts(updatedConfig, 5e4);
|
|
6671
7248
|
const costDiff = newCostData.total.monthly - currentCostData.total.monthly;
|
|
6672
7249
|
console.log(`
|
|
6673
|
-
${
|
|
7250
|
+
${pc12.bold("Cost Impact:")}`);
|
|
6674
7251
|
console.log(
|
|
6675
|
-
` Current: ${
|
|
7252
|
+
` Current: ${pc12.cyan(`${formatCost(currentCostData.total.monthly)}/mo`)}`
|
|
6676
7253
|
);
|
|
6677
7254
|
console.log(
|
|
6678
|
-
` New: ${
|
|
7255
|
+
` New: ${pc12.cyan(`${formatCost(newCostData.total.monthly)}/mo`)}`
|
|
6679
7256
|
);
|
|
6680
7257
|
if (costDiff > 0) {
|
|
6681
|
-
console.log(` Change: ${
|
|
7258
|
+
console.log(` Change: ${pc12.yellow(`+${formatCost(costDiff)}/mo`)}`);
|
|
6682
7259
|
} else if (costDiff < 0) {
|
|
6683
7260
|
console.log(
|
|
6684
|
-
` Change: ${
|
|
7261
|
+
` Change: ${pc12.green(`${formatCost(Math.abs(costDiff))}/mo`)}`
|
|
6685
7262
|
);
|
|
6686
7263
|
}
|
|
6687
7264
|
console.log("");
|
|
6688
7265
|
if (!(options.yes || options.preview)) {
|
|
6689
|
-
const confirmed = await
|
|
7266
|
+
const confirmed = await clack11.confirm({
|
|
6690
7267
|
message: "Proceed with upgrade?",
|
|
6691
7268
|
initialValue: true
|
|
6692
7269
|
});
|
|
6693
|
-
if (
|
|
6694
|
-
|
|
7270
|
+
if (clack11.isCancel(confirmed) || !confirmed) {
|
|
7271
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6695
7272
|
process.exit(0);
|
|
6696
7273
|
}
|
|
6697
7274
|
}
|
|
@@ -6713,7 +7290,7 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6713
7290
|
"Generating upgrade preview",
|
|
6714
7291
|
async () => {
|
|
6715
7292
|
await ensurePulumiWorkDir();
|
|
6716
|
-
const stack = await
|
|
7293
|
+
const stack = await pulumi11.automation.LocalWorkspace.createOrSelectStack(
|
|
6717
7294
|
{
|
|
6718
7295
|
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
6719
7296
|
projectName: "wraps-email",
|
|
@@ -6763,11 +7340,19 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6763
7340
|
costEstimate: costComparison,
|
|
6764
7341
|
commandName: "wraps email upgrade"
|
|
6765
7342
|
});
|
|
6766
|
-
|
|
6767
|
-
|
|
7343
|
+
clack11.outro(
|
|
7344
|
+
pc12.green("Preview complete. Run without --preview to upgrade.")
|
|
6768
7345
|
);
|
|
7346
|
+
trackServiceUpgrade("email", {
|
|
7347
|
+
from_preset: metadata.services.email?.preset,
|
|
7348
|
+
to_preset: newPreset,
|
|
7349
|
+
preview: true,
|
|
7350
|
+
action: typeof upgradeAction === "string" ? upgradeAction : void 0,
|
|
7351
|
+
duration_ms: Date.now() - startTime
|
|
7352
|
+
});
|
|
6769
7353
|
return;
|
|
6770
7354
|
} catch (error) {
|
|
7355
|
+
trackError("PREVIEW_FAILED", "email:upgrade", { step: "preview" });
|
|
6771
7356
|
if (error.message?.includes("stack is currently locked")) {
|
|
6772
7357
|
throw errors.stackLocked();
|
|
6773
7358
|
}
|
|
@@ -6780,7 +7365,7 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6780
7365
|
"Updating Wraps infrastructure (this may take 2-3 minutes)",
|
|
6781
7366
|
async () => {
|
|
6782
7367
|
await ensurePulumiWorkDir();
|
|
6783
|
-
const stack = await
|
|
7368
|
+
const stack = await pulumi11.automation.LocalWorkspace.createOrSelectStack(
|
|
6784
7369
|
{
|
|
6785
7370
|
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
6786
7371
|
projectName: "wraps-email",
|
|
@@ -6841,14 +7426,22 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6841
7426
|
}
|
|
6842
7427
|
);
|
|
6843
7428
|
} catch (error) {
|
|
7429
|
+
trackServiceUpgrade("email", {
|
|
7430
|
+
from_preset: metadata.services.email?.preset,
|
|
7431
|
+
to_preset: newPreset,
|
|
7432
|
+
action: typeof upgradeAction === "string" ? upgradeAction : void 0,
|
|
7433
|
+
duration_ms: Date.now() - startTime
|
|
7434
|
+
});
|
|
6844
7435
|
if (error.message?.includes("stack is currently locked")) {
|
|
7436
|
+
trackError("STACK_LOCKED", "email:upgrade", { step: "deploy" });
|
|
6845
7437
|
throw errors.stackLocked();
|
|
6846
7438
|
}
|
|
7439
|
+
trackError("UPGRADE_FAILED", "email:upgrade", { step: "deploy" });
|
|
6847
7440
|
throw new Error(`Pulumi upgrade failed: ${error.message}`);
|
|
6848
7441
|
}
|
|
6849
7442
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
6850
|
-
const { findHostedZone:
|
|
6851
|
-
const hostedZone = await
|
|
7443
|
+
const { findHostedZone: findHostedZone3, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
7444
|
+
const hostedZone = await findHostedZone3(outputs.domain, region);
|
|
6852
7445
|
if (hostedZone) {
|
|
6853
7446
|
try {
|
|
6854
7447
|
progress.start("Creating DNS records in Route53");
|
|
@@ -6913,21 +7506,21 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6913
7506
|
httpsTrackingEnabled: outputs.httpsTrackingEnabled
|
|
6914
7507
|
});
|
|
6915
7508
|
console.log(`
|
|
6916
|
-
${
|
|
7509
|
+
${pc12.green("\u2713")} ${pc12.bold("Upgrade complete!")}
|
|
6917
7510
|
`);
|
|
6918
7511
|
if (upgradeAction === "preset" && newPreset) {
|
|
6919
7512
|
console.log(
|
|
6920
|
-
`Upgraded to ${
|
|
7513
|
+
`Upgraded to ${pc12.cyan(newPreset)} preset (${pc12.green(`${formatCost(newCostData.total.monthly)}/mo`)})
|
|
6921
7514
|
`
|
|
6922
7515
|
);
|
|
6923
7516
|
} else {
|
|
6924
7517
|
console.log(
|
|
6925
|
-
`Updated configuration (${
|
|
7518
|
+
`Updated configuration (${pc12.green(`${formatCost(newCostData.total.monthly)}/mo`)})
|
|
6926
7519
|
`
|
|
6927
7520
|
);
|
|
6928
7521
|
}
|
|
6929
7522
|
if (needsCertificateValidation) {
|
|
6930
|
-
console.log(
|
|
7523
|
+
console.log(pc12.bold("\u26A0\uFE0F HTTPS Tracking - Next Steps:\n"));
|
|
6931
7524
|
console.log(
|
|
6932
7525
|
" 1. Add the SSL certificate validation DNS record shown above to your DNS provider"
|
|
6933
7526
|
);
|
|
@@ -6935,28 +7528,46 @@ ${pc10.green("\u2713")} ${pc10.bold("Upgrade complete!")}
|
|
|
6935
7528
|
" 2. Wait for DNS propagation and certificate validation (5-30 minutes)"
|
|
6936
7529
|
);
|
|
6937
7530
|
console.log(
|
|
6938
|
-
` 3. Run ${
|
|
7531
|
+
` 3. Run ${pc12.cyan("wraps email upgrade")} again to complete CloudFront setup
|
|
6939
7532
|
`
|
|
6940
7533
|
);
|
|
6941
7534
|
console.log(
|
|
6942
|
-
|
|
7535
|
+
pc12.dim(
|
|
6943
7536
|
" Note: CloudFront distribution will be created once the certificate is validated.\n"
|
|
6944
7537
|
)
|
|
6945
7538
|
);
|
|
6946
7539
|
} else if (outputs.httpsTrackingEnabled && outputs.cloudFrontDomain) {
|
|
6947
7540
|
console.log(
|
|
6948
|
-
|
|
7541
|
+
pc12.green("\u2713") + " " + pc12.bold("HTTPS tracking is fully configured and ready to use!\n")
|
|
6949
7542
|
);
|
|
6950
7543
|
}
|
|
7544
|
+
const enabledFeatures = [];
|
|
7545
|
+
if (updatedConfig.tracking?.enabled) enabledFeatures.push("tracking");
|
|
7546
|
+
if (updatedConfig.suppressionList?.enabled)
|
|
7547
|
+
enabledFeatures.push("suppression_list");
|
|
7548
|
+
if (updatedConfig.eventTracking?.enabled)
|
|
7549
|
+
enabledFeatures.push("event_tracking");
|
|
7550
|
+
if (updatedConfig.eventTracking?.dynamoDBHistory)
|
|
7551
|
+
enabledFeatures.push("dynamodb_history");
|
|
7552
|
+
if (updatedConfig.dedicatedIp) enabledFeatures.push("dedicated_ip");
|
|
7553
|
+
if (updatedConfig.emailArchiving?.enabled)
|
|
7554
|
+
enabledFeatures.push("email_archiving");
|
|
7555
|
+
trackServiceUpgrade("email", {
|
|
7556
|
+
from_preset: metadata.services.email?.preset,
|
|
7557
|
+
to_preset: newPreset,
|
|
7558
|
+
added_features: enabledFeatures,
|
|
7559
|
+
action: typeof upgradeAction === "string" ? upgradeAction : void 0,
|
|
7560
|
+
duration_ms: Date.now() - startTime
|
|
7561
|
+
});
|
|
6951
7562
|
}
|
|
6952
7563
|
|
|
6953
7564
|
// src/commands/shared/dashboard.ts
|
|
6954
7565
|
init_esm_shims();
|
|
6955
|
-
import * as
|
|
6956
|
-
import * as
|
|
7566
|
+
import * as clack12 from "@clack/prompts";
|
|
7567
|
+
import * as pulumi12 from "@pulumi/pulumi";
|
|
6957
7568
|
import getPort from "get-port";
|
|
6958
7569
|
import open from "open";
|
|
6959
|
-
import
|
|
7570
|
+
import pc13 from "picocolors";
|
|
6960
7571
|
|
|
6961
7572
|
// src/console/server.ts
|
|
6962
7573
|
init_esm_shims();
|
|
@@ -8178,9 +8789,10 @@ async function startConsoleServer(config2) {
|
|
|
8178
8789
|
}
|
|
8179
8790
|
|
|
8180
8791
|
// src/commands/shared/dashboard.ts
|
|
8792
|
+
init_events();
|
|
8181
8793
|
init_aws();
|
|
8182
8794
|
async function dashboard(options) {
|
|
8183
|
-
|
|
8795
|
+
clack12.intro(pc13.bold("Wraps Dashboard"));
|
|
8184
8796
|
const progress = new DeploymentProgress();
|
|
8185
8797
|
const identity = await progress.execute(
|
|
8186
8798
|
"Validating AWS credentials",
|
|
@@ -8190,16 +8802,16 @@ async function dashboard(options) {
|
|
|
8190
8802
|
let stackOutputs = {};
|
|
8191
8803
|
try {
|
|
8192
8804
|
await ensurePulumiWorkDir();
|
|
8193
|
-
const stack = await
|
|
8805
|
+
const stack = await pulumi12.automation.LocalWorkspace.selectStack({
|
|
8194
8806
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
8195
8807
|
workDir: getPulumiWorkDir()
|
|
8196
8808
|
});
|
|
8197
8809
|
stackOutputs = await stack.outputs();
|
|
8198
8810
|
} catch (_error) {
|
|
8199
8811
|
progress.stop();
|
|
8200
|
-
|
|
8812
|
+
clack12.log.error("No Wraps infrastructure found");
|
|
8201
8813
|
console.log(
|
|
8202
|
-
`\\nRun ${
|
|
8814
|
+
`\\nRun ${pc13.cyan("wraps email init")} to deploy infrastructure first.\\n`
|
|
8203
8815
|
);
|
|
8204
8816
|
process.exit(1);
|
|
8205
8817
|
}
|
|
@@ -8208,9 +8820,9 @@ async function dashboard(options) {
|
|
|
8208
8820
|
const archivingEnabled = stackOutputs.archivingEnabled?.value ?? false;
|
|
8209
8821
|
const port = options.port || await getPort({ port: [5555, 5556, 5557, 5558, 5559] });
|
|
8210
8822
|
progress.stop();
|
|
8211
|
-
|
|
8823
|
+
clack12.log.success("Starting dashboard server...");
|
|
8212
8824
|
console.log(
|
|
8213
|
-
`${
|
|
8825
|
+
`${pc13.dim("Using current AWS credentials (no role assumption)")}\\n`
|
|
8214
8826
|
);
|
|
8215
8827
|
const { url } = await startConsoleServer({
|
|
8216
8828
|
port,
|
|
@@ -8223,11 +8835,16 @@ async function dashboard(options) {
|
|
|
8223
8835
|
archiveArn,
|
|
8224
8836
|
archivingEnabled
|
|
8225
8837
|
});
|
|
8226
|
-
console.log(`\\n${
|
|
8227
|
-
console.log(`${
|
|
8838
|
+
console.log(`\\n${pc13.bold("Dashboard:")} ${pc13.cyan(url)}`);
|
|
8839
|
+
console.log(`${pc13.dim("Press Ctrl+C to stop")}\\n`);
|
|
8228
8840
|
if (!options.noOpen) {
|
|
8229
8841
|
await open(url);
|
|
8230
8842
|
}
|
|
8843
|
+
trackCommand("console", {
|
|
8844
|
+
success: true,
|
|
8845
|
+
port,
|
|
8846
|
+
no_open: options.noOpen ?? false
|
|
8847
|
+
});
|
|
8231
8848
|
await new Promise(() => {
|
|
8232
8849
|
});
|
|
8233
8850
|
}
|
|
@@ -8235,246 +8852,203 @@ async function dashboard(options) {
|
|
|
8235
8852
|
// src/commands/shared/destroy.ts
|
|
8236
8853
|
init_esm_shims();
|
|
8237
8854
|
init_aws();
|
|
8238
|
-
import * as
|
|
8239
|
-
import
|
|
8240
|
-
import pc12 from "picocolors";
|
|
8855
|
+
import * as clack13 from "@clack/prompts";
|
|
8856
|
+
import pc14 from "picocolors";
|
|
8241
8857
|
async function destroy(options) {
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8858
|
+
clack13.intro(pc14.bold("Wraps Infrastructure Teardown"));
|
|
8859
|
+
const spinner3 = clack13.spinner();
|
|
8860
|
+
spinner3.start("Validating AWS credentials");
|
|
8861
|
+
let identity;
|
|
8862
|
+
try {
|
|
8863
|
+
identity = await validateAWSCredentials();
|
|
8864
|
+
spinner3.stop("AWS credentials validated");
|
|
8865
|
+
} catch (error) {
|
|
8866
|
+
spinner3.stop("AWS credentials validation failed");
|
|
8867
|
+
throw error;
|
|
8868
|
+
}
|
|
8252
8869
|
const region = await getAWSRegion();
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
),
|
|
8258
|
-
initialValue: false
|
|
8259
|
-
});
|
|
8260
|
-
if (clack11.isCancel(confirmed) || !confirmed) {
|
|
8261
|
-
clack11.cancel("Destruction cancelled.");
|
|
8262
|
-
process.exit(0);
|
|
8263
|
-
}
|
|
8870
|
+
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
8871
|
+
const deployedServices = [];
|
|
8872
|
+
if (metadata?.services?.email) {
|
|
8873
|
+
deployedServices.push("email");
|
|
8264
8874
|
}
|
|
8265
|
-
if (
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
throw new Error("No Wraps infrastructure found to preview");
|
|
8280
|
-
}
|
|
8281
|
-
const result = await stack.preview({ diff: true });
|
|
8282
|
-
return result;
|
|
8283
|
-
}
|
|
8284
|
-
);
|
|
8285
|
-
displayPreview({
|
|
8286
|
-
changeSummary: previewResult.changeSummary,
|
|
8287
|
-
costEstimate: "Monthly cost after destruction: $0.00",
|
|
8288
|
-
commandName: "wraps destroy"
|
|
8289
|
-
});
|
|
8290
|
-
clack11.outro(
|
|
8291
|
-
pc12.green("Preview complete. Run without --preview to destroy.")
|
|
8292
|
-
);
|
|
8875
|
+
if (deployedServices.length === 0) {
|
|
8876
|
+
clack13.log.warn("No Wraps services found in this region");
|
|
8877
|
+
console.log(
|
|
8878
|
+
`
|
|
8879
|
+
Run ${pc14.cyan("wraps email init")} to deploy infrastructure.
|
|
8880
|
+
`
|
|
8881
|
+
);
|
|
8882
|
+
process.exit(0);
|
|
8883
|
+
}
|
|
8884
|
+
if (deployedServices.length === 1) {
|
|
8885
|
+
const service = deployedServices[0];
|
|
8886
|
+
clack13.log.info(`Found ${pc14.cyan(service)} service deployed`);
|
|
8887
|
+
if (service === "email") {
|
|
8888
|
+
await emailDestroy(options);
|
|
8293
8889
|
return;
|
|
8294
|
-
} catch (error) {
|
|
8295
|
-
progress.stop();
|
|
8296
|
-
if (error.message.includes("No Wraps infrastructure found")) {
|
|
8297
|
-
clack11.log.warn("No Wraps infrastructure found to preview");
|
|
8298
|
-
process.exit(0);
|
|
8299
|
-
}
|
|
8300
|
-
throw new Error(`Preview failed: ${error.message}`);
|
|
8301
8890
|
}
|
|
8302
8891
|
}
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
} catch (_error) {
|
|
8316
|
-
throw new Error("No Wraps infrastructure found to destroy");
|
|
8317
|
-
}
|
|
8318
|
-
await stack.destroy({ onOutput: () => {
|
|
8319
|
-
} });
|
|
8320
|
-
await stack.workspace.removeStack(stackName);
|
|
8892
|
+
const serviceToDestroy = await clack13.select({
|
|
8893
|
+
message: "Which service would you like to destroy?",
|
|
8894
|
+
options: [
|
|
8895
|
+
...deployedServices.map((s) => ({
|
|
8896
|
+
value: s,
|
|
8897
|
+
label: s.charAt(0).toUpperCase() + s.slice(1),
|
|
8898
|
+
hint: s === "email" ? "AWS SES email infrastructure" : void 0
|
|
8899
|
+
})),
|
|
8900
|
+
{
|
|
8901
|
+
value: "all",
|
|
8902
|
+
label: "All services",
|
|
8903
|
+
hint: "Destroy all Wraps infrastructure"
|
|
8321
8904
|
}
|
|
8322
|
-
|
|
8323
|
-
}
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8905
|
+
]
|
|
8906
|
+
});
|
|
8907
|
+
if (clack13.isCancel(serviceToDestroy)) {
|
|
8908
|
+
clack13.cancel("Operation cancelled.");
|
|
8909
|
+
process.exit(0);
|
|
8910
|
+
}
|
|
8911
|
+
if (serviceToDestroy === "email" || serviceToDestroy === "all") {
|
|
8912
|
+
if (deployedServices.includes("email")) {
|
|
8913
|
+
await emailDestroy(options);
|
|
8329
8914
|
}
|
|
8330
|
-
clack11.log.error("Infrastructure destruction failed");
|
|
8331
|
-
throw error;
|
|
8332
8915
|
}
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
console.log(
|
|
8337
|
-
`
|
|
8338
|
-
Run ${pc12.cyan("wraps email init")} to deploy infrastructure again.
|
|
8339
|
-
`
|
|
8340
|
-
);
|
|
8916
|
+
if (serviceToDestroy === "all") {
|
|
8917
|
+
clack13.outro(pc14.green("All Wraps infrastructure has been removed"));
|
|
8918
|
+
}
|
|
8341
8919
|
}
|
|
8342
8920
|
|
|
8343
8921
|
// src/commands/shared/status.ts
|
|
8344
8922
|
init_esm_shims();
|
|
8923
|
+
init_events();
|
|
8345
8924
|
init_aws();
|
|
8346
|
-
import * as
|
|
8347
|
-
import * as
|
|
8348
|
-
import
|
|
8925
|
+
import * as clack14 from "@clack/prompts";
|
|
8926
|
+
import * as pulumi13 from "@pulumi/pulumi";
|
|
8927
|
+
import pc15 from "picocolors";
|
|
8349
8928
|
async function status(_options) {
|
|
8929
|
+
const startTime = Date.now();
|
|
8350
8930
|
const progress = new DeploymentProgress();
|
|
8931
|
+
clack14.intro(pc15.bold("Wraps Infrastructure Status"));
|
|
8351
8932
|
const identity = await progress.execute(
|
|
8352
8933
|
"Loading infrastructure status",
|
|
8353
8934
|
async () => validateAWSCredentials()
|
|
8354
8935
|
);
|
|
8936
|
+
progress.info(`AWS Account: ${pc15.cyan(identity.accountId)}`);
|
|
8355
8937
|
const region = await getAWSRegion();
|
|
8356
|
-
|
|
8938
|
+
progress.info(`Region: ${pc15.cyan(region)}`);
|
|
8939
|
+
const services = [];
|
|
8357
8940
|
try {
|
|
8358
8941
|
await ensurePulumiWorkDir();
|
|
8359
|
-
const stack = await
|
|
8942
|
+
const stack = await pulumi13.automation.LocalWorkspace.selectStack({
|
|
8360
8943
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
8361
8944
|
workDir: getPulumiWorkDir()
|
|
8362
8945
|
});
|
|
8363
|
-
|
|
8946
|
+
const outputs = await stack.outputs();
|
|
8947
|
+
if (outputs.roleArn?.value) {
|
|
8948
|
+
const domainCount = outputs.domains?.value?.length || 0;
|
|
8949
|
+
services.push({
|
|
8950
|
+
name: "Email",
|
|
8951
|
+
status: "deployed",
|
|
8952
|
+
details: domainCount > 0 ? `${domainCount} domain(s)` : void 0
|
|
8953
|
+
});
|
|
8954
|
+
} else {
|
|
8955
|
+
services.push({ name: "Email", status: "not_deployed" });
|
|
8956
|
+
}
|
|
8364
8957
|
} catch (_error) {
|
|
8365
|
-
|
|
8366
|
-
|
|
8958
|
+
services.push({ name: "Email", status: "not_deployed" });
|
|
8959
|
+
}
|
|
8960
|
+
progress.stop();
|
|
8961
|
+
console.log();
|
|
8962
|
+
clack14.note(
|
|
8963
|
+
services.map((s) => {
|
|
8964
|
+
if (s.status === "deployed") {
|
|
8965
|
+
const details = s.details ? pc15.dim(` (${s.details})`) : "";
|
|
8966
|
+
return ` ${pc15.green("\u2713")} ${s.name}${details}`;
|
|
8967
|
+
}
|
|
8968
|
+
return ` ${pc15.dim("\u25CB")} ${s.name} ${pc15.dim("(not deployed)")}`;
|
|
8969
|
+
}).join("\n"),
|
|
8970
|
+
"Services"
|
|
8971
|
+
);
|
|
8972
|
+
const hasDeployedServices = services.some((s) => s.status === "deployed");
|
|
8973
|
+
if (hasDeployedServices) {
|
|
8974
|
+
console.log(`
|
|
8975
|
+
${pc15.bold("Details:")}`);
|
|
8976
|
+
if (services.find((s) => s.name === "Email")?.status === "deployed") {
|
|
8977
|
+
console.log(
|
|
8978
|
+
` ${pc15.dim("Email:")} ${pc15.cyan("wraps email status")}`
|
|
8979
|
+
);
|
|
8980
|
+
}
|
|
8981
|
+
} else {
|
|
8982
|
+
console.log(`
|
|
8983
|
+
${pc15.bold("Get started:")}`);
|
|
8367
8984
|
console.log(
|
|
8368
|
-
`
|
|
8369
|
-
Run ${pc13.cyan("wraps email init")} to deploy infrastructure.
|
|
8370
|
-
`
|
|
8985
|
+
` ${pc15.dim("Deploy email:")} ${pc15.cyan("wraps email init")}`
|
|
8371
8986
|
);
|
|
8372
|
-
process.exit(1);
|
|
8373
8987
|
}
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
);
|
|
8383
|
-
return {
|
|
8384
|
-
domain: d.domain,
|
|
8385
|
-
status: d.verified ? "verified" : "pending",
|
|
8386
|
-
dkimTokens: identity2.DkimAttributes?.Tokens || [],
|
|
8387
|
-
mailFromDomain: identity2.MailFromAttributes?.MailFromDomain,
|
|
8388
|
-
mailFromStatus: identity2.MailFromAttributes?.MailFromDomainStatus
|
|
8389
|
-
};
|
|
8390
|
-
} catch (_error) {
|
|
8391
|
-
return {
|
|
8392
|
-
domain: d.domain,
|
|
8393
|
-
status: d.verified ? "verified" : "pending",
|
|
8394
|
-
dkimTokens: void 0,
|
|
8395
|
-
mailFromDomain: void 0,
|
|
8396
|
-
mailFromStatus: void 0
|
|
8397
|
-
};
|
|
8398
|
-
}
|
|
8399
|
-
})
|
|
8400
|
-
);
|
|
8401
|
-
const integrationLevel = stackOutputs.configSetName ? "enhanced" : "dashboard-only";
|
|
8402
|
-
progress.stop();
|
|
8403
|
-
displayStatus({
|
|
8404
|
-
integrationLevel,
|
|
8405
|
-
region,
|
|
8406
|
-
domains: domainsWithTokens,
|
|
8407
|
-
resources: {
|
|
8408
|
-
roleArn: stackOutputs.roleArn?.value,
|
|
8409
|
-
configSetName: stackOutputs.configSetName?.value,
|
|
8410
|
-
tableName: stackOutputs.tableName?.value,
|
|
8411
|
-
lambdaFunctions: stackOutputs.lambdaFunctions?.value?.length || 0,
|
|
8412
|
-
snsTopics: integrationLevel === "enhanced" ? 1 : 0,
|
|
8413
|
-
archiveArn: stackOutputs.archiveArn?.value,
|
|
8414
|
-
archivingEnabled: stackOutputs.archivingEnabled?.value,
|
|
8415
|
-
archiveRetention: stackOutputs.archiveRetention?.value
|
|
8416
|
-
},
|
|
8417
|
-
tracking: stackOutputs.customTrackingDomain?.value ? {
|
|
8418
|
-
customTrackingDomain: stackOutputs.customTrackingDomain?.value,
|
|
8419
|
-
httpsEnabled: stackOutputs.httpsTrackingEnabled?.value,
|
|
8420
|
-
cloudFrontDomain: stackOutputs.cloudFrontDomain?.value
|
|
8421
|
-
} : void 0
|
|
8988
|
+
console.log(`
|
|
8989
|
+
${pc15.bold("Dashboard:")} ${pc15.blue("https://app.wraps.dev")}`);
|
|
8990
|
+
console.log(`${pc15.bold("Docs:")} ${pc15.blue("https://wraps.dev/docs")}
|
|
8991
|
+
`);
|
|
8992
|
+
trackCommand("status", {
|
|
8993
|
+
success: true,
|
|
8994
|
+
services_deployed: services.filter((s) => s.status === "deployed").length,
|
|
8995
|
+
duration_ms: Date.now() - startTime
|
|
8422
8996
|
});
|
|
8423
8997
|
}
|
|
8424
8998
|
|
|
8425
8999
|
// src/commands/telemetry.ts
|
|
8426
9000
|
init_esm_shims();
|
|
8427
9001
|
init_client();
|
|
8428
|
-
import * as
|
|
8429
|
-
import
|
|
9002
|
+
import * as clack15 from "@clack/prompts";
|
|
9003
|
+
import pc16 from "picocolors";
|
|
8430
9004
|
async function telemetryEnable() {
|
|
8431
9005
|
const client = getTelemetryClient();
|
|
8432
9006
|
client.enable();
|
|
8433
|
-
|
|
8434
|
-
console.log(` Config: ${
|
|
9007
|
+
clack15.log.success(pc16.green("Telemetry enabled"));
|
|
9008
|
+
console.log(` Config: ${pc16.dim(client.getConfigPath())}`);
|
|
8435
9009
|
console.log(`
|
|
8436
|
-
${
|
|
9010
|
+
${pc16.dim("Thank you for helping improve Wraps!")}
|
|
8437
9011
|
`);
|
|
8438
9012
|
}
|
|
8439
9013
|
async function telemetryDisable() {
|
|
8440
9014
|
const client = getTelemetryClient();
|
|
8441
9015
|
client.disable();
|
|
8442
|
-
|
|
8443
|
-
console.log(` Config: ${
|
|
9016
|
+
clack15.log.success(pc16.green("Telemetry disabled"));
|
|
9017
|
+
console.log(` Config: ${pc16.dim(client.getConfigPath())}`);
|
|
8444
9018
|
console.log(
|
|
8445
9019
|
`
|
|
8446
|
-
${
|
|
9020
|
+
${pc16.dim("You can re-enable with:")} wraps telemetry enable
|
|
8447
9021
|
`
|
|
8448
9022
|
);
|
|
8449
9023
|
}
|
|
8450
9024
|
async function telemetryStatus() {
|
|
8451
9025
|
const client = getTelemetryClient();
|
|
8452
|
-
|
|
8453
|
-
const status2 = client.isEnabled() ?
|
|
9026
|
+
clack15.intro(pc16.bold("Telemetry Status"));
|
|
9027
|
+
const status2 = client.isEnabled() ? pc16.green("Enabled") : pc16.red("Disabled");
|
|
8454
9028
|
console.log();
|
|
8455
|
-
console.log(` ${
|
|
8456
|
-
console.log(` ${
|
|
9029
|
+
console.log(` ${pc16.bold("Status:")} ${status2}`);
|
|
9030
|
+
console.log(` ${pc16.bold("Config file:")} ${pc16.dim(client.getConfigPath())}`);
|
|
8457
9031
|
if (client.isEnabled()) {
|
|
8458
9032
|
console.log();
|
|
8459
|
-
console.log(
|
|
8460
|
-
console.log(` ${
|
|
9033
|
+
console.log(pc16.bold(" How to opt-out:"));
|
|
9034
|
+
console.log(` ${pc16.cyan("wraps telemetry disable")}`);
|
|
8461
9035
|
console.log(
|
|
8462
|
-
` ${
|
|
9036
|
+
` ${pc16.dim("Or set:")} ${pc16.cyan("WRAPS_TELEMETRY_DISABLED=1")}`
|
|
8463
9037
|
);
|
|
8464
|
-
console.log(` ${
|
|
9038
|
+
console.log(` ${pc16.dim("Or set:")} ${pc16.cyan("DO_NOT_TRACK=1")}`);
|
|
8465
9039
|
} else {
|
|
8466
9040
|
console.log();
|
|
8467
|
-
console.log(
|
|
8468
|
-
console.log(` ${
|
|
9041
|
+
console.log(pc16.bold(" How to opt-in:"));
|
|
9042
|
+
console.log(` ${pc16.cyan("wraps telemetry enable")}`);
|
|
8469
9043
|
}
|
|
8470
9044
|
console.log();
|
|
8471
|
-
console.log(
|
|
9045
|
+
console.log(pc16.bold(" Debug mode:"));
|
|
8472
9046
|
console.log(
|
|
8473
|
-
` ${
|
|
9047
|
+
` ${pc16.dim("See what would be sent:")} ${pc16.cyan("WRAPS_TELEMETRY_DEBUG=1 wraps <command>")}`
|
|
8474
9048
|
);
|
|
8475
9049
|
console.log();
|
|
8476
9050
|
console.log(
|
|
8477
|
-
` ${
|
|
9051
|
+
` ${pc16.dim("Learn more:")} ${pc16.cyan("https://wraps.dev/docs/telemetry")}`
|
|
8478
9052
|
);
|
|
8479
9053
|
console.log();
|
|
8480
9054
|
}
|
|
@@ -8492,19 +9066,41 @@ function printCompletionScript() {
|
|
|
8492
9066
|
console.log("# ========================\n");
|
|
8493
9067
|
console.log("# Tab completion will be available in a future release.\n");
|
|
8494
9068
|
console.log("# For now, here are the available commands:\n");
|
|
8495
|
-
console.log("# Commands:");
|
|
9069
|
+
console.log("# Email Commands:");
|
|
8496
9070
|
console.log(
|
|
8497
9071
|
"# wraps email init [--provider vercel|aws|railway|other] [--region <region>] [--domain <domain>]"
|
|
8498
9072
|
);
|
|
8499
|
-
console.log("# wraps
|
|
8500
|
-
console.log("# wraps
|
|
9073
|
+
console.log("# wraps email connect [--region <region>]");
|
|
9074
|
+
console.log("# wraps email status [--account <account-id>]");
|
|
9075
|
+
console.log("# wraps email verify --domain <domain>");
|
|
9076
|
+
console.log("# wraps email sync");
|
|
9077
|
+
console.log("# wraps email upgrade");
|
|
9078
|
+
console.log("# wraps email restore [--region <region>] [--force]");
|
|
9079
|
+
console.log("# wraps email destroy [--force] [--preview]");
|
|
9080
|
+
console.log("# wraps email domains add --domain <domain>");
|
|
9081
|
+
console.log("# wraps email domains list");
|
|
9082
|
+
console.log("# wraps email domains verify --domain <domain>");
|
|
9083
|
+
console.log("# wraps email domains get-dkim --domain <domain>");
|
|
9084
|
+
console.log("# wraps email domains remove --domain <domain> [--force]\n");
|
|
9085
|
+
console.log("# Global Commands:");
|
|
9086
|
+
console.log("# wraps status");
|
|
9087
|
+
console.log("# wraps destroy [--force] [--preview]");
|
|
9088
|
+
console.log("# wraps console [--port <port>] [--no-open]");
|
|
9089
|
+
console.log("# wraps completion");
|
|
9090
|
+
console.log("# wraps telemetry [enable|disable|status]\n");
|
|
9091
|
+
console.log("# Dashboard Commands:");
|
|
9092
|
+
console.log("# wraps dashboard update-role [--region <region>] [--force]\n");
|
|
8501
9093
|
console.log("# Flags:");
|
|
8502
|
-
console.log("# --provider : vercel, aws, railway, other");
|
|
9094
|
+
console.log("# -p, --provider : vercel, aws, railway, other");
|
|
8503
9095
|
console.log(
|
|
8504
|
-
"# --region : us-east-1, us-east-2, us-west-1, us-west-2, eu-west-1, eu-west-2, etc."
|
|
9096
|
+
"# -r, --region : us-east-1, us-east-2, us-west-1, us-west-2, eu-west-1, eu-west-2, etc."
|
|
8505
9097
|
);
|
|
8506
|
-
console.log("# --domain : Your domain name (e.g., myapp.com)");
|
|
8507
|
-
console.log("# --account
|
|
9098
|
+
console.log("# -d, --domain : Your domain name (e.g., myapp.com)");
|
|
9099
|
+
console.log("# --account : AWS account ID or alias");
|
|
9100
|
+
console.log("# --preset : starter, production, enterprise, custom");
|
|
9101
|
+
console.log("# -y, --yes : Skip confirmation prompts");
|
|
9102
|
+
console.log("# -f, --force : Force destructive operations");
|
|
9103
|
+
console.log("# --preview : Preview changes without deploying\n");
|
|
8508
9104
|
}
|
|
8509
9105
|
|
|
8510
9106
|
// src/cli.ts
|
|
@@ -8521,62 +9117,66 @@ function showVersion() {
|
|
|
8521
9117
|
process.exit(0);
|
|
8522
9118
|
}
|
|
8523
9119
|
function showHelp() {
|
|
8524
|
-
|
|
9120
|
+
clack16.intro(pc17.bold(`WRAPS CLI v${VERSION}`));
|
|
8525
9121
|
console.log("Deploy AWS infrastructure to your account\n");
|
|
8526
9122
|
console.log("Usage: wraps [service] <command> [options]\n");
|
|
8527
9123
|
console.log("Services:");
|
|
8528
|
-
console.log(` ${
|
|
9124
|
+
console.log(` ${pc17.cyan("email")} Email infrastructure (AWS SES)
|
|
9125
|
+
`);
|
|
9126
|
+
console.log("Email Commands:");
|
|
8529
9127
|
console.log(
|
|
8530
|
-
` ${
|
|
8531
|
-
`
|
|
9128
|
+
` ${pc17.cyan("email init")} Deploy new email infrastructure`
|
|
8532
9129
|
);
|
|
8533
|
-
console.log("Email Commands:");
|
|
8534
9130
|
console.log(
|
|
8535
|
-
` ${
|
|
9131
|
+
` ${pc17.cyan("email connect")} Connect to existing AWS SES`
|
|
8536
9132
|
);
|
|
9133
|
+
console.log(` ${pc17.cyan("email status")} Show email infrastructure details`);
|
|
9134
|
+
console.log(` ${pc17.cyan("email verify")} Verify domain DNS records`);
|
|
8537
9135
|
console.log(
|
|
8538
|
-
` ${
|
|
9136
|
+
` ${pc17.cyan("email sync")} Apply CLI updates to infrastructure`
|
|
8539
9137
|
);
|
|
8540
|
-
console.log(` ${
|
|
9138
|
+
console.log(` ${pc17.cyan("email upgrade")} Add features`);
|
|
8541
9139
|
console.log(
|
|
8542
|
-
` ${
|
|
9140
|
+
` ${pc17.cyan("email restore")} Restore original configuration`
|
|
8543
9141
|
);
|
|
8544
|
-
console.log(` ${pc15.cyan("email upgrade")} Add features`);
|
|
8545
9142
|
console.log(
|
|
8546
|
-
` ${
|
|
8547
|
-
`
|
|
9143
|
+
` ${pc17.cyan("email destroy")} Remove email infrastructure`
|
|
8548
9144
|
);
|
|
9145
|
+
console.log(` ${pc17.cyan("email domains add")} Add a domain to SES`);
|
|
9146
|
+
console.log(` ${pc17.cyan("email domains list")} List all domains`);
|
|
9147
|
+
console.log(` ${pc17.cyan("email domains remove")} Remove a domain
|
|
9148
|
+
`);
|
|
8549
9149
|
console.log("Console & Dashboard:");
|
|
8550
|
-
console.log(` ${
|
|
9150
|
+
console.log(` ${pc17.cyan("console")} Start local web console`);
|
|
8551
9151
|
console.log(
|
|
8552
|
-
` ${
|
|
9152
|
+
` ${pc17.cyan("dashboard update-role")} Update hosted dashboard IAM permissions
|
|
8553
9153
|
`
|
|
8554
9154
|
);
|
|
8555
9155
|
console.log("Global Commands:");
|
|
8556
|
-
console.log(` ${
|
|
8557
|
-
console.log(` ${
|
|
8558
|
-
console.log(` ${
|
|
9156
|
+
console.log(` ${pc17.cyan("status")} Show overview of all services`);
|
|
9157
|
+
console.log(` ${pc17.cyan("destroy")} Remove deployed infrastructure`);
|
|
9158
|
+
console.log(` ${pc17.cyan("completion")} Generate shell completion script`);
|
|
8559
9159
|
console.log(
|
|
8560
|
-
` ${
|
|
9160
|
+
` ${pc17.cyan("telemetry")} Manage anonymous telemetry settings
|
|
8561
9161
|
`
|
|
8562
9162
|
);
|
|
8563
9163
|
console.log("Options:");
|
|
8564
9164
|
console.log(
|
|
8565
|
-
` ${
|
|
9165
|
+
` ${pc17.dim("-p, --provider")} Hosting provider (vercel, aws, railway, other)`
|
|
8566
9166
|
);
|
|
8567
|
-
console.log(` ${
|
|
8568
|
-
console.log(` ${
|
|
8569
|
-
console.log(` ${
|
|
8570
|
-
console.log(` ${
|
|
8571
|
-
console.log(` ${
|
|
8572
|
-
console.log(` ${
|
|
9167
|
+
console.log(` ${pc17.dim("-r, --region")} AWS region`);
|
|
9168
|
+
console.log(` ${pc17.dim("-d, --domain")} Domain name`);
|
|
9169
|
+
console.log(` ${pc17.dim("--account")} AWS account ID or alias`);
|
|
9170
|
+
console.log(` ${pc17.dim("--preset")} Configuration preset`);
|
|
9171
|
+
console.log(` ${pc17.dim("-y, --yes")} Skip confirmation prompts`);
|
|
9172
|
+
console.log(` ${pc17.dim("-f, --force")} Force destructive operations`);
|
|
8573
9173
|
console.log(
|
|
8574
|
-
` ${
|
|
9174
|
+
` ${pc17.dim("--preview")} Preview changes without deploying`
|
|
8575
9175
|
);
|
|
8576
|
-
console.log(` ${
|
|
9176
|
+
console.log(` ${pc17.dim("-v, --version")} Show version number
|
|
8577
9177
|
`);
|
|
8578
9178
|
console.log(
|
|
8579
|
-
`Run ${
|
|
9179
|
+
`Run ${pc17.cyan("wraps <service> <command> --help")} for more information.
|
|
8580
9180
|
`
|
|
8581
9181
|
);
|
|
8582
9182
|
process.exit(0);
|
|
@@ -8643,37 +9243,9 @@ var flags = args.parse(process.argv);
|
|
|
8643
9243
|
var [primaryCommand, subCommand] = args.sub;
|
|
8644
9244
|
if (!primaryCommand) {
|
|
8645
9245
|
async function selectService() {
|
|
8646
|
-
|
|
8647
|
-
console.log("Welcome! Let's get started deploying your infrastructure.\n");
|
|
8648
|
-
const
|
|
8649
|
-
message: "Which service would you like to set up?",
|
|
8650
|
-
options: [
|
|
8651
|
-
{
|
|
8652
|
-
value: "email",
|
|
8653
|
-
label: "Email",
|
|
8654
|
-
hint: "AWS SES email infrastructure"
|
|
8655
|
-
},
|
|
8656
|
-
{
|
|
8657
|
-
value: "sms",
|
|
8658
|
-
label: "SMS",
|
|
8659
|
-
hint: "Coming soon - AWS End User Messaging"
|
|
8660
|
-
}
|
|
8661
|
-
]
|
|
8662
|
-
});
|
|
8663
|
-
if (clack14.isCancel(service)) {
|
|
8664
|
-
clack14.cancel("Operation cancelled.");
|
|
8665
|
-
process.exit(0);
|
|
8666
|
-
}
|
|
8667
|
-
if (service === "sms") {
|
|
8668
|
-
clack14.log.warn("SMS infrastructure is coming soon!");
|
|
8669
|
-
console.log(
|
|
8670
|
-
`
|
|
8671
|
-
Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-team/wraps")}
|
|
8672
|
-
`
|
|
8673
|
-
);
|
|
8674
|
-
process.exit(0);
|
|
8675
|
-
}
|
|
8676
|
-
const action = await clack14.select({
|
|
9246
|
+
clack16.intro(pc17.bold(`WRAPS CLI v${VERSION}`));
|
|
9247
|
+
console.log("Welcome! Let's get started deploying your email infrastructure.\n");
|
|
9248
|
+
const action = await clack16.select({
|
|
8677
9249
|
message: "What would you like to do?",
|
|
8678
9250
|
options: [
|
|
8679
9251
|
{
|
|
@@ -8688,8 +9260,8 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
|
|
|
8688
9260
|
}
|
|
8689
9261
|
]
|
|
8690
9262
|
});
|
|
8691
|
-
if (
|
|
8692
|
-
|
|
9263
|
+
if (clack16.isCancel(action)) {
|
|
9264
|
+
clack16.cancel("Operation cancelled.");
|
|
8693
9265
|
process.exit(0);
|
|
8694
9266
|
}
|
|
8695
9267
|
if (action === "init") {
|
|
@@ -8718,20 +9290,20 @@ async function run() {
|
|
|
8718
9290
|
const telemetry = getTelemetryClient();
|
|
8719
9291
|
if (telemetry.shouldShowNotification()) {
|
|
8720
9292
|
console.log();
|
|
8721
|
-
|
|
9293
|
+
clack16.log.info(pc17.bold("Anonymous Telemetry"));
|
|
8722
9294
|
console.log(
|
|
8723
|
-
` Wraps collects ${
|
|
9295
|
+
` Wraps collects ${pc17.cyan("anonymous usage data")} to improve the CLI.`
|
|
8724
9296
|
);
|
|
8725
9297
|
console.log(
|
|
8726
|
-
` We ${
|
|
9298
|
+
` We ${pc17.bold("never")} collect: domains, AWS credentials, email content, or PII.`
|
|
8727
9299
|
);
|
|
8728
9300
|
console.log(
|
|
8729
|
-
` We ${
|
|
9301
|
+
` We ${pc17.bold("only")} collect: command names, success/failure, CLI version, OS.`
|
|
8730
9302
|
);
|
|
8731
9303
|
console.log();
|
|
8732
|
-
console.log(` Opt-out anytime: ${
|
|
8733
|
-
console.log(` Or set: ${
|
|
8734
|
-
console.log(` Learn more: ${
|
|
9304
|
+
console.log(` Opt-out anytime: ${pc17.cyan("wraps telemetry disable")}`);
|
|
9305
|
+
console.log(` Or set: ${pc17.cyan("WRAPS_TELEMETRY_DISABLED=1")}`);
|
|
9306
|
+
console.log(` Learn more: ${pc17.cyan("https://wraps.dev/docs/telemetry")}`);
|
|
8735
9307
|
console.log();
|
|
8736
9308
|
telemetry.markNotificationShown();
|
|
8737
9309
|
}
|
|
@@ -8778,15 +9350,33 @@ async function run() {
|
|
|
8778
9350
|
preview: flags.preview
|
|
8779
9351
|
});
|
|
8780
9352
|
break;
|
|
9353
|
+
case "status":
|
|
9354
|
+
await emailStatus({
|
|
9355
|
+
account: flags.account
|
|
9356
|
+
});
|
|
9357
|
+
break;
|
|
9358
|
+
case "verify": {
|
|
9359
|
+
if (!flags.domain) {
|
|
9360
|
+
clack16.log.error("--domain flag is required");
|
|
9361
|
+
console.log(
|
|
9362
|
+
`
|
|
9363
|
+
Usage: ${pc17.cyan("wraps email verify --domain yourapp.com")}
|
|
9364
|
+
`
|
|
9365
|
+
);
|
|
9366
|
+
process.exit(1);
|
|
9367
|
+
}
|
|
9368
|
+
await verifyDomain({ domain: flags.domain });
|
|
9369
|
+
break;
|
|
9370
|
+
}
|
|
8781
9371
|
case "domains": {
|
|
8782
9372
|
const domainsSubCommand = args.sub[2];
|
|
8783
9373
|
switch (domainsSubCommand) {
|
|
8784
9374
|
case "add": {
|
|
8785
9375
|
if (!flags.domain) {
|
|
8786
|
-
|
|
9376
|
+
clack16.log.error("--domain flag is required");
|
|
8787
9377
|
console.log(
|
|
8788
9378
|
`
|
|
8789
|
-
Usage: ${
|
|
9379
|
+
Usage: ${pc17.cyan("wraps email domains add --domain yourapp.com")}
|
|
8790
9380
|
`
|
|
8791
9381
|
);
|
|
8792
9382
|
process.exit(1);
|
|
@@ -8799,10 +9389,10 @@ Usage: ${pc15.cyan("wraps email domains add --domain yourapp.com")}
|
|
|
8799
9389
|
break;
|
|
8800
9390
|
case "verify": {
|
|
8801
9391
|
if (!flags.domain) {
|
|
8802
|
-
|
|
9392
|
+
clack16.log.error("--domain flag is required");
|
|
8803
9393
|
console.log(
|
|
8804
9394
|
`
|
|
8805
|
-
Usage: ${
|
|
9395
|
+
Usage: ${pc17.cyan("wraps email domains verify --domain yourapp.com")}
|
|
8806
9396
|
`
|
|
8807
9397
|
);
|
|
8808
9398
|
process.exit(1);
|
|
@@ -8812,10 +9402,10 @@ Usage: ${pc15.cyan("wraps email domains verify --domain yourapp.com")}
|
|
|
8812
9402
|
}
|
|
8813
9403
|
case "get-dkim": {
|
|
8814
9404
|
if (!flags.domain) {
|
|
8815
|
-
|
|
9405
|
+
clack16.log.error("--domain flag is required");
|
|
8816
9406
|
console.log(
|
|
8817
9407
|
`
|
|
8818
|
-
Usage: ${
|
|
9408
|
+
Usage: ${pc17.cyan("wraps email domains get-dkim --domain yourapp.com")}
|
|
8819
9409
|
`
|
|
8820
9410
|
);
|
|
8821
9411
|
process.exit(1);
|
|
@@ -8825,10 +9415,10 @@ Usage: ${pc15.cyan("wraps email domains get-dkim --domain yourapp.com")}
|
|
|
8825
9415
|
}
|
|
8826
9416
|
case "remove": {
|
|
8827
9417
|
if (!flags.domain) {
|
|
8828
|
-
|
|
9418
|
+
clack16.log.error("--domain flag is required");
|
|
8829
9419
|
console.log(
|
|
8830
9420
|
`
|
|
8831
|
-
Usage: ${
|
|
9421
|
+
Usage: ${pc17.cyan("wraps email domains remove --domain yourapp.com --force")}
|
|
8832
9422
|
`
|
|
8833
9423
|
);
|
|
8834
9424
|
process.exit(1);
|
|
@@ -8840,27 +9430,40 @@ Usage: ${pc15.cyan("wraps email domains remove --domain yourapp.com --force")}
|
|
|
8840
9430
|
break;
|
|
8841
9431
|
}
|
|
8842
9432
|
default:
|
|
8843
|
-
|
|
9433
|
+
clack16.log.error(
|
|
8844
9434
|
`Unknown domains command: ${domainsSubCommand || "(none)"}`
|
|
8845
9435
|
);
|
|
8846
9436
|
console.log(
|
|
8847
9437
|
`
|
|
8848
|
-
Available commands: ${
|
|
9438
|
+
Available commands: ${pc17.cyan("add")}, ${pc17.cyan("list")}, ${pc17.cyan("verify")}, ${pc17.cyan("get-dkim")}, ${pc17.cyan("remove")}
|
|
8849
9439
|
`
|
|
8850
9440
|
);
|
|
8851
9441
|
process.exit(1);
|
|
8852
9442
|
}
|
|
8853
9443
|
break;
|
|
8854
9444
|
}
|
|
9445
|
+
case "destroy":
|
|
9446
|
+
await emailDestroy({
|
|
9447
|
+
force: flags.force,
|
|
9448
|
+
preview: flags.preview
|
|
9449
|
+
});
|
|
9450
|
+
break;
|
|
8855
9451
|
default:
|
|
8856
|
-
|
|
9452
|
+
clack16.log.error(`Unknown email command: ${subCommand}`);
|
|
8857
9453
|
console.log(
|
|
8858
9454
|
`
|
|
8859
|
-
Run ${
|
|
9455
|
+
Run ${pc17.cyan("wraps --help")} for available commands.
|
|
8860
9456
|
`
|
|
8861
9457
|
);
|
|
8862
9458
|
process.exit(1);
|
|
8863
9459
|
}
|
|
9460
|
+
const emailDuration = Date.now() - startTime;
|
|
9461
|
+
const emailCommandName = `email:${subCommand}`;
|
|
9462
|
+
trackCommand(emailCommandName, {
|
|
9463
|
+
success: true,
|
|
9464
|
+
duration_ms: emailDuration,
|
|
9465
|
+
service: "email"
|
|
9466
|
+
});
|
|
8864
9467
|
return;
|
|
8865
9468
|
}
|
|
8866
9469
|
if (primaryCommand === "dashboard" && subCommand) {
|
|
@@ -8872,25 +9475,22 @@ Run ${pc15.cyan("wraps --help")} for available commands.
|
|
|
8872
9475
|
});
|
|
8873
9476
|
break;
|
|
8874
9477
|
default:
|
|
8875
|
-
|
|
9478
|
+
clack16.log.error(`Unknown dashboard command: ${subCommand}`);
|
|
8876
9479
|
console.log(`
|
|
8877
|
-
Available commands: ${
|
|
9480
|
+
Available commands: ${pc17.cyan("update-role")}
|
|
8878
9481
|
`);
|
|
8879
|
-
console.log(`Run ${
|
|
9482
|
+
console.log(`Run ${pc17.cyan("wraps --help")} for more information.
|
|
8880
9483
|
`);
|
|
8881
9484
|
process.exit(1);
|
|
8882
9485
|
}
|
|
9486
|
+
const dashboardDuration = Date.now() - startTime;
|
|
9487
|
+
const dashboardCommandName = `dashboard:${subCommand}`;
|
|
9488
|
+
trackCommand(dashboardCommandName, {
|
|
9489
|
+
success: true,
|
|
9490
|
+
duration_ms: dashboardDuration
|
|
9491
|
+
});
|
|
8883
9492
|
return;
|
|
8884
9493
|
}
|
|
8885
|
-
if (primaryCommand === "sms" && subCommand) {
|
|
8886
|
-
clack14.log.warn("SMS infrastructure is coming soon!");
|
|
8887
|
-
console.log(
|
|
8888
|
-
`
|
|
8889
|
-
Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-team/wraps")}
|
|
8890
|
-
`
|
|
8891
|
-
);
|
|
8892
|
-
process.exit(0);
|
|
8893
|
-
}
|
|
8894
9494
|
switch (primaryCommand) {
|
|
8895
9495
|
// Global commands (work across all services)
|
|
8896
9496
|
case "status":
|
|
@@ -8906,8 +9506,8 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
|
|
|
8906
9506
|
break;
|
|
8907
9507
|
case "dashboard":
|
|
8908
9508
|
if (!subCommand) {
|
|
8909
|
-
|
|
8910
|
-
`'wraps dashboard' is deprecated. Use ${
|
|
9509
|
+
clack16.log.warn(
|
|
9510
|
+
`'wraps dashboard' is deprecated. Use ${pc17.cyan("wraps console")} instead.`
|
|
8911
9511
|
);
|
|
8912
9512
|
await dashboard({
|
|
8913
9513
|
port: flags.port,
|
|
@@ -8937,10 +9537,10 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
|
|
|
8937
9537
|
await telemetryStatus();
|
|
8938
9538
|
break;
|
|
8939
9539
|
default:
|
|
8940
|
-
|
|
9540
|
+
clack16.log.error(`Unknown telemetry command: ${subCommand}`);
|
|
8941
9541
|
console.log(
|
|
8942
9542
|
`
|
|
8943
|
-
Available commands: ${
|
|
9543
|
+
Available commands: ${pc17.cyan("enable")}, ${pc17.cyan("disable")}, ${pc17.cyan("status")}
|
|
8944
9544
|
`
|
|
8945
9545
|
);
|
|
8946
9546
|
process.exit(1);
|
|
@@ -8949,7 +9549,6 @@ Available commands: ${pc15.cyan("enable")}, ${pc15.cyan("disable")}, ${pc15.cyan
|
|
|
8949
9549
|
}
|
|
8950
9550
|
// Show help for service without subcommand
|
|
8951
9551
|
case "email":
|
|
8952
|
-
case "sms":
|
|
8953
9552
|
console.log(
|
|
8954
9553
|
`
|
|
8955
9554
|
Please specify a command for ${primaryCommand} service.
|
|
@@ -8958,10 +9557,10 @@ Please specify a command for ${primaryCommand} service.
|
|
|
8958
9557
|
showHelp();
|
|
8959
9558
|
break;
|
|
8960
9559
|
default:
|
|
8961
|
-
|
|
9560
|
+
clack16.log.error(`Unknown command: ${primaryCommand}`);
|
|
8962
9561
|
console.log(
|
|
8963
9562
|
`
|
|
8964
|
-
Run ${
|
|
9563
|
+
Run ${pc17.cyan("wraps --help")} for available commands.
|
|
8965
9564
|
`
|
|
8966
9565
|
);
|
|
8967
9566
|
process.exit(1);
|