@wraps.dev/cli 1.5.1 → 1.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1076 -703
- 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.3",
|
|
150
150
|
description: "CLI for deploying Wraps email infrastructure to your AWS account",
|
|
151
151
|
type: "module",
|
|
152
152
|
main: "./dist/cli.js",
|
|
@@ -552,7 +552,7 @@ var init_errors = __esm({
|
|
|
552
552
|
stackExists: (stackName) => new WrapsError(
|
|
553
553
|
`Stack "${stackName}" already exists`,
|
|
554
554
|
"STACK_EXISTS",
|
|
555
|
-
`To update: wraps upgrade
|
|
555
|
+
`To update: wraps email upgrade
|
|
556
556
|
To remove: wraps destroy --stack ${stackName}`,
|
|
557
557
|
"https://wraps.dev/docs/cli/upgrade"
|
|
558
558
|
),
|
|
@@ -1592,7 +1592,7 @@ function getPresetInfo(preset) {
|
|
|
1592
1592
|
volume: "10k-500k emails/month",
|
|
1593
1593
|
features: [
|
|
1594
1594
|
"Everything in Starter",
|
|
1595
|
-
"Reputation
|
|
1595
|
+
"Reputation tracking",
|
|
1596
1596
|
"Real-time event tracking (EventBridge)",
|
|
1597
1597
|
"90-day email history storage",
|
|
1598
1598
|
"Optional: Email archiving with rendered viewer",
|
|
@@ -1801,16 +1801,16 @@ async function promptProvider() {
|
|
|
1801
1801
|
const provider = await clack4.select({
|
|
1802
1802
|
message: "Where is your app hosted?",
|
|
1803
1803
|
options: [
|
|
1804
|
-
{
|
|
1805
|
-
value: "vercel",
|
|
1806
|
-
label: "Vercel",
|
|
1807
|
-
hint: "Uses OIDC (no AWS credentials needed)"
|
|
1808
|
-
},
|
|
1809
1804
|
{
|
|
1810
1805
|
value: "aws",
|
|
1811
1806
|
label: "AWS (Lambda/ECS/EC2)",
|
|
1812
1807
|
hint: "Uses IAM roles automatically"
|
|
1813
1808
|
},
|
|
1809
|
+
{
|
|
1810
|
+
value: "vercel",
|
|
1811
|
+
label: "Vercel",
|
|
1812
|
+
hint: "Uses OIDC (no AWS credentials needed)"
|
|
1813
|
+
},
|
|
1814
1814
|
{
|
|
1815
1815
|
value: "railway",
|
|
1816
1816
|
label: "Railway",
|
|
@@ -2204,6 +2204,14 @@ async function promptEmailArchiving() {
|
|
|
2204
2204
|
async function promptCustomConfig(existingConfig) {
|
|
2205
2205
|
clack4.log.info("Custom configuration builder");
|
|
2206
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
|
+
}
|
|
2207
2215
|
const trackingEnabled = await clack4.confirm({
|
|
2208
2216
|
message: "Enable open & click tracking?",
|
|
2209
2217
|
initialValue: existingConfig?.tracking?.enabled ?? true
|
|
@@ -2213,49 +2221,38 @@ async function promptCustomConfig(existingConfig) {
|
|
|
2213
2221
|
process.exit(0);
|
|
2214
2222
|
}
|
|
2215
2223
|
const eventTrackingEnabled = await clack4.confirm({
|
|
2216
|
-
message: "
|
|
2224
|
+
message: "Store email events in DynamoDB?",
|
|
2217
2225
|
initialValue: existingConfig?.eventTracking?.enabled ?? true
|
|
2218
2226
|
});
|
|
2219
2227
|
if (clack4.isCancel(eventTrackingEnabled)) {
|
|
2220
2228
|
clack4.cancel("Operation cancelled.");
|
|
2221
2229
|
process.exit(0);
|
|
2222
2230
|
}
|
|
2223
|
-
let dynamoDBHistory = false;
|
|
2224
2231
|
let archiveRetention = "90days";
|
|
2225
2232
|
if (eventTrackingEnabled) {
|
|
2226
|
-
|
|
2227
|
-
message: "
|
|
2228
|
-
|
|
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"
|
|
2229
2251
|
});
|
|
2230
|
-
if (clack4.isCancel(
|
|
2252
|
+
if (clack4.isCancel(archiveRetention)) {
|
|
2231
2253
|
clack4.cancel("Operation cancelled.");
|
|
2232
2254
|
process.exit(0);
|
|
2233
2255
|
}
|
|
2234
|
-
if (dynamoDBHistory) {
|
|
2235
|
-
archiveRetention = await clack4.select({
|
|
2236
|
-
message: "Email history retention period:",
|
|
2237
|
-
options: [
|
|
2238
|
-
{ value: "7days", label: "7 days", hint: "Minimal storage cost" },
|
|
2239
|
-
{ value: "30days", label: "30 days", hint: "Development/testing" },
|
|
2240
|
-
{
|
|
2241
|
-
value: "90days",
|
|
2242
|
-
label: "90 days (recommended)",
|
|
2243
|
-
hint: "Standard retention"
|
|
2244
|
-
},
|
|
2245
|
-
{ value: "1year", label: "1 year", hint: "Compliance requirements" },
|
|
2246
|
-
{
|
|
2247
|
-
value: "indefinite",
|
|
2248
|
-
label: "Indefinite",
|
|
2249
|
-
hint: "Higher storage cost"
|
|
2250
|
-
}
|
|
2251
|
-
],
|
|
2252
|
-
initialValue: existingConfig?.eventTracking?.archiveRetention || "90days"
|
|
2253
|
-
});
|
|
2254
|
-
if (clack4.isCancel(archiveRetention)) {
|
|
2255
|
-
clack4.cancel("Operation cancelled.");
|
|
2256
|
-
process.exit(0);
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
2256
|
}
|
|
2260
2257
|
const tlsRequired = await clack4.confirm({
|
|
2261
2258
|
message: "Require TLS encryption for all emails?",
|
|
@@ -2265,14 +2262,40 @@ async function promptCustomConfig(existingConfig) {
|
|
|
2265
2262
|
clack4.cancel("Operation cancelled.");
|
|
2266
2263
|
process.exit(0);
|
|
2267
2264
|
}
|
|
2268
|
-
const
|
|
2269
|
-
message: "
|
|
2270
|
-
initialValue: existingConfig?.
|
|
2265
|
+
const customMailFrom = await clack4.confirm({
|
|
2266
|
+
message: "Configure custom MAIL FROM domain? (improves DMARC alignment)",
|
|
2267
|
+
initialValue: existingConfig?.mailFromDomain !== void 0
|
|
2271
2268
|
});
|
|
2272
|
-
if (clack4.isCancel(
|
|
2269
|
+
if (clack4.isCancel(customMailFrom)) {
|
|
2273
2270
|
clack4.cancel("Operation cancelled.");
|
|
2274
2271
|
process.exit(0);
|
|
2275
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
|
+
}
|
|
2276
2299
|
const dedicatedIp = await clack4.confirm({
|
|
2277
2300
|
message: "Request dedicated IP address? (requires 100k+ emails/day)",
|
|
2278
2301
|
initialValue: existingConfig?.dedicatedIp ?? false
|
|
@@ -2336,6 +2359,7 @@ async function promptCustomConfig(existingConfig) {
|
|
|
2336
2359
|
} : { enabled: false },
|
|
2337
2360
|
tlsRequired,
|
|
2338
2361
|
reputationMetrics,
|
|
2362
|
+
mailFromSubdomain: customMailFrom ? typeof mailFromSubdomain === "string" ? mailFromSubdomain : "mail" : void 0,
|
|
2339
2363
|
suppressionList: {
|
|
2340
2364
|
enabled: true,
|
|
2341
2365
|
reasons: ["BOUNCE", "COMPLAINT"]
|
|
@@ -2353,7 +2377,7 @@ async function promptCustomConfig(existingConfig) {
|
|
|
2353
2377
|
"REJECT",
|
|
2354
2378
|
"RENDERING_FAILURE"
|
|
2355
2379
|
],
|
|
2356
|
-
dynamoDBHistory:
|
|
2380
|
+
dynamoDBHistory: true,
|
|
2357
2381
|
archiveRetention: typeof archiveRetention === "string" ? archiveRetention : "90days"
|
|
2358
2382
|
} : { enabled: false },
|
|
2359
2383
|
emailArchiving: emailArchivingEnabled ? {
|
|
@@ -2694,9 +2718,9 @@ init_esm_shims();
|
|
|
2694
2718
|
import { readFileSync } from "fs";
|
|
2695
2719
|
import { dirname as dirname2, join as join4 } from "path";
|
|
2696
2720
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
2697
|
-
import * as
|
|
2721
|
+
import * as clack16 from "@clack/prompts";
|
|
2698
2722
|
import args from "args";
|
|
2699
|
-
import
|
|
2723
|
+
import pc17 from "picocolors";
|
|
2700
2724
|
|
|
2701
2725
|
// src/commands/dashboard/update-role.ts
|
|
2702
2726
|
init_esm_shims();
|
|
@@ -2987,13 +3011,15 @@ Verification should complete within a few minutes.`,
|
|
|
2987
3011
|
)
|
|
2988
3012
|
];
|
|
2989
3013
|
if (domain) {
|
|
3014
|
+
const dmarcRuaDomain = outputs.mailFromDomain || domain;
|
|
2990
3015
|
dnsLines.push(
|
|
2991
3016
|
"",
|
|
2992
3017
|
pc2.bold("SPF Record (TXT):"),
|
|
2993
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"),
|
|
2994
3020
|
"",
|
|
2995
3021
|
pc2.bold("DMARC Record (TXT):"),
|
|
2996
|
-
` ${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}"`
|
|
2997
3023
|
);
|
|
2998
3024
|
if (outputs.mailFromDomain) {
|
|
2999
3025
|
dnsLines.push(
|
|
@@ -3051,7 +3077,7 @@ Verification should complete within a few minutes.`,
|
|
|
3051
3077
|
if (outputs.customTrackingDomain) {
|
|
3052
3078
|
console.log(
|
|
3053
3079
|
`
|
|
3054
|
-
${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(
|
|
3055
3081
|
"(after DNS propagates)"
|
|
3056
3082
|
)}
|
|
3057
3083
|
`
|
|
@@ -3103,7 +3129,7 @@ ${domainStrings.join("\n")}`);
|
|
|
3103
3129
|
);
|
|
3104
3130
|
} else {
|
|
3105
3131
|
featureLines.push(
|
|
3106
|
-
` ${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)")}`
|
|
3107
3133
|
);
|
|
3108
3134
|
}
|
|
3109
3135
|
if (status2.resources.lambdaFunctions && status2.resources.lambdaFunctions > 0) {
|
|
@@ -3112,7 +3138,7 @@ ${domainStrings.join("\n")}`);
|
|
|
3112
3138
|
);
|
|
3113
3139
|
} else {
|
|
3114
3140
|
featureLines.push(
|
|
3115
|
-
` ${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)")}`
|
|
3116
3142
|
);
|
|
3117
3143
|
}
|
|
3118
3144
|
if (status2.resources.archivingEnabled) {
|
|
@@ -3129,7 +3155,7 @@ ${domainStrings.join("\n")}`);
|
|
|
3129
3155
|
);
|
|
3130
3156
|
} else {
|
|
3131
3157
|
featureLines.push(
|
|
3132
|
-
` ${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)")}`
|
|
3133
3159
|
);
|
|
3134
3160
|
}
|
|
3135
3161
|
if (status2.tracking?.customTrackingDomain) {
|
|
@@ -3142,7 +3168,7 @@ ${domainStrings.join("\n")}`);
|
|
|
3142
3168
|
featureLines.push(` ${pc2.cyan(status2.tracking.customTrackingDomain)}`);
|
|
3143
3169
|
} else {
|
|
3144
3170
|
featureLines.push(
|
|
3145
|
-
` ${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)")}`
|
|
3146
3172
|
);
|
|
3147
3173
|
}
|
|
3148
3174
|
featureLines.push(
|
|
@@ -3190,6 +3216,7 @@ ${domainStrings.join("\n")}`);
|
|
|
3190
3216
|
for (const domain of domainsNeedingDNS) {
|
|
3191
3217
|
const dnsLines = [];
|
|
3192
3218
|
if (domain.status === "pending" && domain.dkimTokens && domain.dkimTokens.length > 0) {
|
|
3219
|
+
const dmarcRuaDomain = domain.mailFromDomain || domain.domain;
|
|
3193
3220
|
dnsLines.push(
|
|
3194
3221
|
pc2.bold("DKIM Records (CNAME):"),
|
|
3195
3222
|
...domain.dkimTokens.map(
|
|
@@ -3198,9 +3225,10 @@ ${domainStrings.join("\n")}`);
|
|
|
3198
3225
|
"",
|
|
3199
3226
|
pc2.bold("SPF Record (TXT):"),
|
|
3200
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"),
|
|
3201
3229
|
"",
|
|
3202
3230
|
pc2.bold("DMARC Record (TXT):"),
|
|
3203
|
-
` ${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}"`
|
|
3204
3232
|
);
|
|
3205
3233
|
}
|
|
3206
3234
|
if (domain.mailFromDomain && domain.mailFromStatus !== "SUCCESS") {
|
|
@@ -3220,7 +3248,7 @@ ${domainStrings.join("\n")}`);
|
|
|
3220
3248
|
const exampleDomain = domainsNeedingDNS[0].domain;
|
|
3221
3249
|
console.log(
|
|
3222
3250
|
`
|
|
3223
|
-
${pc2.dim("Run:")} ${pc2.yellow(`wraps verify --domain ${exampleDomain}`)} ${pc2.dim(
|
|
3251
|
+
${pc2.dim("Run:")} ${pc2.yellow(`wraps email verify --domain ${exampleDomain}`)} ${pc2.dim(
|
|
3224
3252
|
"(after DNS propagates)"
|
|
3225
3253
|
)}
|
|
3226
3254
|
`
|
|
@@ -4166,20 +4194,22 @@ async function createSESResources(config2) {
|
|
|
4166
4194
|
dkimTokens = domainIdentity.dkimSigningAttributes.apply(
|
|
4167
4195
|
(attrs) => attrs?.tokens || []
|
|
4168
4196
|
);
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
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
|
+
}
|
|
4183
4213
|
}
|
|
4184
4214
|
return {
|
|
4185
4215
|
configSet,
|
|
@@ -4315,8 +4345,8 @@ async function deployEmailStack(config2) {
|
|
|
4315
4345
|
let cloudFrontResources;
|
|
4316
4346
|
let acmResources;
|
|
4317
4347
|
if (emailConfig.tracking?.enabled && emailConfig.tracking.customRedirectDomain && emailConfig.tracking.httpsEnabled) {
|
|
4318
|
-
const { findHostedZone:
|
|
4319
|
-
const hostedZone = await
|
|
4348
|
+
const { findHostedZone: findHostedZone3 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
4349
|
+
const hostedZone = await findHostedZone3(
|
|
4320
4350
|
emailConfig.tracking.customRedirectDomain,
|
|
4321
4351
|
config2.region
|
|
4322
4352
|
);
|
|
@@ -4342,9 +4372,13 @@ async function deployEmailStack(config2) {
|
|
|
4342
4372
|
"wraps-email-eventbridge",
|
|
4343
4373
|
config2.region
|
|
4344
4374
|
);
|
|
4375
|
+
let mailFromDomain = emailConfig.mailFromDomain;
|
|
4376
|
+
if (!mailFromDomain && emailConfig.mailFromSubdomain && emailConfig.domain) {
|
|
4377
|
+
mailFromDomain = `${emailConfig.mailFromSubdomain}.${emailConfig.domain}`;
|
|
4378
|
+
}
|
|
4345
4379
|
sesResources = await createSESResources({
|
|
4346
4380
|
domain: emailConfig.domain,
|
|
4347
|
-
mailFromDomain
|
|
4381
|
+
mailFromDomain,
|
|
4348
4382
|
region: config2.region,
|
|
4349
4383
|
trackingConfig: emailConfig.tracking,
|
|
4350
4384
|
eventTypes: emailConfig.eventTracking?.events,
|
|
@@ -5177,8 +5211,8 @@ async function connect(options) {
|
|
|
5177
5211
|
throw new Error(`Pulumi deployment failed: ${error.message}`);
|
|
5178
5212
|
}
|
|
5179
5213
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
5180
|
-
const { findHostedZone:
|
|
5181
|
-
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);
|
|
5182
5216
|
if (hostedZone) {
|
|
5183
5217
|
try {
|
|
5184
5218
|
progress.start("Creating DNS records in Route53");
|
|
@@ -5263,119 +5297,412 @@ ${pc6.dim("Example:")}`);
|
|
|
5263
5297
|
});
|
|
5264
5298
|
}
|
|
5265
5299
|
|
|
5266
|
-
// src/commands/email/
|
|
5300
|
+
// src/commands/email/destroy.ts
|
|
5267
5301
|
init_esm_shims();
|
|
5268
5302
|
init_events();
|
|
5269
5303
|
init_aws();
|
|
5270
|
-
|
|
5271
|
-
import { GetEmailIdentityCommand, SESv2Client as SESv2Client2 } from "@aws-sdk/client-sesv2";
|
|
5304
|
+
init_errors();
|
|
5272
5305
|
import * as clack6 from "@clack/prompts";
|
|
5306
|
+
import * as pulumi7 from "@pulumi/pulumi";
|
|
5273
5307
|
import pc7 from "picocolors";
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5308
|
+
|
|
5309
|
+
// src/utils/route53.ts
|
|
5310
|
+
init_esm_shims();
|
|
5311
|
+
import {
|
|
5312
|
+
ChangeResourceRecordSetsCommand as ChangeResourceRecordSetsCommand2,
|
|
5313
|
+
ListHostedZonesByNameCommand as ListHostedZonesByNameCommand2,
|
|
5314
|
+
ListResourceRecordSetsCommand,
|
|
5315
|
+
Route53Client as Route53Client2
|
|
5316
|
+
} from "@aws-sdk/client-route-53";
|
|
5317
|
+
async function findHostedZone2(domain, region) {
|
|
5318
|
+
const client = new Route53Client2({ region });
|
|
5282
5319
|
try {
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
);
|
|
5289
|
-
return response;
|
|
5290
|
-
}
|
|
5320
|
+
const response = await client.send(
|
|
5321
|
+
new ListHostedZonesByNameCommand2({
|
|
5322
|
+
DNSName: domain,
|
|
5323
|
+
MaxItems: 1
|
|
5324
|
+
})
|
|
5291
5325
|
);
|
|
5292
|
-
|
|
5293
|
-
|
|
5326
|
+
const zone = response.HostedZones?.[0];
|
|
5327
|
+
if (zone && zone.Name === `${domain}.` && zone.Id) {
|
|
5328
|
+
return {
|
|
5329
|
+
id: zone.Id.replace("/hostedzone/", ""),
|
|
5330
|
+
name: zone.Name
|
|
5331
|
+
};
|
|
5332
|
+
}
|
|
5333
|
+
return null;
|
|
5294
5334
|
} catch (_error) {
|
|
5295
|
-
|
|
5296
|
-
clack6.log.error(`Domain ${options.domain} not found in SES`);
|
|
5297
|
-
console.log(
|
|
5298
|
-
`
|
|
5299
|
-
Run ${pc7.cyan(`wraps email init --domain ${options.domain}`)} to add this domain.
|
|
5300
|
-
`
|
|
5301
|
-
);
|
|
5302
|
-
process.exit(1);
|
|
5303
|
-
return;
|
|
5335
|
+
return null;
|
|
5304
5336
|
}
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
const
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5337
|
+
}
|
|
5338
|
+
async function deleteDNSRecords(hostedZoneId, domain, dkimTokens, region, customTrackingDomain, mailFromDomain) {
|
|
5339
|
+
const client = new Route53Client2({ region });
|
|
5340
|
+
const response = await client.send(
|
|
5341
|
+
new ListResourceRecordSetsCommand({
|
|
5342
|
+
HostedZoneId: hostedZoneId,
|
|
5343
|
+
MaxItems: 500
|
|
5344
|
+
})
|
|
5345
|
+
);
|
|
5346
|
+
const recordSets = response.ResourceRecordSets || [];
|
|
5347
|
+
const changes = [];
|
|
5348
|
+
const addDeletionIfExists = (name, type) => {
|
|
5349
|
+
const normalizedName = name.endsWith(".") ? name : `${name}.`;
|
|
5350
|
+
const record = recordSets.find(
|
|
5351
|
+
(rs) => rs.Name === normalizedName && rs.Type === type
|
|
5352
|
+
);
|
|
5353
|
+
if (record && record.ResourceRecords) {
|
|
5354
|
+
changes.push({
|
|
5355
|
+
Action: "DELETE",
|
|
5356
|
+
ResourceRecordSet: record
|
|
5325
5357
|
});
|
|
5326
5358
|
}
|
|
5359
|
+
};
|
|
5360
|
+
for (const token of dkimTokens) {
|
|
5361
|
+
addDeletionIfExists(`${token}._domainkey.${domain}`, "CNAME");
|
|
5327
5362
|
}
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
} catch (_error) {
|
|
5339
|
-
dnsResults.push({
|
|
5340
|
-
name: options.domain,
|
|
5341
|
-
type: "TXT (SPF)",
|
|
5342
|
-
status: "missing"
|
|
5343
|
-
});
|
|
5363
|
+
addDeletionIfExists(`_dmarc.${domain}`, "TXT");
|
|
5364
|
+
if (customTrackingDomain) {
|
|
5365
|
+
addDeletionIfExists(customTrackingDomain, "CNAME");
|
|
5366
|
+
}
|
|
5367
|
+
if (mailFromDomain) {
|
|
5368
|
+
addDeletionIfExists(mailFromDomain, "MX");
|
|
5369
|
+
addDeletionIfExists(mailFromDomain, "TXT");
|
|
5370
|
+
}
|
|
5371
|
+
if (changes.length === 0) {
|
|
5372
|
+
return;
|
|
5344
5373
|
}
|
|
5374
|
+
await client.send(
|
|
5375
|
+
new ChangeResourceRecordSetsCommand2({
|
|
5376
|
+
HostedZoneId: hostedZoneId,
|
|
5377
|
+
ChangeBatch: {
|
|
5378
|
+
Changes: changes
|
|
5379
|
+
}
|
|
5380
|
+
})
|
|
5381
|
+
);
|
|
5382
|
+
}
|
|
5383
|
+
|
|
5384
|
+
// src/commands/email/destroy.ts
|
|
5385
|
+
async function getEmailIdentityInfo(domain, region) {
|
|
5345
5386
|
try {
|
|
5346
|
-
const
|
|
5347
|
-
const
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5387
|
+
const { SESv2Client: SESv2Client5, GetEmailIdentityCommand: GetEmailIdentityCommand4 } = await import("@aws-sdk/client-sesv2");
|
|
5388
|
+
const ses = new SESv2Client5({ region });
|
|
5389
|
+
const response = await ses.send(
|
|
5390
|
+
new GetEmailIdentityCommand4({ EmailIdentity: domain })
|
|
5391
|
+
);
|
|
5392
|
+
return {
|
|
5393
|
+
dkimTokens: response.DkimAttributes?.Tokens || [],
|
|
5394
|
+
mailFromDomain: response.MailFromAttributes?.MailFromDomain
|
|
5395
|
+
};
|
|
5354
5396
|
} catch (_error) {
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5397
|
+
return { dkimTokens: [] };
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
5400
|
+
async function emailDestroy(options) {
|
|
5401
|
+
const startTime = Date.now();
|
|
5402
|
+
clack6.intro(
|
|
5403
|
+
pc7.bold(
|
|
5404
|
+
options.preview ? "Email Infrastructure Destruction Preview" : "Email Infrastructure Teardown"
|
|
5405
|
+
)
|
|
5406
|
+
);
|
|
5407
|
+
const progress = new DeploymentProgress();
|
|
5408
|
+
const identity = await progress.execute(
|
|
5409
|
+
"Validating AWS credentials",
|
|
5410
|
+
async () => validateAWSCredentials()
|
|
5411
|
+
);
|
|
5412
|
+
const region = await getAWSRegion();
|
|
5413
|
+
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
5414
|
+
const emailService = metadata?.services?.email;
|
|
5415
|
+
const emailConfig = emailService?.config;
|
|
5416
|
+
const domain = emailConfig?.domain;
|
|
5417
|
+
const storedStackName = emailService?.pulumiStackName;
|
|
5418
|
+
if (!(options.force || options.preview)) {
|
|
5419
|
+
const confirmed = await clack6.confirm({
|
|
5420
|
+
message: pc7.red(
|
|
5421
|
+
"Are you sure you want to destroy all email infrastructure?"
|
|
5422
|
+
),
|
|
5423
|
+
initialValue: false
|
|
5359
5424
|
});
|
|
5425
|
+
if (clack6.isCancel(confirmed) || !confirmed) {
|
|
5426
|
+
clack6.cancel("Destruction cancelled.");
|
|
5427
|
+
process.exit(0);
|
|
5428
|
+
}
|
|
5360
5429
|
}
|
|
5361
|
-
|
|
5430
|
+
let shouldCleanDNS = false;
|
|
5431
|
+
let hostedZone = null;
|
|
5432
|
+
let dkimTokens = [];
|
|
5433
|
+
let mailFromDomain = emailConfig?.mailFromDomain;
|
|
5434
|
+
if (domain && !options.preview) {
|
|
5435
|
+
hostedZone = await findHostedZone2(domain, region);
|
|
5436
|
+
if (hostedZone) {
|
|
5437
|
+
const identityInfo = await getEmailIdentityInfo(domain, region);
|
|
5438
|
+
dkimTokens = identityInfo.dkimTokens;
|
|
5439
|
+
if (!mailFromDomain && identityInfo.mailFromDomain) {
|
|
5440
|
+
mailFromDomain = identityInfo.mailFromDomain;
|
|
5441
|
+
}
|
|
5442
|
+
if (!options.force) {
|
|
5443
|
+
const cleanDNS = await clack6.confirm({
|
|
5444
|
+
message: `Found Route53 hosted zone for ${pc7.cyan(domain)}. Delete DNS records (DKIM, DMARC, MAIL FROM)?`,
|
|
5445
|
+
initialValue: true
|
|
5446
|
+
});
|
|
5447
|
+
if (clack6.isCancel(cleanDNS)) {
|
|
5448
|
+
clack6.cancel("Destruction cancelled.");
|
|
5449
|
+
process.exit(0);
|
|
5450
|
+
}
|
|
5451
|
+
shouldCleanDNS = cleanDNS;
|
|
5452
|
+
} else {
|
|
5453
|
+
shouldCleanDNS = true;
|
|
5454
|
+
}
|
|
5455
|
+
}
|
|
5456
|
+
}
|
|
5457
|
+
if (options.preview) {
|
|
5362
5458
|
try {
|
|
5363
|
-
const
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5459
|
+
const previewResult = await progress.execute(
|
|
5460
|
+
"Generating destruction preview",
|
|
5461
|
+
async () => {
|
|
5462
|
+
await ensurePulumiWorkDir();
|
|
5463
|
+
const stackName = storedStackName || `wraps-email-${identity.accountId}-${region}`;
|
|
5464
|
+
let stack;
|
|
5465
|
+
try {
|
|
5466
|
+
stack = await pulumi7.automation.LocalWorkspace.selectStack({
|
|
5467
|
+
stackName,
|
|
5468
|
+
workDir: getPulumiWorkDir()
|
|
5469
|
+
});
|
|
5470
|
+
} catch (_error) {
|
|
5471
|
+
throw new Error("No email infrastructure found to preview");
|
|
5472
|
+
}
|
|
5473
|
+
const result = await stack.preview({ diff: true });
|
|
5474
|
+
return result;
|
|
5475
|
+
}
|
|
5367
5476
|
);
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5477
|
+
displayPreview({
|
|
5478
|
+
changeSummary: previewResult.changeSummary,
|
|
5479
|
+
costEstimate: "Monthly cost after destruction: $0.00",
|
|
5480
|
+
commandName: "wraps email destroy"
|
|
5481
|
+
});
|
|
5482
|
+
if (domain) {
|
|
5483
|
+
const previewHostedZone = await findHostedZone2(domain, region);
|
|
5484
|
+
if (previewHostedZone) {
|
|
5485
|
+
clack6.log.info(
|
|
5486
|
+
`DNS records in Route53 for ${pc7.cyan(domain)} will also be deleted`
|
|
5487
|
+
);
|
|
5488
|
+
}
|
|
5489
|
+
}
|
|
5490
|
+
clack6.outro(
|
|
5491
|
+
pc7.green("Preview complete. Run without --preview to destroy.")
|
|
5492
|
+
);
|
|
5493
|
+
trackServiceRemoved("email", {
|
|
5494
|
+
preview: true,
|
|
5495
|
+
duration_ms: Date.now() - startTime
|
|
5496
|
+
});
|
|
5497
|
+
return;
|
|
5498
|
+
} catch (error) {
|
|
5499
|
+
progress.stop();
|
|
5500
|
+
if (error.message.includes("No email infrastructure found")) {
|
|
5501
|
+
clack6.log.warn("No email infrastructure found to preview");
|
|
5502
|
+
process.exit(0);
|
|
5503
|
+
}
|
|
5504
|
+
trackError("PREVIEW_FAILED", "email destroy", { step: "preview" });
|
|
5505
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
5506
|
+
}
|
|
5507
|
+
}
|
|
5508
|
+
if (shouldCleanDNS && hostedZone && domain && dkimTokens.length > 0) {
|
|
5509
|
+
try {
|
|
5510
|
+
await progress.execute(
|
|
5511
|
+
`Deleting DNS records for ${domain}`,
|
|
5512
|
+
async () => {
|
|
5513
|
+
await deleteDNSRecords(
|
|
5514
|
+
hostedZone.id,
|
|
5515
|
+
domain,
|
|
5516
|
+
dkimTokens,
|
|
5517
|
+
region,
|
|
5518
|
+
emailConfig?.tracking?.customRedirectDomain,
|
|
5519
|
+
mailFromDomain
|
|
5520
|
+
);
|
|
5521
|
+
}
|
|
5522
|
+
);
|
|
5523
|
+
} catch (error) {
|
|
5524
|
+
clack6.log.warn(`Could not delete DNS records: ${error.message}`);
|
|
5525
|
+
clack6.log.info("You may need to delete them manually from Route53");
|
|
5526
|
+
}
|
|
5527
|
+
}
|
|
5528
|
+
try {
|
|
5529
|
+
await progress.execute(
|
|
5530
|
+
"Destroying email infrastructure (this may take 2-3 minutes)",
|
|
5531
|
+
async () => {
|
|
5532
|
+
await ensurePulumiWorkDir();
|
|
5533
|
+
const stackName = storedStackName || `wraps-email-${identity.accountId}-${region}`;
|
|
5534
|
+
let stack;
|
|
5535
|
+
try {
|
|
5536
|
+
stack = await pulumi7.automation.LocalWorkspace.selectStack({
|
|
5537
|
+
stackName,
|
|
5538
|
+
workDir: getPulumiWorkDir()
|
|
5539
|
+
});
|
|
5540
|
+
} catch (_error) {
|
|
5541
|
+
throw new Error("No email infrastructure found to destroy");
|
|
5542
|
+
}
|
|
5543
|
+
await stack.destroy({ onOutput: () => {
|
|
5544
|
+
} });
|
|
5545
|
+
await stack.workspace.removeStack(stackName);
|
|
5546
|
+
}
|
|
5547
|
+
);
|
|
5548
|
+
} catch (error) {
|
|
5549
|
+
progress.stop();
|
|
5550
|
+
if (error.message.includes("No email infrastructure found")) {
|
|
5551
|
+
clack6.log.warn("No email infrastructure found");
|
|
5552
|
+
await deleteConnectionMetadata(identity.accountId, region);
|
|
5553
|
+
process.exit(0);
|
|
5554
|
+
}
|
|
5555
|
+
if (error.message?.includes("stack is currently locked")) {
|
|
5556
|
+
trackError("STACK_LOCKED", "email destroy", { step: "destroy" });
|
|
5557
|
+
throw errors.stackLocked();
|
|
5558
|
+
}
|
|
5559
|
+
trackError("DESTROY_FAILED", "email destroy", { step: "destroy" });
|
|
5560
|
+
clack6.log.error("Email infrastructure destruction failed");
|
|
5561
|
+
throw error;
|
|
5562
|
+
}
|
|
5563
|
+
await deleteConnectionMetadata(identity.accountId, region);
|
|
5564
|
+
progress.stop();
|
|
5565
|
+
const deletedItems = ["AWS infrastructure"];
|
|
5566
|
+
if (shouldCleanDNS && hostedZone) {
|
|
5567
|
+
deletedItems.push("Route53 DNS records");
|
|
5568
|
+
}
|
|
5569
|
+
clack6.outro(pc7.green(`Email infrastructure has been removed`));
|
|
5570
|
+
if (domain) {
|
|
5571
|
+
console.log(`
|
|
5572
|
+
${pc7.bold("Cleaned up:")}`);
|
|
5573
|
+
for (const item of deletedItems) {
|
|
5574
|
+
console.log(` ${pc7.green("\u2713")} ${item}`);
|
|
5575
|
+
}
|
|
5576
|
+
console.log(
|
|
5577
|
+
`
|
|
5578
|
+
${pc7.dim("Note: SPF record was not deleted. Remove 'include:amazonses.com' manually if needed.")}`
|
|
5579
|
+
);
|
|
5580
|
+
}
|
|
5581
|
+
console.log(
|
|
5582
|
+
`
|
|
5583
|
+
Run ${pc7.cyan("wraps email init")} to deploy infrastructure again.
|
|
5584
|
+
`
|
|
5585
|
+
);
|
|
5586
|
+
trackServiceRemoved("email", {
|
|
5587
|
+
reason: "user_initiated",
|
|
5588
|
+
duration_ms: Date.now() - startTime,
|
|
5589
|
+
dns_cleaned: shouldCleanDNS
|
|
5590
|
+
});
|
|
5591
|
+
}
|
|
5592
|
+
|
|
5593
|
+
// src/commands/email/domains.ts
|
|
5594
|
+
init_esm_shims();
|
|
5595
|
+
init_events();
|
|
5596
|
+
init_aws();
|
|
5597
|
+
import { Resolver } from "dns/promises";
|
|
5598
|
+
import { GetEmailIdentityCommand, SESv2Client as SESv2Client2 } from "@aws-sdk/client-sesv2";
|
|
5599
|
+
import * as clack7 from "@clack/prompts";
|
|
5600
|
+
import pc8 from "picocolors";
|
|
5601
|
+
async function verifyDomain(options) {
|
|
5602
|
+
clack7.intro(pc8.bold(`Verifying ${options.domain}`));
|
|
5603
|
+
const progress = new DeploymentProgress();
|
|
5604
|
+
const region = await getAWSRegion();
|
|
5605
|
+
const sesClient = new SESv2Client2({ region });
|
|
5606
|
+
let identity;
|
|
5607
|
+
let dkimTokens = [];
|
|
5608
|
+
let mailFromDomain;
|
|
5609
|
+
try {
|
|
5610
|
+
identity = await progress.execute(
|
|
5611
|
+
"Checking SES verification status",
|
|
5612
|
+
async () => {
|
|
5613
|
+
const response = await sesClient.send(
|
|
5614
|
+
new GetEmailIdentityCommand({ EmailIdentity: options.domain })
|
|
5615
|
+
);
|
|
5616
|
+
return response;
|
|
5617
|
+
}
|
|
5618
|
+
);
|
|
5619
|
+
dkimTokens = identity.DkimAttributes?.Tokens || [];
|
|
5620
|
+
mailFromDomain = identity.MailFromAttributes?.MailFromDomain;
|
|
5621
|
+
} catch (_error) {
|
|
5622
|
+
progress.stop();
|
|
5623
|
+
clack7.log.error(`Domain ${options.domain} not found in SES`);
|
|
5624
|
+
console.log(
|
|
5625
|
+
`
|
|
5626
|
+
Run ${pc8.cyan(`wraps email init --domain ${options.domain}`)} to add this domain.
|
|
5627
|
+
`
|
|
5628
|
+
);
|
|
5629
|
+
process.exit(1);
|
|
5630
|
+
return;
|
|
5631
|
+
}
|
|
5632
|
+
const resolver = new Resolver();
|
|
5633
|
+
resolver.setServers(["8.8.8.8", "1.1.1.1"]);
|
|
5634
|
+
const dnsResults = [];
|
|
5635
|
+
for (const token of dkimTokens) {
|
|
5636
|
+
const dkimRecord = `${token}._domainkey.${options.domain}`;
|
|
5637
|
+
try {
|
|
5638
|
+
const records = await resolver.resolveCname(dkimRecord);
|
|
5639
|
+
const expected = `${token}.dkim.amazonses.com`;
|
|
5640
|
+
const found = records.some((r) => r === expected || r === `${expected}.`);
|
|
5641
|
+
dnsResults.push({
|
|
5642
|
+
name: dkimRecord,
|
|
5643
|
+
type: "CNAME",
|
|
5644
|
+
status: found ? "verified" : "incorrect",
|
|
5645
|
+
records
|
|
5646
|
+
});
|
|
5647
|
+
} catch (_error) {
|
|
5648
|
+
dnsResults.push({
|
|
5649
|
+
name: dkimRecord,
|
|
5650
|
+
type: "CNAME",
|
|
5651
|
+
status: "missing"
|
|
5652
|
+
});
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
try {
|
|
5656
|
+
const records = await resolver.resolveTxt(options.domain);
|
|
5657
|
+
const spfRecord = records.flat().find((r) => r.startsWith("v=spf1"));
|
|
5658
|
+
const hasAmazonSES = spfRecord?.includes("include:amazonses.com");
|
|
5659
|
+
dnsResults.push({
|
|
5660
|
+
name: options.domain,
|
|
5661
|
+
type: "TXT (SPF)",
|
|
5662
|
+
status: hasAmazonSES ? "verified" : spfRecord ? "incorrect" : "missing",
|
|
5663
|
+
records: spfRecord ? [spfRecord] : void 0
|
|
5664
|
+
});
|
|
5665
|
+
} catch (_error) {
|
|
5666
|
+
dnsResults.push({
|
|
5667
|
+
name: options.domain,
|
|
5668
|
+
type: "TXT (SPF)",
|
|
5669
|
+
status: "missing"
|
|
5670
|
+
});
|
|
5671
|
+
}
|
|
5672
|
+
try {
|
|
5673
|
+
const records = await resolver.resolveTxt(`_dmarc.${options.domain}`);
|
|
5674
|
+
const dmarcRecord = records.flat().find((r) => r.startsWith("v=DMARC1"));
|
|
5675
|
+
dnsResults.push({
|
|
5676
|
+
name: `_dmarc.${options.domain}`,
|
|
5677
|
+
type: "TXT (DMARC)",
|
|
5678
|
+
status: dmarcRecord ? "verified" : "missing",
|
|
5679
|
+
records: dmarcRecord ? [dmarcRecord] : void 0
|
|
5680
|
+
});
|
|
5681
|
+
} catch (_error) {
|
|
5682
|
+
dnsResults.push({
|
|
5683
|
+
name: `_dmarc.${options.domain}`,
|
|
5684
|
+
type: "TXT (DMARC)",
|
|
5685
|
+
status: "missing"
|
|
5686
|
+
});
|
|
5687
|
+
}
|
|
5688
|
+
if (mailFromDomain) {
|
|
5689
|
+
try {
|
|
5690
|
+
const mxRecords = await resolver.resolveMx(mailFromDomain);
|
|
5691
|
+
const expectedMx = `feedback-smtp.${region}.amazonses.com`;
|
|
5692
|
+
const hasMx = mxRecords.some(
|
|
5693
|
+
(r) => r.exchange === expectedMx || r.exchange === `${expectedMx}.`
|
|
5694
|
+
);
|
|
5695
|
+
dnsResults.push({
|
|
5696
|
+
name: mailFromDomain,
|
|
5697
|
+
type: "MX",
|
|
5698
|
+
status: hasMx ? "verified" : mxRecords.length > 0 ? "incorrect" : "missing",
|
|
5699
|
+
records: mxRecords.map((r) => `${r.priority} ${r.exchange}`)
|
|
5700
|
+
});
|
|
5701
|
+
} catch (_error) {
|
|
5702
|
+
dnsResults.push({
|
|
5703
|
+
name: mailFromDomain,
|
|
5704
|
+
type: "MX",
|
|
5705
|
+
status: "missing"
|
|
5379
5706
|
});
|
|
5380
5707
|
}
|
|
5381
5708
|
try {
|
|
@@ -5401,55 +5728,55 @@ Run ${pc7.cyan(`wraps email init --domain ${options.domain}`)} to add this domai
|
|
|
5401
5728
|
const dkimStatus = identity.DkimAttributes?.Status || "PENDING";
|
|
5402
5729
|
const mailFromStatus = identity.MailFromAttributes?.MailFromDomainStatus || "NOT_CONFIGURED";
|
|
5403
5730
|
const statusLines = [
|
|
5404
|
-
`${
|
|
5405
|
-
`${
|
|
5406
|
-
`${
|
|
5731
|
+
`${pc8.bold("Domain:")} ${options.domain}`,
|
|
5732
|
+
`${pc8.bold("Verification Status:")} ${verificationStatus === "verified" ? pc8.green("\u2713 Verified") : pc8.yellow("\u23F1 Pending")}`,
|
|
5733
|
+
`${pc8.bold("DKIM Status:")} ${dkimStatus === "SUCCESS" ? pc8.green("\u2713 Success") : pc8.yellow(`\u23F1 ${dkimStatus}`)}`
|
|
5407
5734
|
];
|
|
5408
5735
|
if (mailFromDomain) {
|
|
5409
5736
|
statusLines.push(
|
|
5410
|
-
`${
|
|
5411
|
-
`${
|
|
5737
|
+
`${pc8.bold("MAIL FROM Domain:")} ${mailFromDomain}`,
|
|
5738
|
+
`${pc8.bold("MAIL FROM Status:")} ${mailFromStatus === "SUCCESS" ? pc8.green("\u2713 Success") : mailFromStatus === "NOT_CONFIGURED" ? pc8.yellow("\u23F1 Not Configured") : pc8.yellow(`\u23F1 ${mailFromStatus}`)}`
|
|
5412
5739
|
);
|
|
5413
5740
|
}
|
|
5414
|
-
|
|
5741
|
+
clack7.note(statusLines.join("\n"), "SES Status");
|
|
5415
5742
|
const dnsLines = dnsResults.map((record) => {
|
|
5416
5743
|
let statusIcon;
|
|
5417
5744
|
let statusColor;
|
|
5418
5745
|
if (record.status === "verified") {
|
|
5419
5746
|
statusIcon = "\u2713";
|
|
5420
|
-
statusColor =
|
|
5747
|
+
statusColor = pc8.green;
|
|
5421
5748
|
} else if (record.status === "incorrect") {
|
|
5422
5749
|
statusIcon = "\u2717";
|
|
5423
|
-
statusColor =
|
|
5750
|
+
statusColor = pc8.red;
|
|
5424
5751
|
} else {
|
|
5425
5752
|
statusIcon = "\u2717";
|
|
5426
|
-
statusColor =
|
|
5753
|
+
statusColor = pc8.red;
|
|
5427
5754
|
}
|
|
5428
5755
|
const recordInfo = record.records ? ` \u2192 ${record.records.join(", ")}` : "";
|
|
5429
5756
|
return ` ${statusColor(statusIcon)} ${record.name} (${record.type}) ${statusColor(
|
|
5430
5757
|
record.status
|
|
5431
5758
|
)}${recordInfo}`;
|
|
5432
5759
|
});
|
|
5433
|
-
|
|
5760
|
+
clack7.note(dnsLines.join("\n"), "DNS Records");
|
|
5434
5761
|
const allVerified = dnsResults.every((r) => r.status === "verified");
|
|
5435
5762
|
const someIncorrect = dnsResults.some((r) => r.status === "incorrect");
|
|
5436
5763
|
if (verificationStatus === "verified" && allVerified) {
|
|
5437
|
-
|
|
5438
|
-
|
|
5764
|
+
clack7.outro(
|
|
5765
|
+
pc8.green("\u2713 Domain is fully verified and ready to send emails!")
|
|
5439
5766
|
);
|
|
5440
5767
|
trackFeature("domain_verified", { dns_auto_detected: true });
|
|
5441
5768
|
} else if (someIncorrect) {
|
|
5442
|
-
|
|
5443
|
-
|
|
5769
|
+
clack7.outro(
|
|
5770
|
+
pc8.red("\u2717 Some DNS records are incorrect. Please update them.")
|
|
5444
5771
|
);
|
|
5445
5772
|
console.log(
|
|
5446
5773
|
`
|
|
5447
|
-
Run ${
|
|
5774
|
+
Run ${pc8.cyan("wraps email status")} to see the correct DNS records.
|
|
5448
5775
|
`
|
|
5449
5776
|
);
|
|
5450
5777
|
} else {
|
|
5451
|
-
|
|
5452
|
-
|
|
5778
|
+
clack7.outro(
|
|
5779
|
+
pc8.yellow("\u23F1 Waiting for DNS propagation and SES verification")
|
|
5453
5780
|
);
|
|
5454
5781
|
console.log("\nDNS records can take up to 48 hours to propagate.");
|
|
5455
5782
|
console.log(
|
|
@@ -5463,7 +5790,7 @@ Run ${pc7.cyan("wraps email status")} to see the correct DNS records.
|
|
|
5463
5790
|
});
|
|
5464
5791
|
}
|
|
5465
5792
|
async function addDomain(options) {
|
|
5466
|
-
|
|
5793
|
+
clack7.intro(pc8.bold(`Adding domain ${options.domain} to SES`));
|
|
5467
5794
|
const progress = new DeploymentProgress();
|
|
5468
5795
|
const region = await getAWSRegion();
|
|
5469
5796
|
const sesClient = new SESv2Client2({ region });
|
|
@@ -5473,10 +5800,10 @@ async function addDomain(options) {
|
|
|
5473
5800
|
new GetEmailIdentityCommand({ EmailIdentity: options.domain })
|
|
5474
5801
|
);
|
|
5475
5802
|
progress.stop();
|
|
5476
|
-
|
|
5803
|
+
clack7.log.warn(`Domain ${options.domain} already exists in SES`);
|
|
5477
5804
|
console.log(
|
|
5478
5805
|
`
|
|
5479
|
-
Run ${
|
|
5806
|
+
Run ${pc8.cyan(`wraps email domains verify --domain ${options.domain}`)} to check verification status.
|
|
5480
5807
|
`
|
|
5481
5808
|
);
|
|
5482
5809
|
return;
|
|
@@ -5501,22 +5828,22 @@ Run ${pc7.cyan(`wraps email domains verify --domain ${options.domain}`)} to chec
|
|
|
5501
5828
|
);
|
|
5502
5829
|
const dkimTokens = identity.DkimAttributes?.Tokens || [];
|
|
5503
5830
|
progress.stop();
|
|
5504
|
-
|
|
5831
|
+
clack7.outro(pc8.green(`\u2713 Domain ${options.domain} added successfully!`));
|
|
5505
5832
|
console.log(`
|
|
5506
|
-
${
|
|
5833
|
+
${pc8.bold("Next steps:")}
|
|
5507
5834
|
`);
|
|
5508
5835
|
console.log("1. Add the following DKIM records to your DNS:\n");
|
|
5509
5836
|
for (const token of dkimTokens) {
|
|
5510
|
-
console.log(` ${
|
|
5837
|
+
console.log(` ${pc8.cyan(`${token}._domainkey.${options.domain}`)}`);
|
|
5511
5838
|
console.log(
|
|
5512
|
-
` ${
|
|
5839
|
+
` ${pc8.dim("Type:")} CNAME ${pc8.dim("Value:")} ${token}.dkim.amazonses.com
|
|
5513
5840
|
`
|
|
5514
5841
|
);
|
|
5515
5842
|
}
|
|
5516
5843
|
console.log(
|
|
5517
|
-
`2. Verify DNS propagation: ${
|
|
5844
|
+
`2. Verify DNS propagation: ${pc8.cyan(`wraps email domains verify --domain ${options.domain}`)}`
|
|
5518
5845
|
);
|
|
5519
|
-
console.log(`3. Check status: ${
|
|
5846
|
+
console.log(`3. Check status: ${pc8.cyan("wraps email status")}
|
|
5520
5847
|
`);
|
|
5521
5848
|
trackCommand("email:domains:add", {
|
|
5522
5849
|
success: true
|
|
@@ -5531,7 +5858,7 @@ ${pc7.bold("Next steps:")}
|
|
|
5531
5858
|
}
|
|
5532
5859
|
}
|
|
5533
5860
|
async function listDomains() {
|
|
5534
|
-
|
|
5861
|
+
clack7.intro(pc8.bold("SES Email Domains"));
|
|
5535
5862
|
const progress = new DeploymentProgress();
|
|
5536
5863
|
const region = await getAWSRegion();
|
|
5537
5864
|
const sesClient = new SESv2Client2({ region });
|
|
@@ -5551,10 +5878,10 @@ async function listDomains() {
|
|
|
5551
5878
|
);
|
|
5552
5879
|
progress.stop();
|
|
5553
5880
|
if (domains.length === 0) {
|
|
5554
|
-
|
|
5881
|
+
clack7.outro("No domains found in SES");
|
|
5555
5882
|
console.log(
|
|
5556
5883
|
`
|
|
5557
|
-
Run ${
|
|
5884
|
+
Run ${pc8.cyan("wraps email domains add <domain>")} to add a domain.
|
|
5558
5885
|
`
|
|
5559
5886
|
);
|
|
5560
5887
|
return;
|
|
@@ -5582,17 +5909,17 @@ Run ${pc7.cyan("wraps email domains add <domain>")} to add a domain.
|
|
|
5582
5909
|
})
|
|
5583
5910
|
);
|
|
5584
5911
|
const domainLines = domainDetails.map((domain) => {
|
|
5585
|
-
const statusIcon = domain.verified ?
|
|
5586
|
-
const dkimIcon = domain.dkimStatus === "SUCCESS" ?
|
|
5587
|
-
return ` ${statusIcon} ${
|
|
5912
|
+
const statusIcon = domain.verified ? pc8.green("\u2713") : pc8.yellow("\u23F1");
|
|
5913
|
+
const dkimIcon = domain.dkimStatus === "SUCCESS" ? pc8.green("\u2713") : pc8.yellow("\u23F1");
|
|
5914
|
+
return ` ${statusIcon} ${pc8.bold(domain.name)} DKIM: ${dkimIcon} ${domain.dkimStatus}`;
|
|
5588
5915
|
});
|
|
5589
|
-
|
|
5916
|
+
clack7.note(
|
|
5590
5917
|
domainLines.join("\n"),
|
|
5591
5918
|
`${domains.length} domain(s) in ${region}`
|
|
5592
5919
|
);
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
`Run ${
|
|
5920
|
+
clack7.outro(
|
|
5921
|
+
pc8.dim(
|
|
5922
|
+
`Run ${pc8.cyan("wraps email domains verify --domain <domain>")} for details`
|
|
5596
5923
|
)
|
|
5597
5924
|
);
|
|
5598
5925
|
trackCommand("email:domains:list", {
|
|
@@ -5606,7 +5933,7 @@ Run ${pc7.cyan("wraps email domains add <domain>")} to add a domain.
|
|
|
5606
5933
|
}
|
|
5607
5934
|
}
|
|
5608
5935
|
async function getDkim(options) {
|
|
5609
|
-
|
|
5936
|
+
clack7.intro(pc8.bold(`DKIM Tokens for ${options.domain}`));
|
|
5610
5937
|
const progress = new DeploymentProgress();
|
|
5611
5938
|
const region = await getAWSRegion();
|
|
5612
5939
|
const sesClient = new SESv2Client2({ region });
|
|
@@ -5624,23 +5951,23 @@ async function getDkim(options) {
|
|
|
5624
5951
|
const dkimStatus = identity.DkimAttributes?.Status || "PENDING";
|
|
5625
5952
|
progress.stop();
|
|
5626
5953
|
if (dkimTokens.length === 0) {
|
|
5627
|
-
|
|
5954
|
+
clack7.outro(pc8.yellow("No DKIM tokens found for this domain"));
|
|
5628
5955
|
return;
|
|
5629
5956
|
}
|
|
5630
|
-
const statusLine = `${
|
|
5631
|
-
|
|
5957
|
+
const statusLine = `${pc8.bold("DKIM Status:")} ${dkimStatus === "SUCCESS" ? pc8.green("\u2713 Verified") : pc8.yellow(`\u23F1 ${dkimStatus}`)}`;
|
|
5958
|
+
clack7.note(statusLine, "Status");
|
|
5632
5959
|
console.log(`
|
|
5633
|
-
${
|
|
5960
|
+
${pc8.bold("DNS Records to add:")}
|
|
5634
5961
|
`);
|
|
5635
5962
|
for (const token of dkimTokens) {
|
|
5636
|
-
console.log(`${
|
|
5637
|
-
console.log(` ${
|
|
5638
|
-
console.log(` ${
|
|
5963
|
+
console.log(`${pc8.cyan(`${token}._domainkey.${options.domain}`)}`);
|
|
5964
|
+
console.log(` ${pc8.dim("Type:")} CNAME`);
|
|
5965
|
+
console.log(` ${pc8.dim("Value:")} ${token}.dkim.amazonses.com
|
|
5639
5966
|
`);
|
|
5640
5967
|
}
|
|
5641
5968
|
if (dkimStatus !== "SUCCESS") {
|
|
5642
5969
|
console.log(
|
|
5643
|
-
`${
|
|
5970
|
+
`${pc8.dim("After adding these records, run:")} ${pc8.cyan(`wraps email domains verify --domain ${options.domain}`)}
|
|
5644
5971
|
`
|
|
5645
5972
|
);
|
|
5646
5973
|
}
|
|
@@ -5652,10 +5979,10 @@ ${pc7.bold("DNS Records to add:")}
|
|
|
5652
5979
|
progress.stop();
|
|
5653
5980
|
trackCommand("email:domains:get-dkim", { success: false });
|
|
5654
5981
|
if (error.name === "NotFoundException") {
|
|
5655
|
-
|
|
5982
|
+
clack7.log.error(`Domain ${options.domain} not found in SES`);
|
|
5656
5983
|
console.log(
|
|
5657
5984
|
`
|
|
5658
|
-
Run ${
|
|
5985
|
+
Run ${pc8.cyan(`wraps email domains add ${options.domain}`)} to add this domain.
|
|
5659
5986
|
`
|
|
5660
5987
|
);
|
|
5661
5988
|
process.exit(1);
|
|
@@ -5665,7 +5992,7 @@ Run ${pc7.cyan(`wraps email domains add ${options.domain}`)} to add this domain.
|
|
|
5665
5992
|
}
|
|
5666
5993
|
}
|
|
5667
5994
|
async function removeDomain(options) {
|
|
5668
|
-
|
|
5995
|
+
clack7.intro(pc8.bold(`Remove domain ${options.domain} from SES`));
|
|
5669
5996
|
const progress = new DeploymentProgress();
|
|
5670
5997
|
const region = await getAWSRegion();
|
|
5671
5998
|
const sesClient = new SESv2Client2({ region });
|
|
@@ -5677,12 +6004,12 @@ async function removeDomain(options) {
|
|
|
5677
6004
|
});
|
|
5678
6005
|
progress.stop();
|
|
5679
6006
|
if (!options.force) {
|
|
5680
|
-
const shouldContinue = await
|
|
5681
|
-
message: `Are you sure you want to remove ${
|
|
6007
|
+
const shouldContinue = await clack7.confirm({
|
|
6008
|
+
message: `Are you sure you want to remove ${pc8.red(options.domain)} from SES?`,
|
|
5682
6009
|
initialValue: false
|
|
5683
6010
|
});
|
|
5684
|
-
if (
|
|
5685
|
-
|
|
6011
|
+
if (clack7.isCancel(shouldContinue) || !shouldContinue) {
|
|
6012
|
+
clack7.cancel("Operation cancelled");
|
|
5686
6013
|
process.exit(0);
|
|
5687
6014
|
}
|
|
5688
6015
|
}
|
|
@@ -5695,7 +6022,7 @@ async function removeDomain(options) {
|
|
|
5695
6022
|
);
|
|
5696
6023
|
});
|
|
5697
6024
|
progress.stop();
|
|
5698
|
-
|
|
6025
|
+
clack7.outro(pc8.green(`\u2713 Domain ${options.domain} removed successfully`));
|
|
5699
6026
|
trackCommand("email:domains:remove", {
|
|
5700
6027
|
success: true
|
|
5701
6028
|
});
|
|
@@ -5704,7 +6031,7 @@ async function removeDomain(options) {
|
|
|
5704
6031
|
progress.stop();
|
|
5705
6032
|
trackCommand("email:domains:remove", { success: false });
|
|
5706
6033
|
if (error.name === "NotFoundException") {
|
|
5707
|
-
|
|
6034
|
+
clack7.log.error(`Domain ${options.domain} not found in SES`);
|
|
5708
6035
|
process.exit(1);
|
|
5709
6036
|
return;
|
|
5710
6037
|
}
|
|
@@ -5714,9 +6041,9 @@ async function removeDomain(options) {
|
|
|
5714
6041
|
|
|
5715
6042
|
// src/commands/email/init.ts
|
|
5716
6043
|
init_esm_shims();
|
|
5717
|
-
import * as
|
|
5718
|
-
import * as
|
|
5719
|
-
import
|
|
6044
|
+
import * as clack8 from "@clack/prompts";
|
|
6045
|
+
import * as pulumi8 from "@pulumi/pulumi";
|
|
6046
|
+
import pc9 from "picocolors";
|
|
5720
6047
|
init_events();
|
|
5721
6048
|
init_costs();
|
|
5722
6049
|
init_presets();
|
|
@@ -5725,8 +6052,8 @@ init_errors();
|
|
|
5725
6052
|
init_prompts();
|
|
5726
6053
|
async function init(options) {
|
|
5727
6054
|
const startTime = Date.now();
|
|
5728
|
-
|
|
5729
|
-
|
|
6055
|
+
clack8.intro(
|
|
6056
|
+
pc9.bold(
|
|
5730
6057
|
options.preview ? "Wraps Email Infrastructure Preview" : "Wraps Email Infrastructure Setup"
|
|
5731
6058
|
)
|
|
5732
6059
|
);
|
|
@@ -5742,7 +6069,7 @@ async function init(options) {
|
|
|
5742
6069
|
"Validating AWS credentials",
|
|
5743
6070
|
async () => validateAWSCredentials()
|
|
5744
6071
|
);
|
|
5745
|
-
progress.info(`Connected to AWS account: ${
|
|
6072
|
+
progress.info(`Connected to AWS account: ${pc9.cyan(identity.accountId)}`);
|
|
5746
6073
|
let provider = options.provider;
|
|
5747
6074
|
if (!provider) {
|
|
5748
6075
|
provider = await promptProvider();
|
|
@@ -5765,12 +6092,12 @@ async function init(options) {
|
|
|
5765
6092
|
region
|
|
5766
6093
|
);
|
|
5767
6094
|
if (existingConnection) {
|
|
5768
|
-
|
|
5769
|
-
`Connection already exists for account ${
|
|
6095
|
+
clack8.log.warn(
|
|
6096
|
+
`Connection already exists for account ${pc9.cyan(identity.accountId)} in region ${pc9.cyan(region)}`
|
|
5770
6097
|
);
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
6098
|
+
clack8.log.info(`Created: ${existingConnection.timestamp}`);
|
|
6099
|
+
clack8.log.info(`Use ${pc9.cyan("wraps status")} to view current setup`);
|
|
6100
|
+
clack8.log.info(`Use ${pc9.cyan("wraps upgrade")} to add more features`);
|
|
5774
6101
|
process.exit(0);
|
|
5775
6102
|
}
|
|
5776
6103
|
let preset = options.preset;
|
|
@@ -5791,15 +6118,15 @@ async function init(options) {
|
|
|
5791
6118
|
}
|
|
5792
6119
|
const estimatedVolume = await promptEstimatedVolume();
|
|
5793
6120
|
progress.info(`
|
|
5794
|
-
${
|
|
6121
|
+
${pc9.bold("Cost Estimate:")}`);
|
|
5795
6122
|
const costSummary = getCostSummary(emailConfig, estimatedVolume);
|
|
5796
|
-
|
|
6123
|
+
clack8.log.info(costSummary);
|
|
5797
6124
|
const warnings = validateConfig(emailConfig);
|
|
5798
6125
|
if (warnings.length > 0) {
|
|
5799
6126
|
progress.info(`
|
|
5800
|
-
${
|
|
6127
|
+
${pc9.yellow(pc9.bold("Configuration Warnings:"))}`);
|
|
5801
6128
|
for (const warning of warnings) {
|
|
5802
|
-
|
|
6129
|
+
clack8.log.warn(warning);
|
|
5803
6130
|
}
|
|
5804
6131
|
}
|
|
5805
6132
|
const metadata = createConnectionMetadata(
|
|
@@ -5815,7 +6142,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5815
6142
|
if (!(options.yes || options.preview)) {
|
|
5816
6143
|
const confirmed = await confirmDeploy();
|
|
5817
6144
|
if (!confirmed) {
|
|
5818
|
-
|
|
6145
|
+
clack8.cancel("Deployment cancelled.");
|
|
5819
6146
|
process.exit(0);
|
|
5820
6147
|
}
|
|
5821
6148
|
}
|
|
@@ -5831,7 +6158,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5831
6158
|
"Generating infrastructure preview",
|
|
5832
6159
|
async () => {
|
|
5833
6160
|
await ensurePulumiWorkDir();
|
|
5834
|
-
const stack = await
|
|
6161
|
+
const stack = await pulumi8.automation.LocalWorkspace.createOrSelectStack(
|
|
5835
6162
|
{
|
|
5836
6163
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
5837
6164
|
projectName: "wraps-email",
|
|
@@ -5872,8 +6199,8 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5872
6199
|
costEstimate: costSummary,
|
|
5873
6200
|
commandName: "wraps email init"
|
|
5874
6201
|
});
|
|
5875
|
-
|
|
5876
|
-
|
|
6202
|
+
clack8.outro(
|
|
6203
|
+
pc9.green("Preview complete. Run without --preview to deploy.")
|
|
5877
6204
|
);
|
|
5878
6205
|
trackServiceInit("email", true, {
|
|
5879
6206
|
preset,
|
|
@@ -5896,7 +6223,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5896
6223
|
"Deploying infrastructure (this may take 2-3 minutes)",
|
|
5897
6224
|
async () => {
|
|
5898
6225
|
await ensurePulumiWorkDir();
|
|
5899
|
-
const stack = await
|
|
6226
|
+
const stack = await pulumi8.automation.LocalWorkspace.createOrSelectStack(
|
|
5900
6227
|
{
|
|
5901
6228
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
5902
6229
|
projectName: "wraps-email",
|
|
@@ -5967,13 +6294,19 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5967
6294
|
}
|
|
5968
6295
|
if (metadata.services.email) {
|
|
5969
6296
|
metadata.services.email.pulumiStackName = `wraps-${identity.accountId}-${region}`;
|
|
6297
|
+
if (outputs.mailFromDomain) {
|
|
6298
|
+
metadata.services.email.config.mailFromDomain = outputs.mailFromDomain;
|
|
6299
|
+
}
|
|
6300
|
+
if (outputs.customTrackingDomain && metadata.services.email.config.tracking) {
|
|
6301
|
+
metadata.services.email.config.tracking.customRedirectDomain = outputs.customTrackingDomain;
|
|
6302
|
+
}
|
|
5970
6303
|
}
|
|
5971
6304
|
await saveConnectionMetadata(metadata);
|
|
5972
6305
|
progress.info("Connection metadata saved for upgrade and restore capability");
|
|
5973
6306
|
let dnsAutoCreated = false;
|
|
5974
6307
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
5975
|
-
const { findHostedZone:
|
|
5976
|
-
const hostedZone = await
|
|
6308
|
+
const { findHostedZone: findHostedZone3, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
6309
|
+
const hostedZone = await findHostedZone3(outputs.domain, region);
|
|
5977
6310
|
if (hostedZone) {
|
|
5978
6311
|
try {
|
|
5979
6312
|
progress.start("Creating DNS records in Route53");
|
|
@@ -5989,7 +6322,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5989
6322
|
dnsAutoCreated = true;
|
|
5990
6323
|
} catch (error) {
|
|
5991
6324
|
progress.fail("Failed to create DNS records in Route53");
|
|
5992
|
-
|
|
6325
|
+
clack8.log.warn(`Could not auto-create DNS records: ${error.message}`);
|
|
5993
6326
|
}
|
|
5994
6327
|
}
|
|
5995
6328
|
}
|
|
@@ -6042,20 +6375,20 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
6042
6375
|
init_esm_shims();
|
|
6043
6376
|
init_events();
|
|
6044
6377
|
init_aws();
|
|
6045
|
-
import * as
|
|
6046
|
-
import * as
|
|
6047
|
-
import
|
|
6378
|
+
import * as clack9 from "@clack/prompts";
|
|
6379
|
+
import * as pulumi9 from "@pulumi/pulumi";
|
|
6380
|
+
import pc10 from "picocolors";
|
|
6048
6381
|
async function restore(options) {
|
|
6049
6382
|
const startTime = Date.now();
|
|
6050
|
-
|
|
6051
|
-
|
|
6383
|
+
clack9.intro(
|
|
6384
|
+
pc10.bold(
|
|
6052
6385
|
options.preview ? "Wraps Restore Preview" : "Wraps Restore - Remove Wraps Infrastructure"
|
|
6053
6386
|
)
|
|
6054
6387
|
);
|
|
6055
|
-
|
|
6056
|
-
`${
|
|
6388
|
+
clack9.log.info(
|
|
6389
|
+
`${pc10.yellow("Note:")} This will remove all Wraps-managed infrastructure.`
|
|
6057
6390
|
);
|
|
6058
|
-
|
|
6391
|
+
clack9.log.info(
|
|
6059
6392
|
"Your original AWS resources remain untouched (Wraps never modifies them).\n"
|
|
6060
6393
|
);
|
|
6061
6394
|
const progress = new DeploymentProgress();
|
|
@@ -6063,7 +6396,7 @@ async function restore(options) {
|
|
|
6063
6396
|
"Validating AWS credentials",
|
|
6064
6397
|
async () => validateAWSCredentials()
|
|
6065
6398
|
);
|
|
6066
|
-
progress.info(`Connected to AWS account: ${
|
|
6399
|
+
progress.info(`Connected to AWS account: ${pc10.cyan(identity.accountId)}`);
|
|
6067
6400
|
let region = options.region;
|
|
6068
6401
|
if (!region) {
|
|
6069
6402
|
const defaultRegion = await getAWSRegion();
|
|
@@ -6071,40 +6404,40 @@ async function restore(options) {
|
|
|
6071
6404
|
}
|
|
6072
6405
|
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
6073
6406
|
if (!metadata) {
|
|
6074
|
-
|
|
6075
|
-
`No Wraps connection found for account ${
|
|
6407
|
+
clack9.log.error(
|
|
6408
|
+
`No Wraps connection found for account ${pc10.cyan(identity.accountId)} in region ${pc10.cyan(region)}`
|
|
6076
6409
|
);
|
|
6077
|
-
|
|
6078
|
-
`Use ${
|
|
6410
|
+
clack9.log.info(
|
|
6411
|
+
`Use ${pc10.cyan("wraps email init")} or ${pc10.cyan("wraps email connect")} to create a connection first.`
|
|
6079
6412
|
);
|
|
6080
6413
|
process.exit(1);
|
|
6081
6414
|
}
|
|
6082
6415
|
progress.info(`Found connection created: ${metadata.timestamp}`);
|
|
6083
6416
|
console.log(
|
|
6084
6417
|
`
|
|
6085
|
-
${
|
|
6418
|
+
${pc10.bold("The following Wraps resources will be removed:")}
|
|
6086
6419
|
`
|
|
6087
6420
|
);
|
|
6088
6421
|
if (metadata.services.email?.config.tracking?.enabled) {
|
|
6089
|
-
console.log(` ${
|
|
6422
|
+
console.log(` ${pc10.cyan("\u2713")} Configuration Set (wraps-email-tracking)`);
|
|
6090
6423
|
}
|
|
6091
6424
|
if (metadata.services.email?.config.eventTracking?.dynamoDBHistory) {
|
|
6092
|
-
console.log(` ${
|
|
6425
|
+
console.log(` ${pc10.cyan("\u2713")} DynamoDB Table (wraps-email-history)`);
|
|
6093
6426
|
}
|
|
6094
6427
|
if (metadata.services.email?.config.eventTracking?.enabled) {
|
|
6095
|
-
console.log(` ${
|
|
6096
|
-
console.log(` ${
|
|
6097
|
-
console.log(` ${
|
|
6428
|
+
console.log(` ${pc10.cyan("\u2713")} EventBridge Rules`);
|
|
6429
|
+
console.log(` ${pc10.cyan("\u2713")} SQS Queues`);
|
|
6430
|
+
console.log(` ${pc10.cyan("\u2713")} Lambda Functions`);
|
|
6098
6431
|
}
|
|
6099
|
-
console.log(` ${
|
|
6432
|
+
console.log(` ${pc10.cyan("\u2713")} IAM Role (wraps-email-role)`);
|
|
6100
6433
|
console.log("");
|
|
6101
6434
|
if (!(options.force || options.preview)) {
|
|
6102
|
-
const confirmed = await
|
|
6435
|
+
const confirmed = await clack9.confirm({
|
|
6103
6436
|
message: "Proceed with removal? This cannot be undone.",
|
|
6104
6437
|
initialValue: false
|
|
6105
6438
|
});
|
|
6106
|
-
if (
|
|
6107
|
-
|
|
6439
|
+
if (clack9.isCancel(confirmed) || !confirmed) {
|
|
6440
|
+
clack9.cancel("Removal cancelled.");
|
|
6108
6441
|
process.exit(0);
|
|
6109
6442
|
}
|
|
6110
6443
|
}
|
|
@@ -6114,7 +6447,7 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
6114
6447
|
const previewResult = await progress.execute(
|
|
6115
6448
|
"Generating removal preview",
|
|
6116
6449
|
async () => {
|
|
6117
|
-
const stack = await
|
|
6450
|
+
const stack = await pulumi9.automation.LocalWorkspace.selectStack(
|
|
6118
6451
|
{
|
|
6119
6452
|
stackName: metadata.services.email.pulumiStackName,
|
|
6120
6453
|
projectName: "wraps-email",
|
|
@@ -6140,8 +6473,8 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
6140
6473
|
costEstimate: "Monthly cost after removal: $0.00",
|
|
6141
6474
|
commandName: "wraps email restore"
|
|
6142
6475
|
});
|
|
6143
|
-
|
|
6144
|
-
|
|
6476
|
+
clack9.outro(
|
|
6477
|
+
pc10.green(
|
|
6145
6478
|
"Preview complete. Run without --preview to remove infrastructure."
|
|
6146
6479
|
)
|
|
6147
6480
|
);
|
|
@@ -6163,7 +6496,7 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
6163
6496
|
if (!metadata.services.email?.pulumiStackName) {
|
|
6164
6497
|
throw new Error("No Pulumi stack name found in metadata");
|
|
6165
6498
|
}
|
|
6166
|
-
const stack = await
|
|
6499
|
+
const stack = await pulumi9.automation.LocalWorkspace.selectStack(
|
|
6167
6500
|
{
|
|
6168
6501
|
stackName: metadata.services.email.pulumiStackName,
|
|
6169
6502
|
projectName: "wraps-email",
|
|
@@ -6195,13 +6528,13 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
6195
6528
|
progress.info("Connection metadata deleted");
|
|
6196
6529
|
console.log(
|
|
6197
6530
|
`
|
|
6198
|
-
${
|
|
6531
|
+
${pc10.green("\u2713")} ${pc10.bold("Infrastructure removed successfully!")}
|
|
6199
6532
|
`
|
|
6200
6533
|
);
|
|
6201
6534
|
console.log(
|
|
6202
|
-
`${
|
|
6535
|
+
`${pc10.dim("All Wraps resources have been deleted from your AWS account.")}`
|
|
6203
6536
|
);
|
|
6204
|
-
console.log(`${
|
|
6537
|
+
console.log(`${pc10.dim("Your original AWS resources remain unchanged.")}
|
|
6205
6538
|
`);
|
|
6206
6539
|
trackServiceRemoved("email", {
|
|
6207
6540
|
reason: "user_initiated",
|
|
@@ -6209,11 +6542,102 @@ ${pc9.green("\u2713")} ${pc9.bold("Infrastructure removed successfully!")}
|
|
|
6209
6542
|
});
|
|
6210
6543
|
}
|
|
6211
6544
|
|
|
6545
|
+
// src/commands/email/status.ts
|
|
6546
|
+
init_esm_shims();
|
|
6547
|
+
init_events();
|
|
6548
|
+
init_aws();
|
|
6549
|
+
import * as clack10 from "@clack/prompts";
|
|
6550
|
+
import * as pulumi10 from "@pulumi/pulumi";
|
|
6551
|
+
import pc11 from "picocolors";
|
|
6552
|
+
async function emailStatus(_options) {
|
|
6553
|
+
const startTime = Date.now();
|
|
6554
|
+
const progress = new DeploymentProgress();
|
|
6555
|
+
clack10.intro(pc11.bold("Wraps Email Status"));
|
|
6556
|
+
const identity = await progress.execute(
|
|
6557
|
+
"Loading email infrastructure status",
|
|
6558
|
+
async () => validateAWSCredentials()
|
|
6559
|
+
);
|
|
6560
|
+
const region = await getAWSRegion();
|
|
6561
|
+
let stackOutputs = {};
|
|
6562
|
+
try {
|
|
6563
|
+
await ensurePulumiWorkDir();
|
|
6564
|
+
const stack = await pulumi10.automation.LocalWorkspace.selectStack({
|
|
6565
|
+
stackName: `wraps-${identity.accountId}-${region}`,
|
|
6566
|
+
workDir: getPulumiWorkDir()
|
|
6567
|
+
});
|
|
6568
|
+
stackOutputs = await stack.outputs();
|
|
6569
|
+
} catch (_error) {
|
|
6570
|
+
progress.stop();
|
|
6571
|
+
clack10.log.error("No email infrastructure found");
|
|
6572
|
+
console.log(
|
|
6573
|
+
`
|
|
6574
|
+
Run ${pc11.cyan("wraps email init")} to deploy email infrastructure.
|
|
6575
|
+
`
|
|
6576
|
+
);
|
|
6577
|
+
process.exit(1);
|
|
6578
|
+
}
|
|
6579
|
+
const domains = await listSESDomains(region);
|
|
6580
|
+
const { SESv2Client: SESv2Client5, GetEmailIdentityCommand: GetEmailIdentityCommand4 } = await import("@aws-sdk/client-sesv2");
|
|
6581
|
+
const sesv2Client = new SESv2Client5({ region });
|
|
6582
|
+
const domainsWithTokens = await Promise.all(
|
|
6583
|
+
domains.map(async (d) => {
|
|
6584
|
+
try {
|
|
6585
|
+
const identity2 = await sesv2Client.send(
|
|
6586
|
+
new GetEmailIdentityCommand4({ EmailIdentity: d.domain })
|
|
6587
|
+
);
|
|
6588
|
+
return {
|
|
6589
|
+
domain: d.domain,
|
|
6590
|
+
status: d.verified ? "verified" : "pending",
|
|
6591
|
+
dkimTokens: identity2.DkimAttributes?.Tokens || [],
|
|
6592
|
+
mailFromDomain: identity2.MailFromAttributes?.MailFromDomain,
|
|
6593
|
+
mailFromStatus: identity2.MailFromAttributes?.MailFromDomainStatus
|
|
6594
|
+
};
|
|
6595
|
+
} catch (_error) {
|
|
6596
|
+
return {
|
|
6597
|
+
domain: d.domain,
|
|
6598
|
+
status: d.verified ? "verified" : "pending",
|
|
6599
|
+
dkimTokens: void 0,
|
|
6600
|
+
mailFromDomain: void 0,
|
|
6601
|
+
mailFromStatus: void 0
|
|
6602
|
+
};
|
|
6603
|
+
}
|
|
6604
|
+
})
|
|
6605
|
+
);
|
|
6606
|
+
const integrationLevel = stackOutputs.configSetName ? "enhanced" : "dashboard-only";
|
|
6607
|
+
progress.stop();
|
|
6608
|
+
displayStatus({
|
|
6609
|
+
integrationLevel,
|
|
6610
|
+
region,
|
|
6611
|
+
domains: domainsWithTokens,
|
|
6612
|
+
resources: {
|
|
6613
|
+
roleArn: stackOutputs.roleArn?.value,
|
|
6614
|
+
configSetName: stackOutputs.configSetName?.value,
|
|
6615
|
+
tableName: stackOutputs.tableName?.value,
|
|
6616
|
+
lambdaFunctions: stackOutputs.lambdaFunctions?.value?.length || 0,
|
|
6617
|
+
snsTopics: integrationLevel === "enhanced" ? 1 : 0,
|
|
6618
|
+
archiveArn: stackOutputs.archiveArn?.value,
|
|
6619
|
+
archivingEnabled: stackOutputs.archivingEnabled?.value,
|
|
6620
|
+
archiveRetention: stackOutputs.archiveRetention?.value
|
|
6621
|
+
},
|
|
6622
|
+
tracking: stackOutputs.customTrackingDomain?.value ? {
|
|
6623
|
+
customTrackingDomain: stackOutputs.customTrackingDomain?.value,
|
|
6624
|
+
httpsEnabled: stackOutputs.httpsTrackingEnabled?.value,
|
|
6625
|
+
cloudFrontDomain: stackOutputs.cloudFrontDomain?.value
|
|
6626
|
+
} : void 0
|
|
6627
|
+
});
|
|
6628
|
+
trackCommand("email:status", {
|
|
6629
|
+
success: true,
|
|
6630
|
+
domain_count: domainsWithTokens.length,
|
|
6631
|
+
integration_level: integrationLevel,
|
|
6632
|
+
duration_ms: Date.now() - startTime
|
|
6633
|
+
});
|
|
6634
|
+
}
|
|
6635
|
+
|
|
6212
6636
|
// src/commands/email/upgrade.ts
|
|
6213
6637
|
init_esm_shims();
|
|
6214
|
-
import * as
|
|
6215
|
-
import * as
|
|
6216
|
-
import
|
|
6638
|
+
import * as clack11 from "@clack/prompts";
|
|
6639
|
+
import * as pulumi11 from "@pulumi/pulumi";
|
|
6640
|
+
import pc12 from "picocolors";
|
|
6217
6641
|
init_events();
|
|
6218
6642
|
init_costs();
|
|
6219
6643
|
init_presets();
|
|
@@ -6223,8 +6647,8 @@ init_prompts();
|
|
|
6223
6647
|
async function upgrade(options) {
|
|
6224
6648
|
const startTime = Date.now();
|
|
6225
6649
|
let upgradeAction = "";
|
|
6226
|
-
|
|
6227
|
-
|
|
6650
|
+
clack11.intro(
|
|
6651
|
+
pc12.bold(
|
|
6228
6652
|
options.preview ? "Wraps Upgrade Preview" : "Wraps Upgrade - Enhance Your Email Infrastructure"
|
|
6229
6653
|
)
|
|
6230
6654
|
);
|
|
@@ -6240,7 +6664,7 @@ async function upgrade(options) {
|
|
|
6240
6664
|
"Validating AWS credentials",
|
|
6241
6665
|
async () => validateAWSCredentials()
|
|
6242
6666
|
);
|
|
6243
|
-
progress.info(`Connected to AWS account: ${
|
|
6667
|
+
progress.info(`Connected to AWS account: ${pc12.cyan(identity.accountId)}`);
|
|
6244
6668
|
let region = options.region;
|
|
6245
6669
|
if (!region) {
|
|
6246
6670
|
const defaultRegion = await getAWSRegion();
|
|
@@ -6248,55 +6672,55 @@ async function upgrade(options) {
|
|
|
6248
6672
|
}
|
|
6249
6673
|
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
6250
6674
|
if (!metadata) {
|
|
6251
|
-
|
|
6252
|
-
`No Wraps connection found for account ${
|
|
6675
|
+
clack11.log.error(
|
|
6676
|
+
`No Wraps connection found for account ${pc12.cyan(identity.accountId)} in region ${pc12.cyan(region)}`
|
|
6253
6677
|
);
|
|
6254
|
-
|
|
6255
|
-
`Use ${
|
|
6678
|
+
clack11.log.info(
|
|
6679
|
+
`Use ${pc12.cyan("wraps email init")} to create new infrastructure or ${pc12.cyan("wraps email connect")} to connect existing.`
|
|
6256
6680
|
);
|
|
6257
6681
|
process.exit(1);
|
|
6258
6682
|
}
|
|
6259
6683
|
progress.info(`Found existing connection created: ${metadata.timestamp}`);
|
|
6260
6684
|
console.log(`
|
|
6261
|
-
${
|
|
6685
|
+
${pc12.bold("Current Configuration:")}
|
|
6262
6686
|
`);
|
|
6263
6687
|
if (metadata.services.email?.preset) {
|
|
6264
|
-
console.log(` Preset: ${
|
|
6688
|
+
console.log(` Preset: ${pc12.cyan(metadata.services.email?.preset)}`);
|
|
6265
6689
|
} else {
|
|
6266
|
-
console.log(` Preset: ${
|
|
6690
|
+
console.log(` Preset: ${pc12.cyan("custom")}`);
|
|
6267
6691
|
}
|
|
6268
6692
|
const config2 = metadata.services.email?.config;
|
|
6269
6693
|
if (!config2) {
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
`Use ${
|
|
6694
|
+
clack11.log.error("No email configuration found in metadata");
|
|
6695
|
+
clack11.log.info(
|
|
6696
|
+
`Use ${pc12.cyan("wraps email init")} to create new infrastructure.`
|
|
6273
6697
|
);
|
|
6274
6698
|
process.exit(1);
|
|
6275
6699
|
}
|
|
6276
6700
|
if (config2.domain) {
|
|
6277
|
-
console.log(` Sending Domain: ${
|
|
6701
|
+
console.log(` Sending Domain: ${pc12.cyan(config2.domain)}`);
|
|
6278
6702
|
}
|
|
6279
6703
|
if (config2.tracking?.enabled) {
|
|
6280
|
-
console.log(` ${
|
|
6704
|
+
console.log(` ${pc12.green("\u2713")} Open & Click Tracking`);
|
|
6281
6705
|
if (config2.tracking.customRedirectDomain) {
|
|
6282
6706
|
console.log(
|
|
6283
|
-
` ${
|
|
6707
|
+
` ${pc12.dim("\u2514\u2500")} Custom domain: ${pc12.cyan(config2.tracking.customRedirectDomain)}`
|
|
6284
6708
|
);
|
|
6285
6709
|
}
|
|
6286
6710
|
}
|
|
6287
6711
|
if (config2.suppressionList?.enabled) {
|
|
6288
|
-
console.log(` ${
|
|
6712
|
+
console.log(` ${pc12.green("\u2713")} Bounce/Complaint Suppression`);
|
|
6289
6713
|
}
|
|
6290
6714
|
if (config2.eventTracking?.enabled) {
|
|
6291
|
-
console.log(` ${
|
|
6715
|
+
console.log(` ${pc12.green("\u2713")} Event Tracking (EventBridge)`);
|
|
6292
6716
|
if (config2.eventTracking.dynamoDBHistory) {
|
|
6293
6717
|
console.log(
|
|
6294
|
-
` ${
|
|
6718
|
+
` ${pc12.dim("\u2514\u2500")} Email History: ${pc12.cyan(config2.eventTracking.archiveRetention || "90days")}`
|
|
6295
6719
|
);
|
|
6296
6720
|
}
|
|
6297
6721
|
}
|
|
6298
6722
|
if (config2.dedicatedIp) {
|
|
6299
|
-
console.log(` ${
|
|
6723
|
+
console.log(` ${pc12.green("\u2713")} Dedicated IP Address`);
|
|
6300
6724
|
}
|
|
6301
6725
|
if (config2.emailArchiving?.enabled) {
|
|
6302
6726
|
const retentionLabel = {
|
|
@@ -6321,15 +6745,15 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6321
6745
|
indefinite: "indefinite",
|
|
6322
6746
|
permanent: "permanent"
|
|
6323
6747
|
}[config2.emailArchiving.retention] || "90 days";
|
|
6324
|
-
console.log(` ${
|
|
6748
|
+
console.log(` ${pc12.green("\u2713")} Email Archiving (${retentionLabel})`);
|
|
6325
6749
|
}
|
|
6326
6750
|
const currentCostData = calculateCosts(config2, 5e4);
|
|
6327
6751
|
console.log(
|
|
6328
6752
|
`
|
|
6329
|
-
Estimated Cost: ${
|
|
6753
|
+
Estimated Cost: ${pc12.cyan(`~${formatCost(currentCostData.total.monthly)}/mo`)}`
|
|
6330
6754
|
);
|
|
6331
6755
|
console.log("");
|
|
6332
|
-
upgradeAction = await
|
|
6756
|
+
upgradeAction = await clack11.select({
|
|
6333
6757
|
message: "What would you like to do?",
|
|
6334
6758
|
options: [
|
|
6335
6759
|
{
|
|
@@ -6369,8 +6793,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6369
6793
|
}
|
|
6370
6794
|
]
|
|
6371
6795
|
});
|
|
6372
|
-
if (
|
|
6373
|
-
|
|
6796
|
+
if (clack11.isCancel(upgradeAction)) {
|
|
6797
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6374
6798
|
process.exit(0);
|
|
6375
6799
|
}
|
|
6376
6800
|
let updatedConfig = { ...config2 };
|
|
@@ -6388,15 +6812,15 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6388
6812
|
disabled: currentPresetIdx >= 0 && idx <= currentPresetIdx ? "Current or lower tier" : void 0
|
|
6389
6813
|
})).filter((p) => !p.disabled);
|
|
6390
6814
|
if (availablePresets.length === 0) {
|
|
6391
|
-
|
|
6815
|
+
clack11.log.warn("Already on highest preset (Enterprise)");
|
|
6392
6816
|
process.exit(0);
|
|
6393
6817
|
}
|
|
6394
|
-
const selectedPreset = await
|
|
6818
|
+
const selectedPreset = await clack11.select({
|
|
6395
6819
|
message: "Select new preset:",
|
|
6396
6820
|
options: availablePresets
|
|
6397
6821
|
});
|
|
6398
|
-
if (
|
|
6399
|
-
|
|
6822
|
+
if (clack11.isCancel(selectedPreset)) {
|
|
6823
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6400
6824
|
process.exit(0);
|
|
6401
6825
|
}
|
|
6402
6826
|
const presetConfig = getPreset(selectedPreset);
|
|
@@ -6406,7 +6830,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6406
6830
|
}
|
|
6407
6831
|
case "archiving": {
|
|
6408
6832
|
if (config2.emailArchiving?.enabled) {
|
|
6409
|
-
const archivingAction = await
|
|
6833
|
+
const archivingAction = await clack11.select({
|
|
6410
6834
|
message: "What would you like to do with email archiving?",
|
|
6411
6835
|
options: [
|
|
6412
6836
|
{
|
|
@@ -6421,17 +6845,17 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6421
6845
|
}
|
|
6422
6846
|
]
|
|
6423
6847
|
});
|
|
6424
|
-
if (
|
|
6425
|
-
|
|
6848
|
+
if (clack11.isCancel(archivingAction)) {
|
|
6849
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6426
6850
|
process.exit(0);
|
|
6427
6851
|
}
|
|
6428
6852
|
if (archivingAction === "disable") {
|
|
6429
|
-
const confirmDisable = await
|
|
6853
|
+
const confirmDisable = await clack11.confirm({
|
|
6430
6854
|
message: "Are you sure? Existing archived emails will remain, but new emails won't be archived.",
|
|
6431
6855
|
initialValue: false
|
|
6432
6856
|
});
|
|
6433
|
-
if (
|
|
6434
|
-
|
|
6857
|
+
if (clack11.isCancel(confirmDisable) || !confirmDisable) {
|
|
6858
|
+
clack11.cancel("Archiving not disabled.");
|
|
6435
6859
|
process.exit(0);
|
|
6436
6860
|
}
|
|
6437
6861
|
updatedConfig = {
|
|
@@ -6442,7 +6866,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6442
6866
|
}
|
|
6443
6867
|
};
|
|
6444
6868
|
} else {
|
|
6445
|
-
const retention = await
|
|
6869
|
+
const retention = await clack11.select({
|
|
6446
6870
|
message: "Email archive retention period:",
|
|
6447
6871
|
options: [
|
|
6448
6872
|
{
|
|
@@ -6478,8 +6902,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6478
6902
|
],
|
|
6479
6903
|
initialValue: config2.emailArchiving.retention
|
|
6480
6904
|
});
|
|
6481
|
-
if (
|
|
6482
|
-
|
|
6905
|
+
if (clack11.isCancel(retention)) {
|
|
6906
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6483
6907
|
process.exit(0);
|
|
6484
6908
|
}
|
|
6485
6909
|
updatedConfig = {
|
|
@@ -6491,19 +6915,19 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6491
6915
|
};
|
|
6492
6916
|
}
|
|
6493
6917
|
} else {
|
|
6494
|
-
const enableArchiving = await
|
|
6918
|
+
const enableArchiving = await clack11.confirm({
|
|
6495
6919
|
message: "Enable email archiving? (Store full email content with HTML for viewing)",
|
|
6496
6920
|
initialValue: true
|
|
6497
6921
|
});
|
|
6498
|
-
if (
|
|
6499
|
-
|
|
6922
|
+
if (clack11.isCancel(enableArchiving)) {
|
|
6923
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6500
6924
|
process.exit(0);
|
|
6501
6925
|
}
|
|
6502
6926
|
if (!enableArchiving) {
|
|
6503
|
-
|
|
6927
|
+
clack11.log.info("Email archiving not enabled.");
|
|
6504
6928
|
process.exit(0);
|
|
6505
6929
|
}
|
|
6506
|
-
const retention = await
|
|
6930
|
+
const retention = await clack11.select({
|
|
6507
6931
|
message: "Email archive retention period:",
|
|
6508
6932
|
options: [
|
|
6509
6933
|
{
|
|
@@ -6539,17 +6963,17 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6539
6963
|
],
|
|
6540
6964
|
initialValue: "90days"
|
|
6541
6965
|
});
|
|
6542
|
-
if (
|
|
6543
|
-
|
|
6966
|
+
if (clack11.isCancel(retention)) {
|
|
6967
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6544
6968
|
process.exit(0);
|
|
6545
6969
|
}
|
|
6546
|
-
|
|
6547
|
-
|
|
6970
|
+
clack11.log.info(
|
|
6971
|
+
pc12.dim(
|
|
6548
6972
|
"Archiving stores full RFC 822 emails with HTML, attachments, and headers"
|
|
6549
6973
|
)
|
|
6550
6974
|
);
|
|
6551
|
-
|
|
6552
|
-
|
|
6975
|
+
clack11.log.info(
|
|
6976
|
+
pc12.dim(
|
|
6553
6977
|
"Cost: $2/GB ingestion + $0.19/GB/month storage (~50KB per email)"
|
|
6554
6978
|
)
|
|
6555
6979
|
);
|
|
@@ -6566,11 +6990,11 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6566
6990
|
}
|
|
6567
6991
|
case "tracking-domain": {
|
|
6568
6992
|
if (!config2.domain) {
|
|
6569
|
-
|
|
6993
|
+
clack11.log.error(
|
|
6570
6994
|
"No sending domain configured. You must configure a sending domain before adding a custom tracking domain."
|
|
6571
6995
|
);
|
|
6572
|
-
|
|
6573
|
-
`Use ${
|
|
6996
|
+
clack11.log.info(
|
|
6997
|
+
`Use ${pc12.cyan("wraps email init")} to set up a sending domain first.`
|
|
6574
6998
|
);
|
|
6575
6999
|
process.exit(1);
|
|
6576
7000
|
}
|
|
@@ -6581,21 +7005,21 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6581
7005
|
);
|
|
6582
7006
|
const sendingDomain = domains.find((d) => d.domain === config2.domain);
|
|
6583
7007
|
if (!sendingDomain?.verified) {
|
|
6584
|
-
|
|
6585
|
-
`Sending domain ${
|
|
7008
|
+
clack11.log.error(
|
|
7009
|
+
`Sending domain ${pc12.cyan(config2.domain)} is not verified.`
|
|
6586
7010
|
);
|
|
6587
|
-
|
|
7011
|
+
clack11.log.info(
|
|
6588
7012
|
"You must verify your sending domain before adding a custom tracking domain."
|
|
6589
7013
|
);
|
|
6590
|
-
|
|
6591
|
-
`Use ${
|
|
7014
|
+
clack11.log.info(
|
|
7015
|
+
`Use ${pc12.cyan("wraps email verify")} to check DNS records and complete verification.`
|
|
6592
7016
|
);
|
|
6593
7017
|
process.exit(1);
|
|
6594
7018
|
}
|
|
6595
7019
|
progress.info(
|
|
6596
|
-
`Sending domain ${
|
|
7020
|
+
`Sending domain ${pc12.cyan(config2.domain)} is verified ${pc12.green("\u2713")}`
|
|
6597
7021
|
);
|
|
6598
|
-
const trackingDomain = await
|
|
7022
|
+
const trackingDomain = await clack11.text({
|
|
6599
7023
|
message: "Custom tracking redirect domain:",
|
|
6600
7024
|
placeholder: "track.yourdomain.com",
|
|
6601
7025
|
initialValue: config2.tracking?.customRedirectDomain || "",
|
|
@@ -6605,62 +7029,62 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6605
7029
|
}
|
|
6606
7030
|
}
|
|
6607
7031
|
});
|
|
6608
|
-
if (
|
|
6609
|
-
|
|
7032
|
+
if (clack11.isCancel(trackingDomain)) {
|
|
7033
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6610
7034
|
process.exit(0);
|
|
6611
7035
|
}
|
|
6612
|
-
const enableHttps = await
|
|
7036
|
+
const enableHttps = await clack11.confirm({
|
|
6613
7037
|
message: "Enable HTTPS tracking with CloudFront + SSL certificate?",
|
|
6614
7038
|
initialValue: true
|
|
6615
7039
|
});
|
|
6616
|
-
if (
|
|
6617
|
-
|
|
7040
|
+
if (clack11.isCancel(enableHttps)) {
|
|
7041
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6618
7042
|
process.exit(0);
|
|
6619
7043
|
}
|
|
6620
7044
|
if (enableHttps) {
|
|
6621
|
-
|
|
6622
|
-
|
|
7045
|
+
clack11.log.info(
|
|
7046
|
+
pc12.dim(
|
|
6623
7047
|
"HTTPS tracking creates a CloudFront distribution with an SSL certificate."
|
|
6624
7048
|
)
|
|
6625
7049
|
);
|
|
6626
|
-
|
|
6627
|
-
|
|
7050
|
+
clack11.log.info(
|
|
7051
|
+
pc12.dim(
|
|
6628
7052
|
"This ensures all tracking links use secure HTTPS connections."
|
|
6629
7053
|
)
|
|
6630
7054
|
);
|
|
6631
|
-
const { findHostedZone:
|
|
7055
|
+
const { findHostedZone: findHostedZone3 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
6632
7056
|
const hostedZone = await progress.execute(
|
|
6633
7057
|
"Checking for Route53 hosted zone",
|
|
6634
|
-
async () => await
|
|
7058
|
+
async () => await findHostedZone3(trackingDomain || config2.domain, region)
|
|
6635
7059
|
);
|
|
6636
7060
|
if (hostedZone) {
|
|
6637
7061
|
progress.info(
|
|
6638
|
-
`Found Route53 hosted zone: ${
|
|
7062
|
+
`Found Route53 hosted zone: ${pc12.cyan(hostedZone.name)} ${pc12.green("\u2713")}`
|
|
6639
7063
|
);
|
|
6640
|
-
|
|
6641
|
-
|
|
7064
|
+
clack11.log.info(
|
|
7065
|
+
pc12.dim(
|
|
6642
7066
|
"DNS records (SSL certificate validation + CloudFront) will be created automatically."
|
|
6643
7067
|
)
|
|
6644
7068
|
);
|
|
6645
7069
|
} else {
|
|
6646
|
-
|
|
6647
|
-
`No Route53 hosted zone found for ${
|
|
7070
|
+
clack11.log.warn(
|
|
7071
|
+
`No Route53 hosted zone found for ${pc12.cyan(trackingDomain || config2.domain)}`
|
|
6648
7072
|
);
|
|
6649
|
-
|
|
6650
|
-
|
|
7073
|
+
clack11.log.info(
|
|
7074
|
+
pc12.dim(
|
|
6651
7075
|
"You'll need to manually create DNS records for SSL certificate validation and CloudFront."
|
|
6652
7076
|
)
|
|
6653
7077
|
);
|
|
6654
|
-
|
|
6655
|
-
|
|
7078
|
+
clack11.log.info(
|
|
7079
|
+
pc12.dim("DNS record details will be shown after deployment.")
|
|
6656
7080
|
);
|
|
6657
7081
|
}
|
|
6658
|
-
const confirmHttps = await
|
|
7082
|
+
const confirmHttps = await clack11.confirm({
|
|
6659
7083
|
message: hostedZone ? "Proceed with automatic HTTPS setup?" : "Proceed with manual HTTPS setup (requires DNS configuration)?",
|
|
6660
7084
|
initialValue: true
|
|
6661
7085
|
});
|
|
6662
|
-
if (
|
|
6663
|
-
|
|
7086
|
+
if (clack11.isCancel(confirmHttps) || !confirmHttps) {
|
|
7087
|
+
clack11.log.info("HTTPS tracking not enabled. Using HTTP tracking.");
|
|
6664
7088
|
updatedConfig = {
|
|
6665
7089
|
...config2,
|
|
6666
7090
|
tracking: {
|
|
@@ -6682,8 +7106,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6682
7106
|
};
|
|
6683
7107
|
}
|
|
6684
7108
|
} else {
|
|
6685
|
-
|
|
6686
|
-
|
|
7109
|
+
clack11.log.info(
|
|
7110
|
+
pc12.dim(
|
|
6687
7111
|
"Using HTTP tracking (standard). Links will use http:// protocol."
|
|
6688
7112
|
)
|
|
6689
7113
|
);
|
|
@@ -6701,7 +7125,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6701
7125
|
break;
|
|
6702
7126
|
}
|
|
6703
7127
|
case "retention": {
|
|
6704
|
-
const retention = await
|
|
7128
|
+
const retention = await clack11.select({
|
|
6705
7129
|
message: "Email history retention period (event data in DynamoDB):",
|
|
6706
7130
|
options: [
|
|
6707
7131
|
{ value: "7days", label: "7 days", hint: "Minimal storage cost" },
|
|
@@ -6725,17 +7149,17 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6725
7149
|
],
|
|
6726
7150
|
initialValue: config2.eventTracking?.archiveRetention || "90days"
|
|
6727
7151
|
});
|
|
6728
|
-
if (
|
|
6729
|
-
|
|
7152
|
+
if (clack11.isCancel(retention)) {
|
|
7153
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6730
7154
|
process.exit(0);
|
|
6731
7155
|
}
|
|
6732
|
-
|
|
6733
|
-
|
|
7156
|
+
clack11.log.info(
|
|
7157
|
+
pc12.dim(
|
|
6734
7158
|
"Note: This is for event data (sent, delivered, opened, etc.) stored in DynamoDB."
|
|
6735
7159
|
)
|
|
6736
7160
|
);
|
|
6737
|
-
|
|
6738
|
-
|
|
7161
|
+
clack11.log.info(
|
|
7162
|
+
pc12.dim(
|
|
6739
7163
|
"For full email content storage, use 'Enable email archiving' option."
|
|
6740
7164
|
)
|
|
6741
7165
|
);
|
|
@@ -6752,7 +7176,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6752
7176
|
break;
|
|
6753
7177
|
}
|
|
6754
7178
|
case "events": {
|
|
6755
|
-
const selectedEvents = await
|
|
7179
|
+
const selectedEvents = await clack11.multiselect({
|
|
6756
7180
|
message: "Select SES event types to track:",
|
|
6757
7181
|
options: [
|
|
6758
7182
|
{ value: "SEND", label: "Send", hint: "Email sent to SES" },
|
|
@@ -6796,8 +7220,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6796
7220
|
],
|
|
6797
7221
|
required: true
|
|
6798
7222
|
});
|
|
6799
|
-
if (
|
|
6800
|
-
|
|
7223
|
+
if (clack11.isCancel(selectedEvents)) {
|
|
7224
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6801
7225
|
process.exit(0);
|
|
6802
7226
|
}
|
|
6803
7227
|
updatedConfig = {
|
|
@@ -6812,16 +7236,16 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6812
7236
|
break;
|
|
6813
7237
|
}
|
|
6814
7238
|
case "dedicated-ip": {
|
|
6815
|
-
const confirmed = await
|
|
7239
|
+
const confirmed = await clack11.confirm({
|
|
6816
7240
|
message: "Enable dedicated IP? (Requires 100k+ emails/day, adds ~$50-100/mo)",
|
|
6817
7241
|
initialValue: false
|
|
6818
7242
|
});
|
|
6819
|
-
if (
|
|
6820
|
-
|
|
7243
|
+
if (clack11.isCancel(confirmed)) {
|
|
7244
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6821
7245
|
process.exit(0);
|
|
6822
7246
|
}
|
|
6823
7247
|
if (!confirmed) {
|
|
6824
|
-
|
|
7248
|
+
clack11.log.info("Dedicated IP not enabled.");
|
|
6825
7249
|
process.exit(0);
|
|
6826
7250
|
}
|
|
6827
7251
|
updatedConfig = {
|
|
@@ -6842,28 +7266,28 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6842
7266
|
const newCostData = calculateCosts(updatedConfig, 5e4);
|
|
6843
7267
|
const costDiff = newCostData.total.monthly - currentCostData.total.monthly;
|
|
6844
7268
|
console.log(`
|
|
6845
|
-
${
|
|
7269
|
+
${pc12.bold("Cost Impact:")}`);
|
|
6846
7270
|
console.log(
|
|
6847
|
-
` Current: ${
|
|
7271
|
+
` Current: ${pc12.cyan(`${formatCost(currentCostData.total.monthly)}/mo`)}`
|
|
6848
7272
|
);
|
|
6849
7273
|
console.log(
|
|
6850
|
-
` New: ${
|
|
7274
|
+
` New: ${pc12.cyan(`${formatCost(newCostData.total.monthly)}/mo`)}`
|
|
6851
7275
|
);
|
|
6852
7276
|
if (costDiff > 0) {
|
|
6853
|
-
console.log(` Change: ${
|
|
7277
|
+
console.log(` Change: ${pc12.yellow(`+${formatCost(costDiff)}/mo`)}`);
|
|
6854
7278
|
} else if (costDiff < 0) {
|
|
6855
7279
|
console.log(
|
|
6856
|
-
` Change: ${
|
|
7280
|
+
` Change: ${pc12.green(`${formatCost(Math.abs(costDiff))}/mo`)}`
|
|
6857
7281
|
);
|
|
6858
7282
|
}
|
|
6859
7283
|
console.log("");
|
|
6860
7284
|
if (!(options.yes || options.preview)) {
|
|
6861
|
-
const confirmed = await
|
|
7285
|
+
const confirmed = await clack11.confirm({
|
|
6862
7286
|
message: "Proceed with upgrade?",
|
|
6863
7287
|
initialValue: true
|
|
6864
7288
|
});
|
|
6865
|
-
if (
|
|
6866
|
-
|
|
7289
|
+
if (clack11.isCancel(confirmed) || !confirmed) {
|
|
7290
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6867
7291
|
process.exit(0);
|
|
6868
7292
|
}
|
|
6869
7293
|
}
|
|
@@ -6885,7 +7309,7 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6885
7309
|
"Generating upgrade preview",
|
|
6886
7310
|
async () => {
|
|
6887
7311
|
await ensurePulumiWorkDir();
|
|
6888
|
-
const stack = await
|
|
7312
|
+
const stack = await pulumi11.automation.LocalWorkspace.createOrSelectStack(
|
|
6889
7313
|
{
|
|
6890
7314
|
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
6891
7315
|
projectName: "wraps-email",
|
|
@@ -6935,8 +7359,8 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6935
7359
|
costEstimate: costComparison,
|
|
6936
7360
|
commandName: "wraps email upgrade"
|
|
6937
7361
|
});
|
|
6938
|
-
|
|
6939
|
-
|
|
7362
|
+
clack11.outro(
|
|
7363
|
+
pc12.green("Preview complete. Run without --preview to upgrade.")
|
|
6940
7364
|
);
|
|
6941
7365
|
trackServiceUpgrade("email", {
|
|
6942
7366
|
from_preset: metadata.services.email?.preset,
|
|
@@ -6960,7 +7384,7 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6960
7384
|
"Updating Wraps infrastructure (this may take 2-3 minutes)",
|
|
6961
7385
|
async () => {
|
|
6962
7386
|
await ensurePulumiWorkDir();
|
|
6963
|
-
const stack = await
|
|
7387
|
+
const stack = await pulumi11.automation.LocalWorkspace.createOrSelectStack(
|
|
6964
7388
|
{
|
|
6965
7389
|
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
6966
7390
|
projectName: "wraps-email",
|
|
@@ -7035,8 +7459,8 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
7035
7459
|
throw new Error(`Pulumi upgrade failed: ${error.message}`);
|
|
7036
7460
|
}
|
|
7037
7461
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
7038
|
-
const { findHostedZone:
|
|
7039
|
-
const hostedZone = await
|
|
7462
|
+
const { findHostedZone: findHostedZone3, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
7463
|
+
const hostedZone = await findHostedZone3(outputs.domain, region);
|
|
7040
7464
|
if (hostedZone) {
|
|
7041
7465
|
try {
|
|
7042
7466
|
progress.start("Creating DNS records in Route53");
|
|
@@ -7101,21 +7525,21 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
7101
7525
|
httpsTrackingEnabled: outputs.httpsTrackingEnabled
|
|
7102
7526
|
});
|
|
7103
7527
|
console.log(`
|
|
7104
|
-
${
|
|
7528
|
+
${pc12.green("\u2713")} ${pc12.bold("Upgrade complete!")}
|
|
7105
7529
|
`);
|
|
7106
7530
|
if (upgradeAction === "preset" && newPreset) {
|
|
7107
7531
|
console.log(
|
|
7108
|
-
`Upgraded to ${
|
|
7532
|
+
`Upgraded to ${pc12.cyan(newPreset)} preset (${pc12.green(`${formatCost(newCostData.total.monthly)}/mo`)})
|
|
7109
7533
|
`
|
|
7110
7534
|
);
|
|
7111
7535
|
} else {
|
|
7112
7536
|
console.log(
|
|
7113
|
-
`Updated configuration (${
|
|
7537
|
+
`Updated configuration (${pc12.green(`${formatCost(newCostData.total.monthly)}/mo`)})
|
|
7114
7538
|
`
|
|
7115
7539
|
);
|
|
7116
7540
|
}
|
|
7117
7541
|
if (needsCertificateValidation) {
|
|
7118
|
-
console.log(
|
|
7542
|
+
console.log(pc12.bold("\u26A0\uFE0F HTTPS Tracking - Next Steps:\n"));
|
|
7119
7543
|
console.log(
|
|
7120
7544
|
" 1. Add the SSL certificate validation DNS record shown above to your DNS provider"
|
|
7121
7545
|
);
|
|
@@ -7123,17 +7547,17 @@ ${pc10.green("\u2713")} ${pc10.bold("Upgrade complete!")}
|
|
|
7123
7547
|
" 2. Wait for DNS propagation and certificate validation (5-30 minutes)"
|
|
7124
7548
|
);
|
|
7125
7549
|
console.log(
|
|
7126
|
-
` 3. Run ${
|
|
7550
|
+
` 3. Run ${pc12.cyan("wraps email upgrade")} again to complete CloudFront setup
|
|
7127
7551
|
`
|
|
7128
7552
|
);
|
|
7129
7553
|
console.log(
|
|
7130
|
-
|
|
7554
|
+
pc12.dim(
|
|
7131
7555
|
" Note: CloudFront distribution will be created once the certificate is validated.\n"
|
|
7132
7556
|
)
|
|
7133
7557
|
);
|
|
7134
7558
|
} else if (outputs.httpsTrackingEnabled && outputs.cloudFrontDomain) {
|
|
7135
7559
|
console.log(
|
|
7136
|
-
|
|
7560
|
+
pc12.green("\u2713") + " " + pc12.bold("HTTPS tracking is fully configured and ready to use!\n")
|
|
7137
7561
|
);
|
|
7138
7562
|
}
|
|
7139
7563
|
const enabledFeatures = [];
|
|
@@ -7158,11 +7582,11 @@ ${pc10.green("\u2713")} ${pc10.bold("Upgrade complete!")}
|
|
|
7158
7582
|
|
|
7159
7583
|
// src/commands/shared/dashboard.ts
|
|
7160
7584
|
init_esm_shims();
|
|
7161
|
-
import * as
|
|
7162
|
-
import * as
|
|
7585
|
+
import * as clack12 from "@clack/prompts";
|
|
7586
|
+
import * as pulumi12 from "@pulumi/pulumi";
|
|
7163
7587
|
import getPort from "get-port";
|
|
7164
7588
|
import open from "open";
|
|
7165
|
-
import
|
|
7589
|
+
import pc13 from "picocolors";
|
|
7166
7590
|
|
|
7167
7591
|
// src/console/server.ts
|
|
7168
7592
|
init_esm_shims();
|
|
@@ -8387,7 +8811,7 @@ async function startConsoleServer(config2) {
|
|
|
8387
8811
|
init_events();
|
|
8388
8812
|
init_aws();
|
|
8389
8813
|
async function dashboard(options) {
|
|
8390
|
-
|
|
8814
|
+
clack12.intro(pc13.bold("Wraps Dashboard"));
|
|
8391
8815
|
const progress = new DeploymentProgress();
|
|
8392
8816
|
const identity = await progress.execute(
|
|
8393
8817
|
"Validating AWS credentials",
|
|
@@ -8397,16 +8821,16 @@ async function dashboard(options) {
|
|
|
8397
8821
|
let stackOutputs = {};
|
|
8398
8822
|
try {
|
|
8399
8823
|
await ensurePulumiWorkDir();
|
|
8400
|
-
const stack = await
|
|
8824
|
+
const stack = await pulumi12.automation.LocalWorkspace.selectStack({
|
|
8401
8825
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
8402
8826
|
workDir: getPulumiWorkDir()
|
|
8403
8827
|
});
|
|
8404
8828
|
stackOutputs = await stack.outputs();
|
|
8405
8829
|
} catch (_error) {
|
|
8406
8830
|
progress.stop();
|
|
8407
|
-
|
|
8831
|
+
clack12.log.error("No Wraps infrastructure found");
|
|
8408
8832
|
console.log(
|
|
8409
|
-
`\\nRun ${
|
|
8833
|
+
`\\nRun ${pc13.cyan("wraps email init")} to deploy infrastructure first.\\n`
|
|
8410
8834
|
);
|
|
8411
8835
|
process.exit(1);
|
|
8412
8836
|
}
|
|
@@ -8415,9 +8839,9 @@ async function dashboard(options) {
|
|
|
8415
8839
|
const archivingEnabled = stackOutputs.archivingEnabled?.value ?? false;
|
|
8416
8840
|
const port = options.port || await getPort({ port: [5555, 5556, 5557, 5558, 5559] });
|
|
8417
8841
|
progress.stop();
|
|
8418
|
-
|
|
8842
|
+
clack12.log.success("Starting dashboard server...");
|
|
8419
8843
|
console.log(
|
|
8420
|
-
`${
|
|
8844
|
+
`${pc13.dim("Using current AWS credentials (no role assumption)")}\\n`
|
|
8421
8845
|
);
|
|
8422
8846
|
const { url } = await startConsoleServer({
|
|
8423
8847
|
port,
|
|
@@ -8430,8 +8854,8 @@ async function dashboard(options) {
|
|
|
8430
8854
|
archiveArn,
|
|
8431
8855
|
archivingEnabled
|
|
8432
8856
|
});
|
|
8433
|
-
console.log(`\\n${
|
|
8434
|
-
console.log(`${
|
|
8857
|
+
console.log(`\\n${pc13.bold("Dashboard:")} ${pc13.cyan(url)}`);
|
|
8858
|
+
console.log(`${pc13.dim("Press Ctrl+C to stop")}\\n`);
|
|
8435
8859
|
if (!options.noOpen) {
|
|
8436
8860
|
await open(url);
|
|
8437
8861
|
}
|
|
@@ -8446,210 +8870,147 @@ async function dashboard(options) {
|
|
|
8446
8870
|
|
|
8447
8871
|
// src/commands/shared/destroy.ts
|
|
8448
8872
|
init_esm_shims();
|
|
8449
|
-
init_events();
|
|
8450
8873
|
init_aws();
|
|
8451
|
-
import * as
|
|
8452
|
-
import
|
|
8453
|
-
import pc12 from "picocolors";
|
|
8874
|
+
import * as clack13 from "@clack/prompts";
|
|
8875
|
+
import pc14 from "picocolors";
|
|
8454
8876
|
async function destroy(options) {
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
"
|
|
8464
|
-
|
|
8465
|
-
|
|
8877
|
+
clack13.intro(pc14.bold("Wraps Infrastructure Teardown"));
|
|
8878
|
+
const spinner3 = clack13.spinner();
|
|
8879
|
+
spinner3.start("Validating AWS credentials");
|
|
8880
|
+
let identity;
|
|
8881
|
+
try {
|
|
8882
|
+
identity = await validateAWSCredentials();
|
|
8883
|
+
spinner3.stop("AWS credentials validated");
|
|
8884
|
+
} catch (error) {
|
|
8885
|
+
spinner3.stop("AWS credentials validation failed");
|
|
8886
|
+
throw error;
|
|
8887
|
+
}
|
|
8466
8888
|
const region = await getAWSRegion();
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
),
|
|
8472
|
-
initialValue: false
|
|
8473
|
-
});
|
|
8474
|
-
if (clack11.isCancel(confirmed) || !confirmed) {
|
|
8475
|
-
clack11.cancel("Destruction cancelled.");
|
|
8476
|
-
process.exit(0);
|
|
8477
|
-
}
|
|
8889
|
+
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
8890
|
+
const deployedServices = [];
|
|
8891
|
+
if (metadata?.services?.email) {
|
|
8892
|
+
deployedServices.push("email");
|
|
8478
8893
|
}
|
|
8479
|
-
if (
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
throw new Error("No Wraps infrastructure found to preview");
|
|
8494
|
-
}
|
|
8495
|
-
const result = await stack.preview({ diff: true });
|
|
8496
|
-
return result;
|
|
8497
|
-
}
|
|
8498
|
-
);
|
|
8499
|
-
displayPreview({
|
|
8500
|
-
changeSummary: previewResult.changeSummary,
|
|
8501
|
-
costEstimate: "Monthly cost after destruction: $0.00",
|
|
8502
|
-
commandName: "wraps destroy"
|
|
8503
|
-
});
|
|
8504
|
-
clack11.outro(
|
|
8505
|
-
pc12.green("Preview complete. Run without --preview to destroy.")
|
|
8506
|
-
);
|
|
8507
|
-
trackServiceRemoved("email", {
|
|
8508
|
-
preview: true,
|
|
8509
|
-
duration_ms: Date.now() - startTime
|
|
8510
|
-
});
|
|
8894
|
+
if (deployedServices.length === 0) {
|
|
8895
|
+
clack13.log.warn("No Wraps services found in this region");
|
|
8896
|
+
console.log(
|
|
8897
|
+
`
|
|
8898
|
+
Run ${pc14.cyan("wraps email init")} to deploy infrastructure.
|
|
8899
|
+
`
|
|
8900
|
+
);
|
|
8901
|
+
process.exit(0);
|
|
8902
|
+
}
|
|
8903
|
+
if (deployedServices.length === 1) {
|
|
8904
|
+
const service = deployedServices[0];
|
|
8905
|
+
clack13.log.info(`Found ${pc14.cyan(service)} service deployed`);
|
|
8906
|
+
if (service === "email") {
|
|
8907
|
+
await emailDestroy(options);
|
|
8511
8908
|
return;
|
|
8512
|
-
} catch (error) {
|
|
8513
|
-
progress.stop();
|
|
8514
|
-
if (error.message.includes("No Wraps infrastructure found")) {
|
|
8515
|
-
clack11.log.warn("No Wraps infrastructure found to preview");
|
|
8516
|
-
process.exit(0);
|
|
8517
|
-
}
|
|
8518
|
-
trackError("PREVIEW_FAILED", "destroy", { step: "preview" });
|
|
8519
|
-
throw new Error(`Preview failed: ${error.message}`);
|
|
8520
8909
|
}
|
|
8521
8910
|
}
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
} catch (_error) {
|
|
8535
|
-
throw new Error("No Wraps infrastructure found to destroy");
|
|
8536
|
-
}
|
|
8537
|
-
await stack.destroy({ onOutput: () => {
|
|
8538
|
-
} });
|
|
8539
|
-
await stack.workspace.removeStack(stackName);
|
|
8911
|
+
const serviceToDestroy = await clack13.select({
|
|
8912
|
+
message: "Which service would you like to destroy?",
|
|
8913
|
+
options: [
|
|
8914
|
+
...deployedServices.map((s) => ({
|
|
8915
|
+
value: s,
|
|
8916
|
+
label: s.charAt(0).toUpperCase() + s.slice(1),
|
|
8917
|
+
hint: s === "email" ? "AWS SES email infrastructure" : void 0
|
|
8918
|
+
})),
|
|
8919
|
+
{
|
|
8920
|
+
value: "all",
|
|
8921
|
+
label: "All services",
|
|
8922
|
+
hint: "Destroy all Wraps infrastructure"
|
|
8540
8923
|
}
|
|
8541
|
-
|
|
8542
|
-
}
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8924
|
+
]
|
|
8925
|
+
});
|
|
8926
|
+
if (clack13.isCancel(serviceToDestroy)) {
|
|
8927
|
+
clack13.cancel("Operation cancelled.");
|
|
8928
|
+
process.exit(0);
|
|
8929
|
+
}
|
|
8930
|
+
if (serviceToDestroy === "email" || serviceToDestroy === "all") {
|
|
8931
|
+
if (deployedServices.includes("email")) {
|
|
8932
|
+
await emailDestroy(options);
|
|
8548
8933
|
}
|
|
8549
|
-
trackError("DESTROY_FAILED", "destroy", { step: "destroy" });
|
|
8550
|
-
clack11.log.error("Infrastructure destruction failed");
|
|
8551
|
-
throw error;
|
|
8552
8934
|
}
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
console.log(
|
|
8557
|
-
`
|
|
8558
|
-
Run ${pc12.cyan("wraps email init")} to deploy infrastructure again.
|
|
8559
|
-
`
|
|
8560
|
-
);
|
|
8561
|
-
trackServiceRemoved("email", {
|
|
8562
|
-
reason: "user_initiated",
|
|
8563
|
-
duration_ms: Date.now() - startTime
|
|
8564
|
-
});
|
|
8935
|
+
if (serviceToDestroy === "all") {
|
|
8936
|
+
clack13.outro(pc14.green("All Wraps infrastructure has been removed"));
|
|
8937
|
+
}
|
|
8565
8938
|
}
|
|
8566
8939
|
|
|
8567
8940
|
// src/commands/shared/status.ts
|
|
8568
8941
|
init_esm_shims();
|
|
8569
8942
|
init_events();
|
|
8570
8943
|
init_aws();
|
|
8571
|
-
import * as
|
|
8572
|
-
import * as
|
|
8573
|
-
import
|
|
8944
|
+
import * as clack14 from "@clack/prompts";
|
|
8945
|
+
import * as pulumi13 from "@pulumi/pulumi";
|
|
8946
|
+
import pc15 from "picocolors";
|
|
8574
8947
|
async function status(_options) {
|
|
8575
8948
|
const startTime = Date.now();
|
|
8576
8949
|
const progress = new DeploymentProgress();
|
|
8950
|
+
clack14.intro(pc15.bold("Wraps Infrastructure Status"));
|
|
8577
8951
|
const identity = await progress.execute(
|
|
8578
8952
|
"Loading infrastructure status",
|
|
8579
8953
|
async () => validateAWSCredentials()
|
|
8580
8954
|
);
|
|
8955
|
+
progress.info(`AWS Account: ${pc15.cyan(identity.accountId)}`);
|
|
8581
8956
|
const region = await getAWSRegion();
|
|
8582
|
-
|
|
8957
|
+
progress.info(`Region: ${pc15.cyan(region)}`);
|
|
8958
|
+
const services = [];
|
|
8583
8959
|
try {
|
|
8584
8960
|
await ensurePulumiWorkDir();
|
|
8585
|
-
const stack = await
|
|
8961
|
+
const stack = await pulumi13.automation.LocalWorkspace.selectStack({
|
|
8586
8962
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
8587
8963
|
workDir: getPulumiWorkDir()
|
|
8588
8964
|
});
|
|
8589
|
-
|
|
8965
|
+
const outputs = await stack.outputs();
|
|
8966
|
+
if (outputs.roleArn?.value) {
|
|
8967
|
+
const domainCount = outputs.domains?.value?.length || 0;
|
|
8968
|
+
services.push({
|
|
8969
|
+
name: "Email",
|
|
8970
|
+
status: "deployed",
|
|
8971
|
+
details: domainCount > 0 ? `${domainCount} domain(s)` : void 0
|
|
8972
|
+
});
|
|
8973
|
+
} else {
|
|
8974
|
+
services.push({ name: "Email", status: "not_deployed" });
|
|
8975
|
+
}
|
|
8590
8976
|
} catch (_error) {
|
|
8591
|
-
|
|
8592
|
-
|
|
8977
|
+
services.push({ name: "Email", status: "not_deployed" });
|
|
8978
|
+
}
|
|
8979
|
+
progress.stop();
|
|
8980
|
+
console.log();
|
|
8981
|
+
clack14.note(
|
|
8982
|
+
services.map((s) => {
|
|
8983
|
+
if (s.status === "deployed") {
|
|
8984
|
+
const details = s.details ? pc15.dim(` (${s.details})`) : "";
|
|
8985
|
+
return ` ${pc15.green("\u2713")} ${s.name}${details}`;
|
|
8986
|
+
}
|
|
8987
|
+
return ` ${pc15.dim("\u25CB")} ${s.name} ${pc15.dim("(not deployed)")}`;
|
|
8988
|
+
}).join("\n"),
|
|
8989
|
+
"Services"
|
|
8990
|
+
);
|
|
8991
|
+
const hasDeployedServices = services.some((s) => s.status === "deployed");
|
|
8992
|
+
if (hasDeployedServices) {
|
|
8993
|
+
console.log(`
|
|
8994
|
+
${pc15.bold("Details:")}`);
|
|
8995
|
+
if (services.find((s) => s.name === "Email")?.status === "deployed") {
|
|
8996
|
+
console.log(
|
|
8997
|
+
` ${pc15.dim("Email:")} ${pc15.cyan("wraps email status")}`
|
|
8998
|
+
);
|
|
8999
|
+
}
|
|
9000
|
+
} else {
|
|
9001
|
+
console.log(`
|
|
9002
|
+
${pc15.bold("Get started:")}`);
|
|
8593
9003
|
console.log(
|
|
8594
|
-
`
|
|
8595
|
-
Run ${pc13.cyan("wraps email init")} to deploy infrastructure.
|
|
8596
|
-
`
|
|
9004
|
+
` ${pc15.dim("Deploy email:")} ${pc15.cyan("wraps email init")}`
|
|
8597
9005
|
);
|
|
8598
|
-
process.exit(1);
|
|
8599
9006
|
}
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
domains.map(async (d) => {
|
|
8605
|
-
try {
|
|
8606
|
-
const identity2 = await sesv2Client.send(
|
|
8607
|
-
new GetEmailIdentityCommand4({ EmailIdentity: d.domain })
|
|
8608
|
-
);
|
|
8609
|
-
return {
|
|
8610
|
-
domain: d.domain,
|
|
8611
|
-
status: d.verified ? "verified" : "pending",
|
|
8612
|
-
dkimTokens: identity2.DkimAttributes?.Tokens || [],
|
|
8613
|
-
mailFromDomain: identity2.MailFromAttributes?.MailFromDomain,
|
|
8614
|
-
mailFromStatus: identity2.MailFromAttributes?.MailFromDomainStatus
|
|
8615
|
-
};
|
|
8616
|
-
} catch (_error) {
|
|
8617
|
-
return {
|
|
8618
|
-
domain: d.domain,
|
|
8619
|
-
status: d.verified ? "verified" : "pending",
|
|
8620
|
-
dkimTokens: void 0,
|
|
8621
|
-
mailFromDomain: void 0,
|
|
8622
|
-
mailFromStatus: void 0
|
|
8623
|
-
};
|
|
8624
|
-
}
|
|
8625
|
-
})
|
|
8626
|
-
);
|
|
8627
|
-
const integrationLevel = stackOutputs.configSetName ? "enhanced" : "dashboard-only";
|
|
8628
|
-
progress.stop();
|
|
8629
|
-
displayStatus({
|
|
8630
|
-
integrationLevel,
|
|
8631
|
-
region,
|
|
8632
|
-
domains: domainsWithTokens,
|
|
8633
|
-
resources: {
|
|
8634
|
-
roleArn: stackOutputs.roleArn?.value,
|
|
8635
|
-
configSetName: stackOutputs.configSetName?.value,
|
|
8636
|
-
tableName: stackOutputs.tableName?.value,
|
|
8637
|
-
lambdaFunctions: stackOutputs.lambdaFunctions?.value?.length || 0,
|
|
8638
|
-
snsTopics: integrationLevel === "enhanced" ? 1 : 0,
|
|
8639
|
-
archiveArn: stackOutputs.archiveArn?.value,
|
|
8640
|
-
archivingEnabled: stackOutputs.archivingEnabled?.value,
|
|
8641
|
-
archiveRetention: stackOutputs.archiveRetention?.value
|
|
8642
|
-
},
|
|
8643
|
-
tracking: stackOutputs.customTrackingDomain?.value ? {
|
|
8644
|
-
customTrackingDomain: stackOutputs.customTrackingDomain?.value,
|
|
8645
|
-
httpsEnabled: stackOutputs.httpsTrackingEnabled?.value,
|
|
8646
|
-
cloudFrontDomain: stackOutputs.cloudFrontDomain?.value
|
|
8647
|
-
} : void 0
|
|
8648
|
-
});
|
|
9007
|
+
console.log(`
|
|
9008
|
+
${pc15.bold("Dashboard:")} ${pc15.blue("https://app.wraps.dev")}`);
|
|
9009
|
+
console.log(`${pc15.bold("Docs:")} ${pc15.blue("https://wraps.dev/docs")}
|
|
9010
|
+
`);
|
|
8649
9011
|
trackCommand("status", {
|
|
8650
9012
|
success: true,
|
|
8651
|
-
|
|
8652
|
-
integration_level: integrationLevel,
|
|
9013
|
+
services_deployed: services.filter((s) => s.status === "deployed").length,
|
|
8653
9014
|
duration_ms: Date.now() - startTime
|
|
8654
9015
|
});
|
|
8655
9016
|
}
|
|
@@ -8657,56 +9018,56 @@ Run ${pc13.cyan("wraps email init")} to deploy infrastructure.
|
|
|
8657
9018
|
// src/commands/telemetry.ts
|
|
8658
9019
|
init_esm_shims();
|
|
8659
9020
|
init_client();
|
|
8660
|
-
import * as
|
|
8661
|
-
import
|
|
9021
|
+
import * as clack15 from "@clack/prompts";
|
|
9022
|
+
import pc16 from "picocolors";
|
|
8662
9023
|
async function telemetryEnable() {
|
|
8663
9024
|
const client = getTelemetryClient();
|
|
8664
9025
|
client.enable();
|
|
8665
|
-
|
|
8666
|
-
console.log(` Config: ${
|
|
9026
|
+
clack15.log.success(pc16.green("Telemetry enabled"));
|
|
9027
|
+
console.log(` Config: ${pc16.dim(client.getConfigPath())}`);
|
|
8667
9028
|
console.log(`
|
|
8668
|
-
${
|
|
9029
|
+
${pc16.dim("Thank you for helping improve Wraps!")}
|
|
8669
9030
|
`);
|
|
8670
9031
|
}
|
|
8671
9032
|
async function telemetryDisable() {
|
|
8672
9033
|
const client = getTelemetryClient();
|
|
8673
9034
|
client.disable();
|
|
8674
|
-
|
|
8675
|
-
console.log(` Config: ${
|
|
9035
|
+
clack15.log.success(pc16.green("Telemetry disabled"));
|
|
9036
|
+
console.log(` Config: ${pc16.dim(client.getConfigPath())}`);
|
|
8676
9037
|
console.log(
|
|
8677
9038
|
`
|
|
8678
|
-
${
|
|
9039
|
+
${pc16.dim("You can re-enable with:")} wraps telemetry enable
|
|
8679
9040
|
`
|
|
8680
9041
|
);
|
|
8681
9042
|
}
|
|
8682
9043
|
async function telemetryStatus() {
|
|
8683
9044
|
const client = getTelemetryClient();
|
|
8684
|
-
|
|
8685
|
-
const status2 = client.isEnabled() ?
|
|
9045
|
+
clack15.intro(pc16.bold("Telemetry Status"));
|
|
9046
|
+
const status2 = client.isEnabled() ? pc16.green("Enabled") : pc16.red("Disabled");
|
|
8686
9047
|
console.log();
|
|
8687
|
-
console.log(` ${
|
|
8688
|
-
console.log(` ${
|
|
9048
|
+
console.log(` ${pc16.bold("Status:")} ${status2}`);
|
|
9049
|
+
console.log(` ${pc16.bold("Config file:")} ${pc16.dim(client.getConfigPath())}`);
|
|
8689
9050
|
if (client.isEnabled()) {
|
|
8690
9051
|
console.log();
|
|
8691
|
-
console.log(
|
|
8692
|
-
console.log(` ${
|
|
9052
|
+
console.log(pc16.bold(" How to opt-out:"));
|
|
9053
|
+
console.log(` ${pc16.cyan("wraps telemetry disable")}`);
|
|
8693
9054
|
console.log(
|
|
8694
|
-
` ${
|
|
9055
|
+
` ${pc16.dim("Or set:")} ${pc16.cyan("WRAPS_TELEMETRY_DISABLED=1")}`
|
|
8695
9056
|
);
|
|
8696
|
-
console.log(` ${
|
|
9057
|
+
console.log(` ${pc16.dim("Or set:")} ${pc16.cyan("DO_NOT_TRACK=1")}`);
|
|
8697
9058
|
} else {
|
|
8698
9059
|
console.log();
|
|
8699
|
-
console.log(
|
|
8700
|
-
console.log(` ${
|
|
9060
|
+
console.log(pc16.bold(" How to opt-in:"));
|
|
9061
|
+
console.log(` ${pc16.cyan("wraps telemetry enable")}`);
|
|
8701
9062
|
}
|
|
8702
9063
|
console.log();
|
|
8703
|
-
console.log(
|
|
9064
|
+
console.log(pc16.bold(" Debug mode:"));
|
|
8704
9065
|
console.log(
|
|
8705
|
-
` ${
|
|
9066
|
+
` ${pc16.dim("See what would be sent:")} ${pc16.cyan("WRAPS_TELEMETRY_DEBUG=1 wraps <command>")}`
|
|
8706
9067
|
);
|
|
8707
9068
|
console.log();
|
|
8708
9069
|
console.log(
|
|
8709
|
-
` ${
|
|
9070
|
+
` ${pc16.dim("Learn more:")} ${pc16.cyan("https://wraps.dev/docs/telemetry")}`
|
|
8710
9071
|
);
|
|
8711
9072
|
console.log();
|
|
8712
9073
|
}
|
|
@@ -8724,19 +9085,41 @@ function printCompletionScript() {
|
|
|
8724
9085
|
console.log("# ========================\n");
|
|
8725
9086
|
console.log("# Tab completion will be available in a future release.\n");
|
|
8726
9087
|
console.log("# For now, here are the available commands:\n");
|
|
8727
|
-
console.log("# Commands:");
|
|
9088
|
+
console.log("# Email Commands:");
|
|
8728
9089
|
console.log(
|
|
8729
9090
|
"# wraps email init [--provider vercel|aws|railway|other] [--region <region>] [--domain <domain>]"
|
|
8730
9091
|
);
|
|
8731
|
-
console.log("# wraps
|
|
8732
|
-
console.log("# wraps
|
|
9092
|
+
console.log("# wraps email connect [--region <region>]");
|
|
9093
|
+
console.log("# wraps email status [--account <account-id>]");
|
|
9094
|
+
console.log("# wraps email verify --domain <domain>");
|
|
9095
|
+
console.log("# wraps email sync");
|
|
9096
|
+
console.log("# wraps email upgrade");
|
|
9097
|
+
console.log("# wraps email restore [--region <region>] [--force]");
|
|
9098
|
+
console.log("# wraps email destroy [--force] [--preview]");
|
|
9099
|
+
console.log("# wraps email domains add --domain <domain>");
|
|
9100
|
+
console.log("# wraps email domains list");
|
|
9101
|
+
console.log("# wraps email domains verify --domain <domain>");
|
|
9102
|
+
console.log("# wraps email domains get-dkim --domain <domain>");
|
|
9103
|
+
console.log("# wraps email domains remove --domain <domain> [--force]\n");
|
|
9104
|
+
console.log("# Global Commands:");
|
|
9105
|
+
console.log("# wraps status");
|
|
9106
|
+
console.log("# wraps destroy [--force] [--preview]");
|
|
9107
|
+
console.log("# wraps console [--port <port>] [--no-open]");
|
|
9108
|
+
console.log("# wraps completion");
|
|
9109
|
+
console.log("# wraps telemetry [enable|disable|status]\n");
|
|
9110
|
+
console.log("# Dashboard Commands:");
|
|
9111
|
+
console.log("# wraps dashboard update-role [--region <region>] [--force]\n");
|
|
8733
9112
|
console.log("# Flags:");
|
|
8734
|
-
console.log("# --provider : vercel, aws, railway, other");
|
|
9113
|
+
console.log("# -p, --provider : vercel, aws, railway, other");
|
|
8735
9114
|
console.log(
|
|
8736
|
-
"# --region : us-east-1, us-east-2, us-west-1, us-west-2, eu-west-1, eu-west-2, etc."
|
|
9115
|
+
"# -r, --region : us-east-1, us-east-2, us-west-1, us-west-2, eu-west-1, eu-west-2, etc."
|
|
8737
9116
|
);
|
|
8738
|
-
console.log("# --domain : Your domain name (e.g., myapp.com)");
|
|
8739
|
-
console.log("# --account
|
|
9117
|
+
console.log("# -d, --domain : Your domain name (e.g., myapp.com)");
|
|
9118
|
+
console.log("# --account : AWS account ID or alias");
|
|
9119
|
+
console.log("# --preset : starter, production, enterprise, custom");
|
|
9120
|
+
console.log("# -y, --yes : Skip confirmation prompts");
|
|
9121
|
+
console.log("# -f, --force : Force destructive operations");
|
|
9122
|
+
console.log("# --preview : Preview changes without deploying\n");
|
|
8740
9123
|
}
|
|
8741
9124
|
|
|
8742
9125
|
// src/cli.ts
|
|
@@ -8753,62 +9136,66 @@ function showVersion() {
|
|
|
8753
9136
|
process.exit(0);
|
|
8754
9137
|
}
|
|
8755
9138
|
function showHelp() {
|
|
8756
|
-
|
|
9139
|
+
clack16.intro(pc17.bold(`WRAPS CLI v${VERSION}`));
|
|
8757
9140
|
console.log("Deploy AWS infrastructure to your account\n");
|
|
8758
9141
|
console.log("Usage: wraps [service] <command> [options]\n");
|
|
8759
9142
|
console.log("Services:");
|
|
8760
|
-
console.log(` ${
|
|
9143
|
+
console.log(` ${pc17.cyan("email")} Email infrastructure (AWS SES)
|
|
9144
|
+
`);
|
|
9145
|
+
console.log("Email Commands:");
|
|
8761
9146
|
console.log(
|
|
8762
|
-
` ${
|
|
8763
|
-
`
|
|
9147
|
+
` ${pc17.cyan("email init")} Deploy new email infrastructure`
|
|
8764
9148
|
);
|
|
8765
|
-
console.log("Email Commands:");
|
|
8766
9149
|
console.log(
|
|
8767
|
-
` ${
|
|
9150
|
+
` ${pc17.cyan("email connect")} Connect to existing AWS SES`
|
|
8768
9151
|
);
|
|
9152
|
+
console.log(` ${pc17.cyan("email status")} Show email infrastructure details`);
|
|
9153
|
+
console.log(` ${pc17.cyan("email verify")} Verify domain DNS records`);
|
|
8769
9154
|
console.log(
|
|
8770
|
-
` ${
|
|
9155
|
+
` ${pc17.cyan("email sync")} Apply CLI updates to infrastructure`
|
|
8771
9156
|
);
|
|
8772
|
-
console.log(` ${
|
|
9157
|
+
console.log(` ${pc17.cyan("email upgrade")} Add features`);
|
|
8773
9158
|
console.log(
|
|
8774
|
-
` ${
|
|
9159
|
+
` ${pc17.cyan("email restore")} Restore original configuration`
|
|
8775
9160
|
);
|
|
8776
|
-
console.log(` ${pc15.cyan("email upgrade")} Add features`);
|
|
8777
9161
|
console.log(
|
|
8778
|
-
` ${
|
|
8779
|
-
`
|
|
9162
|
+
` ${pc17.cyan("email destroy")} Remove email infrastructure`
|
|
8780
9163
|
);
|
|
9164
|
+
console.log(` ${pc17.cyan("email domains add")} Add a domain to SES`);
|
|
9165
|
+
console.log(` ${pc17.cyan("email domains list")} List all domains`);
|
|
9166
|
+
console.log(` ${pc17.cyan("email domains remove")} Remove a domain
|
|
9167
|
+
`);
|
|
8781
9168
|
console.log("Console & Dashboard:");
|
|
8782
|
-
console.log(` ${
|
|
9169
|
+
console.log(` ${pc17.cyan("console")} Start local web console`);
|
|
8783
9170
|
console.log(
|
|
8784
|
-
` ${
|
|
9171
|
+
` ${pc17.cyan("dashboard update-role")} Update hosted dashboard IAM permissions
|
|
8785
9172
|
`
|
|
8786
9173
|
);
|
|
8787
9174
|
console.log("Global Commands:");
|
|
8788
|
-
console.log(` ${
|
|
8789
|
-
console.log(` ${
|
|
8790
|
-
console.log(` ${
|
|
9175
|
+
console.log(` ${pc17.cyan("status")} Show overview of all services`);
|
|
9176
|
+
console.log(` ${pc17.cyan("destroy")} Remove deployed infrastructure`);
|
|
9177
|
+
console.log(` ${pc17.cyan("completion")} Generate shell completion script`);
|
|
8791
9178
|
console.log(
|
|
8792
|
-
` ${
|
|
9179
|
+
` ${pc17.cyan("telemetry")} Manage anonymous telemetry settings
|
|
8793
9180
|
`
|
|
8794
9181
|
);
|
|
8795
9182
|
console.log("Options:");
|
|
8796
9183
|
console.log(
|
|
8797
|
-
` ${
|
|
9184
|
+
` ${pc17.dim("-p, --provider")} Hosting provider (vercel, aws, railway, other)`
|
|
8798
9185
|
);
|
|
8799
|
-
console.log(` ${
|
|
8800
|
-
console.log(` ${
|
|
8801
|
-
console.log(` ${
|
|
8802
|
-
console.log(` ${
|
|
8803
|
-
console.log(` ${
|
|
8804
|
-
console.log(` ${
|
|
9186
|
+
console.log(` ${pc17.dim("-r, --region")} AWS region`);
|
|
9187
|
+
console.log(` ${pc17.dim("-d, --domain")} Domain name`);
|
|
9188
|
+
console.log(` ${pc17.dim("--account")} AWS account ID or alias`);
|
|
9189
|
+
console.log(` ${pc17.dim("--preset")} Configuration preset`);
|
|
9190
|
+
console.log(` ${pc17.dim("-y, --yes")} Skip confirmation prompts`);
|
|
9191
|
+
console.log(` ${pc17.dim("-f, --force")} Force destructive operations`);
|
|
8805
9192
|
console.log(
|
|
8806
|
-
` ${
|
|
9193
|
+
` ${pc17.dim("--preview")} Preview changes without deploying`
|
|
8807
9194
|
);
|
|
8808
|
-
console.log(` ${
|
|
9195
|
+
console.log(` ${pc17.dim("-v, --version")} Show version number
|
|
8809
9196
|
`);
|
|
8810
9197
|
console.log(
|
|
8811
|
-
`Run ${
|
|
9198
|
+
`Run ${pc17.cyan("wraps <service> <command> --help")} for more information.
|
|
8812
9199
|
`
|
|
8813
9200
|
);
|
|
8814
9201
|
process.exit(0);
|
|
@@ -8875,37 +9262,9 @@ var flags = args.parse(process.argv);
|
|
|
8875
9262
|
var [primaryCommand, subCommand] = args.sub;
|
|
8876
9263
|
if (!primaryCommand) {
|
|
8877
9264
|
async function selectService() {
|
|
8878
|
-
|
|
8879
|
-
console.log("Welcome! Let's get started deploying your infrastructure.\n");
|
|
8880
|
-
const
|
|
8881
|
-
message: "Which service would you like to set up?",
|
|
8882
|
-
options: [
|
|
8883
|
-
{
|
|
8884
|
-
value: "email",
|
|
8885
|
-
label: "Email",
|
|
8886
|
-
hint: "AWS SES email infrastructure"
|
|
8887
|
-
},
|
|
8888
|
-
{
|
|
8889
|
-
value: "sms",
|
|
8890
|
-
label: "SMS",
|
|
8891
|
-
hint: "Coming soon - AWS End User Messaging"
|
|
8892
|
-
}
|
|
8893
|
-
]
|
|
8894
|
-
});
|
|
8895
|
-
if (clack14.isCancel(service)) {
|
|
8896
|
-
clack14.cancel("Operation cancelled.");
|
|
8897
|
-
process.exit(0);
|
|
8898
|
-
}
|
|
8899
|
-
if (service === "sms") {
|
|
8900
|
-
clack14.log.warn("SMS infrastructure is coming soon!");
|
|
8901
|
-
console.log(
|
|
8902
|
-
`
|
|
8903
|
-
Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-team/wraps")}
|
|
8904
|
-
`
|
|
8905
|
-
);
|
|
8906
|
-
process.exit(0);
|
|
8907
|
-
}
|
|
8908
|
-
const action = await clack14.select({
|
|
9265
|
+
clack16.intro(pc17.bold(`WRAPS CLI v${VERSION}`));
|
|
9266
|
+
console.log("Welcome! Let's get started deploying your email infrastructure.\n");
|
|
9267
|
+
const action = await clack16.select({
|
|
8909
9268
|
message: "What would you like to do?",
|
|
8910
9269
|
options: [
|
|
8911
9270
|
{
|
|
@@ -8920,8 +9279,8 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
|
|
|
8920
9279
|
}
|
|
8921
9280
|
]
|
|
8922
9281
|
});
|
|
8923
|
-
if (
|
|
8924
|
-
|
|
9282
|
+
if (clack16.isCancel(action)) {
|
|
9283
|
+
clack16.cancel("Operation cancelled.");
|
|
8925
9284
|
process.exit(0);
|
|
8926
9285
|
}
|
|
8927
9286
|
if (action === "init") {
|
|
@@ -8950,20 +9309,20 @@ async function run() {
|
|
|
8950
9309
|
const telemetry = getTelemetryClient();
|
|
8951
9310
|
if (telemetry.shouldShowNotification()) {
|
|
8952
9311
|
console.log();
|
|
8953
|
-
|
|
9312
|
+
clack16.log.info(pc17.bold("Anonymous Telemetry"));
|
|
8954
9313
|
console.log(
|
|
8955
|
-
` Wraps collects ${
|
|
9314
|
+
` Wraps collects ${pc17.cyan("anonymous usage data")} to improve the CLI.`
|
|
8956
9315
|
);
|
|
8957
9316
|
console.log(
|
|
8958
|
-
` We ${
|
|
9317
|
+
` We ${pc17.bold("never")} collect: domains, AWS credentials, email content, or PII.`
|
|
8959
9318
|
);
|
|
8960
9319
|
console.log(
|
|
8961
|
-
` We ${
|
|
9320
|
+
` We ${pc17.bold("only")} collect: command names, success/failure, CLI version, OS.`
|
|
8962
9321
|
);
|
|
8963
9322
|
console.log();
|
|
8964
|
-
console.log(` Opt-out anytime: ${
|
|
8965
|
-
console.log(` Or set: ${
|
|
8966
|
-
console.log(` Learn more: ${
|
|
9323
|
+
console.log(` Opt-out anytime: ${pc17.cyan("wraps telemetry disable")}`);
|
|
9324
|
+
console.log(` Or set: ${pc17.cyan("WRAPS_TELEMETRY_DISABLED=1")}`);
|
|
9325
|
+
console.log(` Learn more: ${pc17.cyan("https://wraps.dev/docs/telemetry")}`);
|
|
8967
9326
|
console.log();
|
|
8968
9327
|
telemetry.markNotificationShown();
|
|
8969
9328
|
}
|
|
@@ -9010,15 +9369,33 @@ async function run() {
|
|
|
9010
9369
|
preview: flags.preview
|
|
9011
9370
|
});
|
|
9012
9371
|
break;
|
|
9372
|
+
case "status":
|
|
9373
|
+
await emailStatus({
|
|
9374
|
+
account: flags.account
|
|
9375
|
+
});
|
|
9376
|
+
break;
|
|
9377
|
+
case "verify": {
|
|
9378
|
+
if (!flags.domain) {
|
|
9379
|
+
clack16.log.error("--domain flag is required");
|
|
9380
|
+
console.log(
|
|
9381
|
+
`
|
|
9382
|
+
Usage: ${pc17.cyan("wraps email verify --domain yourapp.com")}
|
|
9383
|
+
`
|
|
9384
|
+
);
|
|
9385
|
+
process.exit(1);
|
|
9386
|
+
}
|
|
9387
|
+
await verifyDomain({ domain: flags.domain });
|
|
9388
|
+
break;
|
|
9389
|
+
}
|
|
9013
9390
|
case "domains": {
|
|
9014
9391
|
const domainsSubCommand = args.sub[2];
|
|
9015
9392
|
switch (domainsSubCommand) {
|
|
9016
9393
|
case "add": {
|
|
9017
9394
|
if (!flags.domain) {
|
|
9018
|
-
|
|
9395
|
+
clack16.log.error("--domain flag is required");
|
|
9019
9396
|
console.log(
|
|
9020
9397
|
`
|
|
9021
|
-
Usage: ${
|
|
9398
|
+
Usage: ${pc17.cyan("wraps email domains add --domain yourapp.com")}
|
|
9022
9399
|
`
|
|
9023
9400
|
);
|
|
9024
9401
|
process.exit(1);
|
|
@@ -9031,10 +9408,10 @@ Usage: ${pc15.cyan("wraps email domains add --domain yourapp.com")}
|
|
|
9031
9408
|
break;
|
|
9032
9409
|
case "verify": {
|
|
9033
9410
|
if (!flags.domain) {
|
|
9034
|
-
|
|
9411
|
+
clack16.log.error("--domain flag is required");
|
|
9035
9412
|
console.log(
|
|
9036
9413
|
`
|
|
9037
|
-
Usage: ${
|
|
9414
|
+
Usage: ${pc17.cyan("wraps email domains verify --domain yourapp.com")}
|
|
9038
9415
|
`
|
|
9039
9416
|
);
|
|
9040
9417
|
process.exit(1);
|
|
@@ -9044,10 +9421,10 @@ Usage: ${pc15.cyan("wraps email domains verify --domain yourapp.com")}
|
|
|
9044
9421
|
}
|
|
9045
9422
|
case "get-dkim": {
|
|
9046
9423
|
if (!flags.domain) {
|
|
9047
|
-
|
|
9424
|
+
clack16.log.error("--domain flag is required");
|
|
9048
9425
|
console.log(
|
|
9049
9426
|
`
|
|
9050
|
-
Usage: ${
|
|
9427
|
+
Usage: ${pc17.cyan("wraps email domains get-dkim --domain yourapp.com")}
|
|
9051
9428
|
`
|
|
9052
9429
|
);
|
|
9053
9430
|
process.exit(1);
|
|
@@ -9057,10 +9434,10 @@ Usage: ${pc15.cyan("wraps email domains get-dkim --domain yourapp.com")}
|
|
|
9057
9434
|
}
|
|
9058
9435
|
case "remove": {
|
|
9059
9436
|
if (!flags.domain) {
|
|
9060
|
-
|
|
9437
|
+
clack16.log.error("--domain flag is required");
|
|
9061
9438
|
console.log(
|
|
9062
9439
|
`
|
|
9063
|
-
Usage: ${
|
|
9440
|
+
Usage: ${pc17.cyan("wraps email domains remove --domain yourapp.com --force")}
|
|
9064
9441
|
`
|
|
9065
9442
|
);
|
|
9066
9443
|
process.exit(1);
|
|
@@ -9072,23 +9449,29 @@ Usage: ${pc15.cyan("wraps email domains remove --domain yourapp.com --force")}
|
|
|
9072
9449
|
break;
|
|
9073
9450
|
}
|
|
9074
9451
|
default:
|
|
9075
|
-
|
|
9452
|
+
clack16.log.error(
|
|
9076
9453
|
`Unknown domains command: ${domainsSubCommand || "(none)"}`
|
|
9077
9454
|
);
|
|
9078
9455
|
console.log(
|
|
9079
9456
|
`
|
|
9080
|
-
Available commands: ${
|
|
9457
|
+
Available commands: ${pc17.cyan("add")}, ${pc17.cyan("list")}, ${pc17.cyan("verify")}, ${pc17.cyan("get-dkim")}, ${pc17.cyan("remove")}
|
|
9081
9458
|
`
|
|
9082
9459
|
);
|
|
9083
9460
|
process.exit(1);
|
|
9084
9461
|
}
|
|
9085
9462
|
break;
|
|
9086
9463
|
}
|
|
9464
|
+
case "destroy":
|
|
9465
|
+
await emailDestroy({
|
|
9466
|
+
force: flags.force,
|
|
9467
|
+
preview: flags.preview
|
|
9468
|
+
});
|
|
9469
|
+
break;
|
|
9087
9470
|
default:
|
|
9088
|
-
|
|
9471
|
+
clack16.log.error(`Unknown email command: ${subCommand}`);
|
|
9089
9472
|
console.log(
|
|
9090
9473
|
`
|
|
9091
|
-
Run ${
|
|
9474
|
+
Run ${pc17.cyan("wraps --help")} for available commands.
|
|
9092
9475
|
`
|
|
9093
9476
|
);
|
|
9094
9477
|
process.exit(1);
|
|
@@ -9111,11 +9494,11 @@ Run ${pc15.cyan("wraps --help")} for available commands.
|
|
|
9111
9494
|
});
|
|
9112
9495
|
break;
|
|
9113
9496
|
default:
|
|
9114
|
-
|
|
9497
|
+
clack16.log.error(`Unknown dashboard command: ${subCommand}`);
|
|
9115
9498
|
console.log(`
|
|
9116
|
-
Available commands: ${
|
|
9499
|
+
Available commands: ${pc17.cyan("update-role")}
|
|
9117
9500
|
`);
|
|
9118
|
-
console.log(`Run ${
|
|
9501
|
+
console.log(`Run ${pc17.cyan("wraps --help")} for more information.
|
|
9119
9502
|
`);
|
|
9120
9503
|
process.exit(1);
|
|
9121
9504
|
}
|
|
@@ -9127,15 +9510,6 @@ Available commands: ${pc15.cyan("update-role")}
|
|
|
9127
9510
|
});
|
|
9128
9511
|
return;
|
|
9129
9512
|
}
|
|
9130
|
-
if (primaryCommand === "sms" && subCommand) {
|
|
9131
|
-
clack14.log.warn("SMS infrastructure is coming soon!");
|
|
9132
|
-
console.log(
|
|
9133
|
-
`
|
|
9134
|
-
Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-team/wraps")}
|
|
9135
|
-
`
|
|
9136
|
-
);
|
|
9137
|
-
process.exit(0);
|
|
9138
|
-
}
|
|
9139
9513
|
switch (primaryCommand) {
|
|
9140
9514
|
// Global commands (work across all services)
|
|
9141
9515
|
case "status":
|
|
@@ -9151,8 +9525,8 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
|
|
|
9151
9525
|
break;
|
|
9152
9526
|
case "dashboard":
|
|
9153
9527
|
if (!subCommand) {
|
|
9154
|
-
|
|
9155
|
-
`'wraps dashboard' is deprecated. Use ${
|
|
9528
|
+
clack16.log.warn(
|
|
9529
|
+
`'wraps dashboard' is deprecated. Use ${pc17.cyan("wraps console")} instead.`
|
|
9156
9530
|
);
|
|
9157
9531
|
await dashboard({
|
|
9158
9532
|
port: flags.port,
|
|
@@ -9182,10 +9556,10 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
|
|
|
9182
9556
|
await telemetryStatus();
|
|
9183
9557
|
break;
|
|
9184
9558
|
default:
|
|
9185
|
-
|
|
9559
|
+
clack16.log.error(`Unknown telemetry command: ${subCommand}`);
|
|
9186
9560
|
console.log(
|
|
9187
9561
|
`
|
|
9188
|
-
Available commands: ${
|
|
9562
|
+
Available commands: ${pc17.cyan("enable")}, ${pc17.cyan("disable")}, ${pc17.cyan("status")}
|
|
9189
9563
|
`
|
|
9190
9564
|
);
|
|
9191
9565
|
process.exit(1);
|
|
@@ -9194,7 +9568,6 @@ Available commands: ${pc15.cyan("enable")}, ${pc15.cyan("disable")}, ${pc15.cyan
|
|
|
9194
9568
|
}
|
|
9195
9569
|
// Show help for service without subcommand
|
|
9196
9570
|
case "email":
|
|
9197
|
-
case "sms":
|
|
9198
9571
|
console.log(
|
|
9199
9572
|
`
|
|
9200
9573
|
Please specify a command for ${primaryCommand} service.
|
|
@@ -9203,10 +9576,10 @@ Please specify a command for ${primaryCommand} service.
|
|
|
9203
9576
|
showHelp();
|
|
9204
9577
|
break;
|
|
9205
9578
|
default:
|
|
9206
|
-
|
|
9579
|
+
clack16.log.error(`Unknown command: ${primaryCommand}`);
|
|
9207
9580
|
console.log(
|
|
9208
9581
|
`
|
|
9209
|
-
Run ${
|
|
9582
|
+
Run ${pc17.cyan("wraps --help")} for available commands.
|
|
9210
9583
|
`
|
|
9211
9584
|
);
|
|
9212
9585
|
process.exit(1);
|