@wraps.dev/cli 1.5.1 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +968 -614
- package/dist/cli.js.map +1 -1
- package/dist/lambda/event-processor/.bundled +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -146,7 +146,7 @@ var require_package = __commonJS({
|
|
|
146
146
|
"package.json"(exports, module) {
|
|
147
147
|
module.exports = {
|
|
148
148
|
name: "@wraps.dev/cli",
|
|
149
|
-
version: "1.5.
|
|
149
|
+
version: "1.5.2",
|
|
150
150
|
description: "CLI for deploying Wraps email infrastructure to your AWS account",
|
|
151
151
|
type: "module",
|
|
152
152
|
main: "./dist/cli.js",
|
|
@@ -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,16 +5297,296 @@ ${pc6.dim("Example:")}`);
|
|
|
5263
5297
|
});
|
|
5264
5298
|
}
|
|
5265
5299
|
|
|
5300
|
+
// src/commands/email/destroy.ts
|
|
5301
|
+
init_esm_shims();
|
|
5302
|
+
init_events();
|
|
5303
|
+
init_aws();
|
|
5304
|
+
import * as clack6 from "@clack/prompts";
|
|
5305
|
+
import * as pulumi7 from "@pulumi/pulumi";
|
|
5306
|
+
import pc7 from "picocolors";
|
|
5307
|
+
|
|
5308
|
+
// src/utils/route53.ts
|
|
5309
|
+
init_esm_shims();
|
|
5310
|
+
import {
|
|
5311
|
+
ChangeResourceRecordSetsCommand as ChangeResourceRecordSetsCommand2,
|
|
5312
|
+
ListHostedZonesByNameCommand as ListHostedZonesByNameCommand2,
|
|
5313
|
+
ListResourceRecordSetsCommand,
|
|
5314
|
+
Route53Client as Route53Client2
|
|
5315
|
+
} from "@aws-sdk/client-route-53";
|
|
5316
|
+
async function findHostedZone2(domain, region) {
|
|
5317
|
+
const client = new Route53Client2({ region });
|
|
5318
|
+
try {
|
|
5319
|
+
const response = await client.send(
|
|
5320
|
+
new ListHostedZonesByNameCommand2({
|
|
5321
|
+
DNSName: domain,
|
|
5322
|
+
MaxItems: 1
|
|
5323
|
+
})
|
|
5324
|
+
);
|
|
5325
|
+
const zone = response.HostedZones?.[0];
|
|
5326
|
+
if (zone && zone.Name === `${domain}.` && zone.Id) {
|
|
5327
|
+
return {
|
|
5328
|
+
id: zone.Id.replace("/hostedzone/", ""),
|
|
5329
|
+
name: zone.Name
|
|
5330
|
+
};
|
|
5331
|
+
}
|
|
5332
|
+
return null;
|
|
5333
|
+
} catch (_error) {
|
|
5334
|
+
return null;
|
|
5335
|
+
}
|
|
5336
|
+
}
|
|
5337
|
+
async function deleteDNSRecords(hostedZoneId, domain, dkimTokens, region, customTrackingDomain, mailFromDomain) {
|
|
5338
|
+
const client = new Route53Client2({ region });
|
|
5339
|
+
const response = await client.send(
|
|
5340
|
+
new ListResourceRecordSetsCommand({
|
|
5341
|
+
HostedZoneId: hostedZoneId,
|
|
5342
|
+
MaxItems: 500
|
|
5343
|
+
})
|
|
5344
|
+
);
|
|
5345
|
+
const recordSets = response.ResourceRecordSets || [];
|
|
5346
|
+
const changes = [];
|
|
5347
|
+
const addDeletionIfExists = (name, type) => {
|
|
5348
|
+
const normalizedName = name.endsWith(".") ? name : `${name}.`;
|
|
5349
|
+
const record = recordSets.find(
|
|
5350
|
+
(rs) => rs.Name === normalizedName && rs.Type === type
|
|
5351
|
+
);
|
|
5352
|
+
if (record && record.ResourceRecords) {
|
|
5353
|
+
changes.push({
|
|
5354
|
+
Action: "DELETE",
|
|
5355
|
+
ResourceRecordSet: record
|
|
5356
|
+
});
|
|
5357
|
+
}
|
|
5358
|
+
};
|
|
5359
|
+
for (const token of dkimTokens) {
|
|
5360
|
+
addDeletionIfExists(`${token}._domainkey.${domain}`, "CNAME");
|
|
5361
|
+
}
|
|
5362
|
+
addDeletionIfExists(`_dmarc.${domain}`, "TXT");
|
|
5363
|
+
if (customTrackingDomain) {
|
|
5364
|
+
addDeletionIfExists(customTrackingDomain, "CNAME");
|
|
5365
|
+
}
|
|
5366
|
+
if (mailFromDomain) {
|
|
5367
|
+
addDeletionIfExists(mailFromDomain, "MX");
|
|
5368
|
+
addDeletionIfExists(mailFromDomain, "TXT");
|
|
5369
|
+
}
|
|
5370
|
+
if (changes.length === 0) {
|
|
5371
|
+
return;
|
|
5372
|
+
}
|
|
5373
|
+
await client.send(
|
|
5374
|
+
new ChangeResourceRecordSetsCommand2({
|
|
5375
|
+
HostedZoneId: hostedZoneId,
|
|
5376
|
+
ChangeBatch: {
|
|
5377
|
+
Changes: changes
|
|
5378
|
+
}
|
|
5379
|
+
})
|
|
5380
|
+
);
|
|
5381
|
+
}
|
|
5382
|
+
|
|
5383
|
+
// src/commands/email/destroy.ts
|
|
5384
|
+
async function getDkimTokensFromSES(domain, region) {
|
|
5385
|
+
try {
|
|
5386
|
+
const { SESv2Client: SESv2Client5, GetEmailIdentityCommand: GetEmailIdentityCommand4 } = await import("@aws-sdk/client-sesv2");
|
|
5387
|
+
const ses = new SESv2Client5({ region });
|
|
5388
|
+
const response = await ses.send(
|
|
5389
|
+
new GetEmailIdentityCommand4({ EmailIdentity: domain })
|
|
5390
|
+
);
|
|
5391
|
+
return response.DkimAttributes?.Tokens || [];
|
|
5392
|
+
} catch (_error) {
|
|
5393
|
+
return [];
|
|
5394
|
+
}
|
|
5395
|
+
}
|
|
5396
|
+
async function emailDestroy(options) {
|
|
5397
|
+
const startTime = Date.now();
|
|
5398
|
+
clack6.intro(
|
|
5399
|
+
pc7.bold(
|
|
5400
|
+
options.preview ? "Email Infrastructure Destruction Preview" : "Email Infrastructure Teardown"
|
|
5401
|
+
)
|
|
5402
|
+
);
|
|
5403
|
+
const progress = new DeploymentProgress();
|
|
5404
|
+
const identity = await progress.execute(
|
|
5405
|
+
"Validating AWS credentials",
|
|
5406
|
+
async () => validateAWSCredentials()
|
|
5407
|
+
);
|
|
5408
|
+
const region = await getAWSRegion();
|
|
5409
|
+
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
5410
|
+
const emailService = metadata?.services?.email;
|
|
5411
|
+
const emailConfig = emailService?.config;
|
|
5412
|
+
const domain = emailConfig?.domain;
|
|
5413
|
+
const storedStackName = emailService?.pulumiStackName;
|
|
5414
|
+
if (!(options.force || options.preview)) {
|
|
5415
|
+
const confirmed = await clack6.confirm({
|
|
5416
|
+
message: pc7.red(
|
|
5417
|
+
"Are you sure you want to destroy all email infrastructure?"
|
|
5418
|
+
),
|
|
5419
|
+
initialValue: false
|
|
5420
|
+
});
|
|
5421
|
+
if (clack6.isCancel(confirmed) || !confirmed) {
|
|
5422
|
+
clack6.cancel("Destruction cancelled.");
|
|
5423
|
+
process.exit(0);
|
|
5424
|
+
}
|
|
5425
|
+
}
|
|
5426
|
+
let shouldCleanDNS = false;
|
|
5427
|
+
let hostedZone = null;
|
|
5428
|
+
let dkimTokens = [];
|
|
5429
|
+
if (domain && !options.preview) {
|
|
5430
|
+
hostedZone = await findHostedZone2(domain, region);
|
|
5431
|
+
if (hostedZone) {
|
|
5432
|
+
dkimTokens = await getDkimTokensFromSES(domain, region);
|
|
5433
|
+
if (!options.force) {
|
|
5434
|
+
const cleanDNS = await clack6.confirm({
|
|
5435
|
+
message: `Found Route53 hosted zone for ${pc7.cyan(domain)}. Delete DNS records (DKIM, DMARC, MAIL FROM)?`,
|
|
5436
|
+
initialValue: true
|
|
5437
|
+
});
|
|
5438
|
+
if (clack6.isCancel(cleanDNS)) {
|
|
5439
|
+
clack6.cancel("Destruction cancelled.");
|
|
5440
|
+
process.exit(0);
|
|
5441
|
+
}
|
|
5442
|
+
shouldCleanDNS = cleanDNS;
|
|
5443
|
+
} else {
|
|
5444
|
+
shouldCleanDNS = true;
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
if (options.preview) {
|
|
5449
|
+
try {
|
|
5450
|
+
const previewResult = await progress.execute(
|
|
5451
|
+
"Generating destruction preview",
|
|
5452
|
+
async () => {
|
|
5453
|
+
await ensurePulumiWorkDir();
|
|
5454
|
+
const stackName = storedStackName || `wraps-email-${identity.accountId}-${region}`;
|
|
5455
|
+
let stack;
|
|
5456
|
+
try {
|
|
5457
|
+
stack = await pulumi7.automation.LocalWorkspace.selectStack({
|
|
5458
|
+
stackName,
|
|
5459
|
+
workDir: getPulumiWorkDir()
|
|
5460
|
+
});
|
|
5461
|
+
} catch (_error) {
|
|
5462
|
+
throw new Error("No email infrastructure found to preview");
|
|
5463
|
+
}
|
|
5464
|
+
const result = await stack.preview({ diff: true });
|
|
5465
|
+
return result;
|
|
5466
|
+
}
|
|
5467
|
+
);
|
|
5468
|
+
displayPreview({
|
|
5469
|
+
changeSummary: previewResult.changeSummary,
|
|
5470
|
+
costEstimate: "Monthly cost after destruction: $0.00",
|
|
5471
|
+
commandName: "wraps email destroy"
|
|
5472
|
+
});
|
|
5473
|
+
if (domain) {
|
|
5474
|
+
const previewHostedZone = await findHostedZone2(domain, region);
|
|
5475
|
+
if (previewHostedZone) {
|
|
5476
|
+
clack6.log.info(
|
|
5477
|
+
`DNS records in Route53 for ${pc7.cyan(domain)} will also be deleted`
|
|
5478
|
+
);
|
|
5479
|
+
}
|
|
5480
|
+
}
|
|
5481
|
+
clack6.outro(
|
|
5482
|
+
pc7.green("Preview complete. Run without --preview to destroy.")
|
|
5483
|
+
);
|
|
5484
|
+
trackServiceRemoved("email", {
|
|
5485
|
+
preview: true,
|
|
5486
|
+
duration_ms: Date.now() - startTime
|
|
5487
|
+
});
|
|
5488
|
+
return;
|
|
5489
|
+
} catch (error) {
|
|
5490
|
+
progress.stop();
|
|
5491
|
+
if (error.message.includes("No email infrastructure found")) {
|
|
5492
|
+
clack6.log.warn("No email infrastructure found to preview");
|
|
5493
|
+
process.exit(0);
|
|
5494
|
+
}
|
|
5495
|
+
trackError("PREVIEW_FAILED", "email destroy", { step: "preview" });
|
|
5496
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
5497
|
+
}
|
|
5498
|
+
}
|
|
5499
|
+
if (shouldCleanDNS && hostedZone && domain && dkimTokens.length > 0) {
|
|
5500
|
+
try {
|
|
5501
|
+
await progress.execute(
|
|
5502
|
+
`Deleting DNS records for ${domain}`,
|
|
5503
|
+
async () => {
|
|
5504
|
+
await deleteDNSRecords(
|
|
5505
|
+
hostedZone.id,
|
|
5506
|
+
domain,
|
|
5507
|
+
dkimTokens,
|
|
5508
|
+
region,
|
|
5509
|
+
emailConfig?.tracking?.customRedirectDomain,
|
|
5510
|
+
emailConfig?.mailFromDomain
|
|
5511
|
+
);
|
|
5512
|
+
}
|
|
5513
|
+
);
|
|
5514
|
+
} catch (error) {
|
|
5515
|
+
clack6.log.warn(`Could not delete DNS records: ${error.message}`);
|
|
5516
|
+
clack6.log.info("You may need to delete them manually from Route53");
|
|
5517
|
+
}
|
|
5518
|
+
}
|
|
5519
|
+
try {
|
|
5520
|
+
await progress.execute(
|
|
5521
|
+
"Destroying email infrastructure (this may take 2-3 minutes)",
|
|
5522
|
+
async () => {
|
|
5523
|
+
await ensurePulumiWorkDir();
|
|
5524
|
+
const stackName = storedStackName || `wraps-email-${identity.accountId}-${region}`;
|
|
5525
|
+
let stack;
|
|
5526
|
+
try {
|
|
5527
|
+
stack = await pulumi7.automation.LocalWorkspace.selectStack({
|
|
5528
|
+
stackName,
|
|
5529
|
+
workDir: getPulumiWorkDir()
|
|
5530
|
+
});
|
|
5531
|
+
} catch (_error) {
|
|
5532
|
+
throw new Error("No email infrastructure found to destroy");
|
|
5533
|
+
}
|
|
5534
|
+
await stack.destroy({ onOutput: () => {
|
|
5535
|
+
} });
|
|
5536
|
+
await stack.workspace.removeStack(stackName);
|
|
5537
|
+
}
|
|
5538
|
+
);
|
|
5539
|
+
} catch (error) {
|
|
5540
|
+
progress.stop();
|
|
5541
|
+
if (error.message.includes("No email infrastructure found")) {
|
|
5542
|
+
clack6.log.warn("No email infrastructure found");
|
|
5543
|
+
await deleteConnectionMetadata(identity.accountId, region);
|
|
5544
|
+
process.exit(0);
|
|
5545
|
+
}
|
|
5546
|
+
trackError("DESTROY_FAILED", "email destroy", { step: "destroy" });
|
|
5547
|
+
clack6.log.error("Email infrastructure destruction failed");
|
|
5548
|
+
throw error;
|
|
5549
|
+
}
|
|
5550
|
+
await deleteConnectionMetadata(identity.accountId, region);
|
|
5551
|
+
progress.stop();
|
|
5552
|
+
const deletedItems = ["AWS infrastructure"];
|
|
5553
|
+
if (shouldCleanDNS && hostedZone) {
|
|
5554
|
+
deletedItems.push("Route53 DNS records");
|
|
5555
|
+
}
|
|
5556
|
+
clack6.outro(pc7.green(`Email infrastructure has been removed`));
|
|
5557
|
+
if (domain) {
|
|
5558
|
+
console.log(`
|
|
5559
|
+
${pc7.bold("Cleaned up:")}`);
|
|
5560
|
+
for (const item of deletedItems) {
|
|
5561
|
+
console.log(` ${pc7.green("\u2713")} ${item}`);
|
|
5562
|
+
}
|
|
5563
|
+
console.log(
|
|
5564
|
+
`
|
|
5565
|
+
${pc7.dim("Note: SPF record was not deleted. Remove 'include:amazonses.com' manually if needed.")}`
|
|
5566
|
+
);
|
|
5567
|
+
}
|
|
5568
|
+
console.log(
|
|
5569
|
+
`
|
|
5570
|
+
Run ${pc7.cyan("wraps email init")} to deploy infrastructure again.
|
|
5571
|
+
`
|
|
5572
|
+
);
|
|
5573
|
+
trackServiceRemoved("email", {
|
|
5574
|
+
reason: "user_initiated",
|
|
5575
|
+
duration_ms: Date.now() - startTime,
|
|
5576
|
+
dns_cleaned: shouldCleanDNS
|
|
5577
|
+
});
|
|
5578
|
+
}
|
|
5579
|
+
|
|
5266
5580
|
// src/commands/email/domains.ts
|
|
5267
5581
|
init_esm_shims();
|
|
5268
5582
|
init_events();
|
|
5269
5583
|
init_aws();
|
|
5270
5584
|
import { Resolver } from "dns/promises";
|
|
5271
5585
|
import { GetEmailIdentityCommand, SESv2Client as SESv2Client2 } from "@aws-sdk/client-sesv2";
|
|
5272
|
-
import * as
|
|
5273
|
-
import
|
|
5586
|
+
import * as clack7 from "@clack/prompts";
|
|
5587
|
+
import pc8 from "picocolors";
|
|
5274
5588
|
async function verifyDomain(options) {
|
|
5275
|
-
|
|
5589
|
+
clack7.intro(pc8.bold(`Verifying ${options.domain}`));
|
|
5276
5590
|
const progress = new DeploymentProgress();
|
|
5277
5591
|
const region = await getAWSRegion();
|
|
5278
5592
|
const sesClient = new SESv2Client2({ region });
|
|
@@ -5293,10 +5607,10 @@ async function verifyDomain(options) {
|
|
|
5293
5607
|
mailFromDomain = identity.MailFromAttributes?.MailFromDomain;
|
|
5294
5608
|
} catch (_error) {
|
|
5295
5609
|
progress.stop();
|
|
5296
|
-
|
|
5610
|
+
clack7.log.error(`Domain ${options.domain} not found in SES`);
|
|
5297
5611
|
console.log(
|
|
5298
5612
|
`
|
|
5299
|
-
Run ${
|
|
5613
|
+
Run ${pc8.cyan(`wraps email init --domain ${options.domain}`)} to add this domain.
|
|
5300
5614
|
`
|
|
5301
5615
|
);
|
|
5302
5616
|
process.exit(1);
|
|
@@ -5401,55 +5715,55 @@ Run ${pc7.cyan(`wraps email init --domain ${options.domain}`)} to add this domai
|
|
|
5401
5715
|
const dkimStatus = identity.DkimAttributes?.Status || "PENDING";
|
|
5402
5716
|
const mailFromStatus = identity.MailFromAttributes?.MailFromDomainStatus || "NOT_CONFIGURED";
|
|
5403
5717
|
const statusLines = [
|
|
5404
|
-
`${
|
|
5405
|
-
`${
|
|
5406
|
-
`${
|
|
5718
|
+
`${pc8.bold("Domain:")} ${options.domain}`,
|
|
5719
|
+
`${pc8.bold("Verification Status:")} ${verificationStatus === "verified" ? pc8.green("\u2713 Verified") : pc8.yellow("\u23F1 Pending")}`,
|
|
5720
|
+
`${pc8.bold("DKIM Status:")} ${dkimStatus === "SUCCESS" ? pc8.green("\u2713 Success") : pc8.yellow(`\u23F1 ${dkimStatus}`)}`
|
|
5407
5721
|
];
|
|
5408
5722
|
if (mailFromDomain) {
|
|
5409
5723
|
statusLines.push(
|
|
5410
|
-
`${
|
|
5411
|
-
`${
|
|
5724
|
+
`${pc8.bold("MAIL FROM Domain:")} ${mailFromDomain}`,
|
|
5725
|
+
`${pc8.bold("MAIL FROM Status:")} ${mailFromStatus === "SUCCESS" ? pc8.green("\u2713 Success") : mailFromStatus === "NOT_CONFIGURED" ? pc8.yellow("\u23F1 Not Configured") : pc8.yellow(`\u23F1 ${mailFromStatus}`)}`
|
|
5412
5726
|
);
|
|
5413
5727
|
}
|
|
5414
|
-
|
|
5728
|
+
clack7.note(statusLines.join("\n"), "SES Status");
|
|
5415
5729
|
const dnsLines = dnsResults.map((record) => {
|
|
5416
5730
|
let statusIcon;
|
|
5417
5731
|
let statusColor;
|
|
5418
5732
|
if (record.status === "verified") {
|
|
5419
5733
|
statusIcon = "\u2713";
|
|
5420
|
-
statusColor =
|
|
5734
|
+
statusColor = pc8.green;
|
|
5421
5735
|
} else if (record.status === "incorrect") {
|
|
5422
5736
|
statusIcon = "\u2717";
|
|
5423
|
-
statusColor =
|
|
5737
|
+
statusColor = pc8.red;
|
|
5424
5738
|
} else {
|
|
5425
5739
|
statusIcon = "\u2717";
|
|
5426
|
-
statusColor =
|
|
5740
|
+
statusColor = pc8.red;
|
|
5427
5741
|
}
|
|
5428
5742
|
const recordInfo = record.records ? ` \u2192 ${record.records.join(", ")}` : "";
|
|
5429
5743
|
return ` ${statusColor(statusIcon)} ${record.name} (${record.type}) ${statusColor(
|
|
5430
5744
|
record.status
|
|
5431
5745
|
)}${recordInfo}`;
|
|
5432
5746
|
});
|
|
5433
|
-
|
|
5747
|
+
clack7.note(dnsLines.join("\n"), "DNS Records");
|
|
5434
5748
|
const allVerified = dnsResults.every((r) => r.status === "verified");
|
|
5435
5749
|
const someIncorrect = dnsResults.some((r) => r.status === "incorrect");
|
|
5436
5750
|
if (verificationStatus === "verified" && allVerified) {
|
|
5437
|
-
|
|
5438
|
-
|
|
5751
|
+
clack7.outro(
|
|
5752
|
+
pc8.green("\u2713 Domain is fully verified and ready to send emails!")
|
|
5439
5753
|
);
|
|
5440
5754
|
trackFeature("domain_verified", { dns_auto_detected: true });
|
|
5441
5755
|
} else if (someIncorrect) {
|
|
5442
|
-
|
|
5443
|
-
|
|
5756
|
+
clack7.outro(
|
|
5757
|
+
pc8.red("\u2717 Some DNS records are incorrect. Please update them.")
|
|
5444
5758
|
);
|
|
5445
5759
|
console.log(
|
|
5446
5760
|
`
|
|
5447
|
-
Run ${
|
|
5761
|
+
Run ${pc8.cyan("wraps email status")} to see the correct DNS records.
|
|
5448
5762
|
`
|
|
5449
5763
|
);
|
|
5450
5764
|
} else {
|
|
5451
|
-
|
|
5452
|
-
|
|
5765
|
+
clack7.outro(
|
|
5766
|
+
pc8.yellow("\u23F1 Waiting for DNS propagation and SES verification")
|
|
5453
5767
|
);
|
|
5454
5768
|
console.log("\nDNS records can take up to 48 hours to propagate.");
|
|
5455
5769
|
console.log(
|
|
@@ -5463,7 +5777,7 @@ Run ${pc7.cyan("wraps email status")} to see the correct DNS records.
|
|
|
5463
5777
|
});
|
|
5464
5778
|
}
|
|
5465
5779
|
async function addDomain(options) {
|
|
5466
|
-
|
|
5780
|
+
clack7.intro(pc8.bold(`Adding domain ${options.domain} to SES`));
|
|
5467
5781
|
const progress = new DeploymentProgress();
|
|
5468
5782
|
const region = await getAWSRegion();
|
|
5469
5783
|
const sesClient = new SESv2Client2({ region });
|
|
@@ -5473,10 +5787,10 @@ async function addDomain(options) {
|
|
|
5473
5787
|
new GetEmailIdentityCommand({ EmailIdentity: options.domain })
|
|
5474
5788
|
);
|
|
5475
5789
|
progress.stop();
|
|
5476
|
-
|
|
5790
|
+
clack7.log.warn(`Domain ${options.domain} already exists in SES`);
|
|
5477
5791
|
console.log(
|
|
5478
5792
|
`
|
|
5479
|
-
Run ${
|
|
5793
|
+
Run ${pc8.cyan(`wraps email domains verify --domain ${options.domain}`)} to check verification status.
|
|
5480
5794
|
`
|
|
5481
5795
|
);
|
|
5482
5796
|
return;
|
|
@@ -5501,22 +5815,22 @@ Run ${pc7.cyan(`wraps email domains verify --domain ${options.domain}`)} to chec
|
|
|
5501
5815
|
);
|
|
5502
5816
|
const dkimTokens = identity.DkimAttributes?.Tokens || [];
|
|
5503
5817
|
progress.stop();
|
|
5504
|
-
|
|
5818
|
+
clack7.outro(pc8.green(`\u2713 Domain ${options.domain} added successfully!`));
|
|
5505
5819
|
console.log(`
|
|
5506
|
-
${
|
|
5820
|
+
${pc8.bold("Next steps:")}
|
|
5507
5821
|
`);
|
|
5508
5822
|
console.log("1. Add the following DKIM records to your DNS:\n");
|
|
5509
5823
|
for (const token of dkimTokens) {
|
|
5510
|
-
console.log(` ${
|
|
5824
|
+
console.log(` ${pc8.cyan(`${token}._domainkey.${options.domain}`)}`);
|
|
5511
5825
|
console.log(
|
|
5512
|
-
` ${
|
|
5826
|
+
` ${pc8.dim("Type:")} CNAME ${pc8.dim("Value:")} ${token}.dkim.amazonses.com
|
|
5513
5827
|
`
|
|
5514
5828
|
);
|
|
5515
5829
|
}
|
|
5516
5830
|
console.log(
|
|
5517
|
-
`2. Verify DNS propagation: ${
|
|
5831
|
+
`2. Verify DNS propagation: ${pc8.cyan(`wraps email domains verify --domain ${options.domain}`)}`
|
|
5518
5832
|
);
|
|
5519
|
-
console.log(`3. Check status: ${
|
|
5833
|
+
console.log(`3. Check status: ${pc8.cyan("wraps email status")}
|
|
5520
5834
|
`);
|
|
5521
5835
|
trackCommand("email:domains:add", {
|
|
5522
5836
|
success: true
|
|
@@ -5531,7 +5845,7 @@ ${pc7.bold("Next steps:")}
|
|
|
5531
5845
|
}
|
|
5532
5846
|
}
|
|
5533
5847
|
async function listDomains() {
|
|
5534
|
-
|
|
5848
|
+
clack7.intro(pc8.bold("SES Email Domains"));
|
|
5535
5849
|
const progress = new DeploymentProgress();
|
|
5536
5850
|
const region = await getAWSRegion();
|
|
5537
5851
|
const sesClient = new SESv2Client2({ region });
|
|
@@ -5551,10 +5865,10 @@ async function listDomains() {
|
|
|
5551
5865
|
);
|
|
5552
5866
|
progress.stop();
|
|
5553
5867
|
if (domains.length === 0) {
|
|
5554
|
-
|
|
5868
|
+
clack7.outro("No domains found in SES");
|
|
5555
5869
|
console.log(
|
|
5556
5870
|
`
|
|
5557
|
-
Run ${
|
|
5871
|
+
Run ${pc8.cyan("wraps email domains add <domain>")} to add a domain.
|
|
5558
5872
|
`
|
|
5559
5873
|
);
|
|
5560
5874
|
return;
|
|
@@ -5582,17 +5896,17 @@ Run ${pc7.cyan("wraps email domains add <domain>")} to add a domain.
|
|
|
5582
5896
|
})
|
|
5583
5897
|
);
|
|
5584
5898
|
const domainLines = domainDetails.map((domain) => {
|
|
5585
|
-
const statusIcon = domain.verified ?
|
|
5586
|
-
const dkimIcon = domain.dkimStatus === "SUCCESS" ?
|
|
5587
|
-
return ` ${statusIcon} ${
|
|
5899
|
+
const statusIcon = domain.verified ? pc8.green("\u2713") : pc8.yellow("\u23F1");
|
|
5900
|
+
const dkimIcon = domain.dkimStatus === "SUCCESS" ? pc8.green("\u2713") : pc8.yellow("\u23F1");
|
|
5901
|
+
return ` ${statusIcon} ${pc8.bold(domain.name)} DKIM: ${dkimIcon} ${domain.dkimStatus}`;
|
|
5588
5902
|
});
|
|
5589
|
-
|
|
5903
|
+
clack7.note(
|
|
5590
5904
|
domainLines.join("\n"),
|
|
5591
5905
|
`${domains.length} domain(s) in ${region}`
|
|
5592
5906
|
);
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
`Run ${
|
|
5907
|
+
clack7.outro(
|
|
5908
|
+
pc8.dim(
|
|
5909
|
+
`Run ${pc8.cyan("wraps email domains verify --domain <domain>")} for details`
|
|
5596
5910
|
)
|
|
5597
5911
|
);
|
|
5598
5912
|
trackCommand("email:domains:list", {
|
|
@@ -5606,7 +5920,7 @@ Run ${pc7.cyan("wraps email domains add <domain>")} to add a domain.
|
|
|
5606
5920
|
}
|
|
5607
5921
|
}
|
|
5608
5922
|
async function getDkim(options) {
|
|
5609
|
-
|
|
5923
|
+
clack7.intro(pc8.bold(`DKIM Tokens for ${options.domain}`));
|
|
5610
5924
|
const progress = new DeploymentProgress();
|
|
5611
5925
|
const region = await getAWSRegion();
|
|
5612
5926
|
const sesClient = new SESv2Client2({ region });
|
|
@@ -5624,23 +5938,23 @@ async function getDkim(options) {
|
|
|
5624
5938
|
const dkimStatus = identity.DkimAttributes?.Status || "PENDING";
|
|
5625
5939
|
progress.stop();
|
|
5626
5940
|
if (dkimTokens.length === 0) {
|
|
5627
|
-
|
|
5941
|
+
clack7.outro(pc8.yellow("No DKIM tokens found for this domain"));
|
|
5628
5942
|
return;
|
|
5629
5943
|
}
|
|
5630
|
-
const statusLine = `${
|
|
5631
|
-
|
|
5944
|
+
const statusLine = `${pc8.bold("DKIM Status:")} ${dkimStatus === "SUCCESS" ? pc8.green("\u2713 Verified") : pc8.yellow(`\u23F1 ${dkimStatus}`)}`;
|
|
5945
|
+
clack7.note(statusLine, "Status");
|
|
5632
5946
|
console.log(`
|
|
5633
|
-
${
|
|
5947
|
+
${pc8.bold("DNS Records to add:")}
|
|
5634
5948
|
`);
|
|
5635
5949
|
for (const token of dkimTokens) {
|
|
5636
|
-
console.log(`${
|
|
5637
|
-
console.log(` ${
|
|
5638
|
-
console.log(` ${
|
|
5950
|
+
console.log(`${pc8.cyan(`${token}._domainkey.${options.domain}`)}`);
|
|
5951
|
+
console.log(` ${pc8.dim("Type:")} CNAME`);
|
|
5952
|
+
console.log(` ${pc8.dim("Value:")} ${token}.dkim.amazonses.com
|
|
5639
5953
|
`);
|
|
5640
5954
|
}
|
|
5641
5955
|
if (dkimStatus !== "SUCCESS") {
|
|
5642
5956
|
console.log(
|
|
5643
|
-
`${
|
|
5957
|
+
`${pc8.dim("After adding these records, run:")} ${pc8.cyan(`wraps email domains verify --domain ${options.domain}`)}
|
|
5644
5958
|
`
|
|
5645
5959
|
);
|
|
5646
5960
|
}
|
|
@@ -5652,10 +5966,10 @@ ${pc7.bold("DNS Records to add:")}
|
|
|
5652
5966
|
progress.stop();
|
|
5653
5967
|
trackCommand("email:domains:get-dkim", { success: false });
|
|
5654
5968
|
if (error.name === "NotFoundException") {
|
|
5655
|
-
|
|
5969
|
+
clack7.log.error(`Domain ${options.domain} not found in SES`);
|
|
5656
5970
|
console.log(
|
|
5657
5971
|
`
|
|
5658
|
-
Run ${
|
|
5972
|
+
Run ${pc8.cyan(`wraps email domains add ${options.domain}`)} to add this domain.
|
|
5659
5973
|
`
|
|
5660
5974
|
);
|
|
5661
5975
|
process.exit(1);
|
|
@@ -5665,7 +5979,7 @@ Run ${pc7.cyan(`wraps email domains add ${options.domain}`)} to add this domain.
|
|
|
5665
5979
|
}
|
|
5666
5980
|
}
|
|
5667
5981
|
async function removeDomain(options) {
|
|
5668
|
-
|
|
5982
|
+
clack7.intro(pc8.bold(`Remove domain ${options.domain} from SES`));
|
|
5669
5983
|
const progress = new DeploymentProgress();
|
|
5670
5984
|
const region = await getAWSRegion();
|
|
5671
5985
|
const sesClient = new SESv2Client2({ region });
|
|
@@ -5677,12 +5991,12 @@ async function removeDomain(options) {
|
|
|
5677
5991
|
});
|
|
5678
5992
|
progress.stop();
|
|
5679
5993
|
if (!options.force) {
|
|
5680
|
-
const shouldContinue = await
|
|
5681
|
-
message: `Are you sure you want to remove ${
|
|
5994
|
+
const shouldContinue = await clack7.confirm({
|
|
5995
|
+
message: `Are you sure you want to remove ${pc8.red(options.domain)} from SES?`,
|
|
5682
5996
|
initialValue: false
|
|
5683
5997
|
});
|
|
5684
|
-
if (
|
|
5685
|
-
|
|
5998
|
+
if (clack7.isCancel(shouldContinue) || !shouldContinue) {
|
|
5999
|
+
clack7.cancel("Operation cancelled");
|
|
5686
6000
|
process.exit(0);
|
|
5687
6001
|
}
|
|
5688
6002
|
}
|
|
@@ -5695,7 +6009,7 @@ async function removeDomain(options) {
|
|
|
5695
6009
|
);
|
|
5696
6010
|
});
|
|
5697
6011
|
progress.stop();
|
|
5698
|
-
|
|
6012
|
+
clack7.outro(pc8.green(`\u2713 Domain ${options.domain} removed successfully`));
|
|
5699
6013
|
trackCommand("email:domains:remove", {
|
|
5700
6014
|
success: true
|
|
5701
6015
|
});
|
|
@@ -5704,7 +6018,7 @@ async function removeDomain(options) {
|
|
|
5704
6018
|
progress.stop();
|
|
5705
6019
|
trackCommand("email:domains:remove", { success: false });
|
|
5706
6020
|
if (error.name === "NotFoundException") {
|
|
5707
|
-
|
|
6021
|
+
clack7.log.error(`Domain ${options.domain} not found in SES`);
|
|
5708
6022
|
process.exit(1);
|
|
5709
6023
|
return;
|
|
5710
6024
|
}
|
|
@@ -5714,9 +6028,9 @@ async function removeDomain(options) {
|
|
|
5714
6028
|
|
|
5715
6029
|
// src/commands/email/init.ts
|
|
5716
6030
|
init_esm_shims();
|
|
5717
|
-
import * as
|
|
5718
|
-
import * as
|
|
5719
|
-
import
|
|
6031
|
+
import * as clack8 from "@clack/prompts";
|
|
6032
|
+
import * as pulumi8 from "@pulumi/pulumi";
|
|
6033
|
+
import pc9 from "picocolors";
|
|
5720
6034
|
init_events();
|
|
5721
6035
|
init_costs();
|
|
5722
6036
|
init_presets();
|
|
@@ -5725,8 +6039,8 @@ init_errors();
|
|
|
5725
6039
|
init_prompts();
|
|
5726
6040
|
async function init(options) {
|
|
5727
6041
|
const startTime = Date.now();
|
|
5728
|
-
|
|
5729
|
-
|
|
6042
|
+
clack8.intro(
|
|
6043
|
+
pc9.bold(
|
|
5730
6044
|
options.preview ? "Wraps Email Infrastructure Preview" : "Wraps Email Infrastructure Setup"
|
|
5731
6045
|
)
|
|
5732
6046
|
);
|
|
@@ -5742,7 +6056,7 @@ async function init(options) {
|
|
|
5742
6056
|
"Validating AWS credentials",
|
|
5743
6057
|
async () => validateAWSCredentials()
|
|
5744
6058
|
);
|
|
5745
|
-
progress.info(`Connected to AWS account: ${
|
|
6059
|
+
progress.info(`Connected to AWS account: ${pc9.cyan(identity.accountId)}`);
|
|
5746
6060
|
let provider = options.provider;
|
|
5747
6061
|
if (!provider) {
|
|
5748
6062
|
provider = await promptProvider();
|
|
@@ -5765,12 +6079,12 @@ async function init(options) {
|
|
|
5765
6079
|
region
|
|
5766
6080
|
);
|
|
5767
6081
|
if (existingConnection) {
|
|
5768
|
-
|
|
5769
|
-
`Connection already exists for account ${
|
|
6082
|
+
clack8.log.warn(
|
|
6083
|
+
`Connection already exists for account ${pc9.cyan(identity.accountId)} in region ${pc9.cyan(region)}`
|
|
5770
6084
|
);
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
6085
|
+
clack8.log.info(`Created: ${existingConnection.timestamp}`);
|
|
6086
|
+
clack8.log.info(`Use ${pc9.cyan("wraps status")} to view current setup`);
|
|
6087
|
+
clack8.log.info(`Use ${pc9.cyan("wraps upgrade")} to add more features`);
|
|
5774
6088
|
process.exit(0);
|
|
5775
6089
|
}
|
|
5776
6090
|
let preset = options.preset;
|
|
@@ -5791,15 +6105,15 @@ async function init(options) {
|
|
|
5791
6105
|
}
|
|
5792
6106
|
const estimatedVolume = await promptEstimatedVolume();
|
|
5793
6107
|
progress.info(`
|
|
5794
|
-
${
|
|
6108
|
+
${pc9.bold("Cost Estimate:")}`);
|
|
5795
6109
|
const costSummary = getCostSummary(emailConfig, estimatedVolume);
|
|
5796
|
-
|
|
6110
|
+
clack8.log.info(costSummary);
|
|
5797
6111
|
const warnings = validateConfig(emailConfig);
|
|
5798
6112
|
if (warnings.length > 0) {
|
|
5799
6113
|
progress.info(`
|
|
5800
|
-
${
|
|
6114
|
+
${pc9.yellow(pc9.bold("Configuration Warnings:"))}`);
|
|
5801
6115
|
for (const warning of warnings) {
|
|
5802
|
-
|
|
6116
|
+
clack8.log.warn(warning);
|
|
5803
6117
|
}
|
|
5804
6118
|
}
|
|
5805
6119
|
const metadata = createConnectionMetadata(
|
|
@@ -5815,7 +6129,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5815
6129
|
if (!(options.yes || options.preview)) {
|
|
5816
6130
|
const confirmed = await confirmDeploy();
|
|
5817
6131
|
if (!confirmed) {
|
|
5818
|
-
|
|
6132
|
+
clack8.cancel("Deployment cancelled.");
|
|
5819
6133
|
process.exit(0);
|
|
5820
6134
|
}
|
|
5821
6135
|
}
|
|
@@ -5831,7 +6145,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5831
6145
|
"Generating infrastructure preview",
|
|
5832
6146
|
async () => {
|
|
5833
6147
|
await ensurePulumiWorkDir();
|
|
5834
|
-
const stack = await
|
|
6148
|
+
const stack = await pulumi8.automation.LocalWorkspace.createOrSelectStack(
|
|
5835
6149
|
{
|
|
5836
6150
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
5837
6151
|
projectName: "wraps-email",
|
|
@@ -5872,8 +6186,8 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5872
6186
|
costEstimate: costSummary,
|
|
5873
6187
|
commandName: "wraps email init"
|
|
5874
6188
|
});
|
|
5875
|
-
|
|
5876
|
-
|
|
6189
|
+
clack8.outro(
|
|
6190
|
+
pc9.green("Preview complete. Run without --preview to deploy.")
|
|
5877
6191
|
);
|
|
5878
6192
|
trackServiceInit("email", true, {
|
|
5879
6193
|
preset,
|
|
@@ -5896,7 +6210,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5896
6210
|
"Deploying infrastructure (this may take 2-3 minutes)",
|
|
5897
6211
|
async () => {
|
|
5898
6212
|
await ensurePulumiWorkDir();
|
|
5899
|
-
const stack = await
|
|
6213
|
+
const stack = await pulumi8.automation.LocalWorkspace.createOrSelectStack(
|
|
5900
6214
|
{
|
|
5901
6215
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
5902
6216
|
projectName: "wraps-email",
|
|
@@ -5972,8 +6286,8 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5972
6286
|
progress.info("Connection metadata saved for upgrade and restore capability");
|
|
5973
6287
|
let dnsAutoCreated = false;
|
|
5974
6288
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
5975
|
-
const { findHostedZone:
|
|
5976
|
-
const hostedZone = await
|
|
6289
|
+
const { findHostedZone: findHostedZone3, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
6290
|
+
const hostedZone = await findHostedZone3(outputs.domain, region);
|
|
5977
6291
|
if (hostedZone) {
|
|
5978
6292
|
try {
|
|
5979
6293
|
progress.start("Creating DNS records in Route53");
|
|
@@ -5989,7 +6303,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5989
6303
|
dnsAutoCreated = true;
|
|
5990
6304
|
} catch (error) {
|
|
5991
6305
|
progress.fail("Failed to create DNS records in Route53");
|
|
5992
|
-
|
|
6306
|
+
clack8.log.warn(`Could not auto-create DNS records: ${error.message}`);
|
|
5993
6307
|
}
|
|
5994
6308
|
}
|
|
5995
6309
|
}
|
|
@@ -6042,20 +6356,20 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
6042
6356
|
init_esm_shims();
|
|
6043
6357
|
init_events();
|
|
6044
6358
|
init_aws();
|
|
6045
|
-
import * as
|
|
6046
|
-
import * as
|
|
6047
|
-
import
|
|
6359
|
+
import * as clack9 from "@clack/prompts";
|
|
6360
|
+
import * as pulumi9 from "@pulumi/pulumi";
|
|
6361
|
+
import pc10 from "picocolors";
|
|
6048
6362
|
async function restore(options) {
|
|
6049
6363
|
const startTime = Date.now();
|
|
6050
|
-
|
|
6051
|
-
|
|
6364
|
+
clack9.intro(
|
|
6365
|
+
pc10.bold(
|
|
6052
6366
|
options.preview ? "Wraps Restore Preview" : "Wraps Restore - Remove Wraps Infrastructure"
|
|
6053
6367
|
)
|
|
6054
6368
|
);
|
|
6055
|
-
|
|
6056
|
-
`${
|
|
6369
|
+
clack9.log.info(
|
|
6370
|
+
`${pc10.yellow("Note:")} This will remove all Wraps-managed infrastructure.`
|
|
6057
6371
|
);
|
|
6058
|
-
|
|
6372
|
+
clack9.log.info(
|
|
6059
6373
|
"Your original AWS resources remain untouched (Wraps never modifies them).\n"
|
|
6060
6374
|
);
|
|
6061
6375
|
const progress = new DeploymentProgress();
|
|
@@ -6063,7 +6377,7 @@ async function restore(options) {
|
|
|
6063
6377
|
"Validating AWS credentials",
|
|
6064
6378
|
async () => validateAWSCredentials()
|
|
6065
6379
|
);
|
|
6066
|
-
progress.info(`Connected to AWS account: ${
|
|
6380
|
+
progress.info(`Connected to AWS account: ${pc10.cyan(identity.accountId)}`);
|
|
6067
6381
|
let region = options.region;
|
|
6068
6382
|
if (!region) {
|
|
6069
6383
|
const defaultRegion = await getAWSRegion();
|
|
@@ -6071,40 +6385,40 @@ async function restore(options) {
|
|
|
6071
6385
|
}
|
|
6072
6386
|
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
6073
6387
|
if (!metadata) {
|
|
6074
|
-
|
|
6075
|
-
`No Wraps connection found for account ${
|
|
6388
|
+
clack9.log.error(
|
|
6389
|
+
`No Wraps connection found for account ${pc10.cyan(identity.accountId)} in region ${pc10.cyan(region)}`
|
|
6076
6390
|
);
|
|
6077
|
-
|
|
6078
|
-
`Use ${
|
|
6391
|
+
clack9.log.info(
|
|
6392
|
+
`Use ${pc10.cyan("wraps email init")} or ${pc10.cyan("wraps email connect")} to create a connection first.`
|
|
6079
6393
|
);
|
|
6080
6394
|
process.exit(1);
|
|
6081
6395
|
}
|
|
6082
6396
|
progress.info(`Found connection created: ${metadata.timestamp}`);
|
|
6083
6397
|
console.log(
|
|
6084
6398
|
`
|
|
6085
|
-
${
|
|
6399
|
+
${pc10.bold("The following Wraps resources will be removed:")}
|
|
6086
6400
|
`
|
|
6087
6401
|
);
|
|
6088
6402
|
if (metadata.services.email?.config.tracking?.enabled) {
|
|
6089
|
-
console.log(` ${
|
|
6403
|
+
console.log(` ${pc10.cyan("\u2713")} Configuration Set (wraps-email-tracking)`);
|
|
6090
6404
|
}
|
|
6091
6405
|
if (metadata.services.email?.config.eventTracking?.dynamoDBHistory) {
|
|
6092
|
-
console.log(` ${
|
|
6406
|
+
console.log(` ${pc10.cyan("\u2713")} DynamoDB Table (wraps-email-history)`);
|
|
6093
6407
|
}
|
|
6094
6408
|
if (metadata.services.email?.config.eventTracking?.enabled) {
|
|
6095
|
-
console.log(` ${
|
|
6096
|
-
console.log(` ${
|
|
6097
|
-
console.log(` ${
|
|
6409
|
+
console.log(` ${pc10.cyan("\u2713")} EventBridge Rules`);
|
|
6410
|
+
console.log(` ${pc10.cyan("\u2713")} SQS Queues`);
|
|
6411
|
+
console.log(` ${pc10.cyan("\u2713")} Lambda Functions`);
|
|
6098
6412
|
}
|
|
6099
|
-
console.log(` ${
|
|
6413
|
+
console.log(` ${pc10.cyan("\u2713")} IAM Role (wraps-email-role)`);
|
|
6100
6414
|
console.log("");
|
|
6101
6415
|
if (!(options.force || options.preview)) {
|
|
6102
|
-
const confirmed = await
|
|
6416
|
+
const confirmed = await clack9.confirm({
|
|
6103
6417
|
message: "Proceed with removal? This cannot be undone.",
|
|
6104
6418
|
initialValue: false
|
|
6105
6419
|
});
|
|
6106
|
-
if (
|
|
6107
|
-
|
|
6420
|
+
if (clack9.isCancel(confirmed) || !confirmed) {
|
|
6421
|
+
clack9.cancel("Removal cancelled.");
|
|
6108
6422
|
process.exit(0);
|
|
6109
6423
|
}
|
|
6110
6424
|
}
|
|
@@ -6114,7 +6428,7 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
6114
6428
|
const previewResult = await progress.execute(
|
|
6115
6429
|
"Generating removal preview",
|
|
6116
6430
|
async () => {
|
|
6117
|
-
const stack = await
|
|
6431
|
+
const stack = await pulumi9.automation.LocalWorkspace.selectStack(
|
|
6118
6432
|
{
|
|
6119
6433
|
stackName: metadata.services.email.pulumiStackName,
|
|
6120
6434
|
projectName: "wraps-email",
|
|
@@ -6140,8 +6454,8 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
6140
6454
|
costEstimate: "Monthly cost after removal: $0.00",
|
|
6141
6455
|
commandName: "wraps email restore"
|
|
6142
6456
|
});
|
|
6143
|
-
|
|
6144
|
-
|
|
6457
|
+
clack9.outro(
|
|
6458
|
+
pc10.green(
|
|
6145
6459
|
"Preview complete. Run without --preview to remove infrastructure."
|
|
6146
6460
|
)
|
|
6147
6461
|
);
|
|
@@ -6163,7 +6477,7 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
6163
6477
|
if (!metadata.services.email?.pulumiStackName) {
|
|
6164
6478
|
throw new Error("No Pulumi stack name found in metadata");
|
|
6165
6479
|
}
|
|
6166
|
-
const stack = await
|
|
6480
|
+
const stack = await pulumi9.automation.LocalWorkspace.selectStack(
|
|
6167
6481
|
{
|
|
6168
6482
|
stackName: metadata.services.email.pulumiStackName,
|
|
6169
6483
|
projectName: "wraps-email",
|
|
@@ -6195,13 +6509,13 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
6195
6509
|
progress.info("Connection metadata deleted");
|
|
6196
6510
|
console.log(
|
|
6197
6511
|
`
|
|
6198
|
-
${
|
|
6512
|
+
${pc10.green("\u2713")} ${pc10.bold("Infrastructure removed successfully!")}
|
|
6199
6513
|
`
|
|
6200
6514
|
);
|
|
6201
6515
|
console.log(
|
|
6202
|
-
`${
|
|
6516
|
+
`${pc10.dim("All Wraps resources have been deleted from your AWS account.")}`
|
|
6203
6517
|
);
|
|
6204
|
-
console.log(`${
|
|
6518
|
+
console.log(`${pc10.dim("Your original AWS resources remain unchanged.")}
|
|
6205
6519
|
`);
|
|
6206
6520
|
trackServiceRemoved("email", {
|
|
6207
6521
|
reason: "user_initiated",
|
|
@@ -6209,11 +6523,102 @@ ${pc9.green("\u2713")} ${pc9.bold("Infrastructure removed successfully!")}
|
|
|
6209
6523
|
});
|
|
6210
6524
|
}
|
|
6211
6525
|
|
|
6526
|
+
// src/commands/email/status.ts
|
|
6527
|
+
init_esm_shims();
|
|
6528
|
+
init_events();
|
|
6529
|
+
init_aws();
|
|
6530
|
+
import * as clack10 from "@clack/prompts";
|
|
6531
|
+
import * as pulumi10 from "@pulumi/pulumi";
|
|
6532
|
+
import pc11 from "picocolors";
|
|
6533
|
+
async function emailStatus(_options) {
|
|
6534
|
+
const startTime = Date.now();
|
|
6535
|
+
const progress = new DeploymentProgress();
|
|
6536
|
+
clack10.intro(pc11.bold("Wraps Email Status"));
|
|
6537
|
+
const identity = await progress.execute(
|
|
6538
|
+
"Loading email infrastructure status",
|
|
6539
|
+
async () => validateAWSCredentials()
|
|
6540
|
+
);
|
|
6541
|
+
const region = await getAWSRegion();
|
|
6542
|
+
let stackOutputs = {};
|
|
6543
|
+
try {
|
|
6544
|
+
await ensurePulumiWorkDir();
|
|
6545
|
+
const stack = await pulumi10.automation.LocalWorkspace.selectStack({
|
|
6546
|
+
stackName: `wraps-${identity.accountId}-${region}`,
|
|
6547
|
+
workDir: getPulumiWorkDir()
|
|
6548
|
+
});
|
|
6549
|
+
stackOutputs = await stack.outputs();
|
|
6550
|
+
} catch (_error) {
|
|
6551
|
+
progress.stop();
|
|
6552
|
+
clack10.log.error("No email infrastructure found");
|
|
6553
|
+
console.log(
|
|
6554
|
+
`
|
|
6555
|
+
Run ${pc11.cyan("wraps email init")} to deploy email infrastructure.
|
|
6556
|
+
`
|
|
6557
|
+
);
|
|
6558
|
+
process.exit(1);
|
|
6559
|
+
}
|
|
6560
|
+
const domains = await listSESDomains(region);
|
|
6561
|
+
const { SESv2Client: SESv2Client5, GetEmailIdentityCommand: GetEmailIdentityCommand4 } = await import("@aws-sdk/client-sesv2");
|
|
6562
|
+
const sesv2Client = new SESv2Client5({ region });
|
|
6563
|
+
const domainsWithTokens = await Promise.all(
|
|
6564
|
+
domains.map(async (d) => {
|
|
6565
|
+
try {
|
|
6566
|
+
const identity2 = await sesv2Client.send(
|
|
6567
|
+
new GetEmailIdentityCommand4({ EmailIdentity: d.domain })
|
|
6568
|
+
);
|
|
6569
|
+
return {
|
|
6570
|
+
domain: d.domain,
|
|
6571
|
+
status: d.verified ? "verified" : "pending",
|
|
6572
|
+
dkimTokens: identity2.DkimAttributes?.Tokens || [],
|
|
6573
|
+
mailFromDomain: identity2.MailFromAttributes?.MailFromDomain,
|
|
6574
|
+
mailFromStatus: identity2.MailFromAttributes?.MailFromDomainStatus
|
|
6575
|
+
};
|
|
6576
|
+
} catch (_error) {
|
|
6577
|
+
return {
|
|
6578
|
+
domain: d.domain,
|
|
6579
|
+
status: d.verified ? "verified" : "pending",
|
|
6580
|
+
dkimTokens: void 0,
|
|
6581
|
+
mailFromDomain: void 0,
|
|
6582
|
+
mailFromStatus: void 0
|
|
6583
|
+
};
|
|
6584
|
+
}
|
|
6585
|
+
})
|
|
6586
|
+
);
|
|
6587
|
+
const integrationLevel = stackOutputs.configSetName ? "enhanced" : "dashboard-only";
|
|
6588
|
+
progress.stop();
|
|
6589
|
+
displayStatus({
|
|
6590
|
+
integrationLevel,
|
|
6591
|
+
region,
|
|
6592
|
+
domains: domainsWithTokens,
|
|
6593
|
+
resources: {
|
|
6594
|
+
roleArn: stackOutputs.roleArn?.value,
|
|
6595
|
+
configSetName: stackOutputs.configSetName?.value,
|
|
6596
|
+
tableName: stackOutputs.tableName?.value,
|
|
6597
|
+
lambdaFunctions: stackOutputs.lambdaFunctions?.value?.length || 0,
|
|
6598
|
+
snsTopics: integrationLevel === "enhanced" ? 1 : 0,
|
|
6599
|
+
archiveArn: stackOutputs.archiveArn?.value,
|
|
6600
|
+
archivingEnabled: stackOutputs.archivingEnabled?.value,
|
|
6601
|
+
archiveRetention: stackOutputs.archiveRetention?.value
|
|
6602
|
+
},
|
|
6603
|
+
tracking: stackOutputs.customTrackingDomain?.value ? {
|
|
6604
|
+
customTrackingDomain: stackOutputs.customTrackingDomain?.value,
|
|
6605
|
+
httpsEnabled: stackOutputs.httpsTrackingEnabled?.value,
|
|
6606
|
+
cloudFrontDomain: stackOutputs.cloudFrontDomain?.value
|
|
6607
|
+
} : void 0
|
|
6608
|
+
});
|
|
6609
|
+
trackCommand("email:status", {
|
|
6610
|
+
success: true,
|
|
6611
|
+
domain_count: domainsWithTokens.length,
|
|
6612
|
+
integration_level: integrationLevel,
|
|
6613
|
+
duration_ms: Date.now() - startTime
|
|
6614
|
+
});
|
|
6615
|
+
}
|
|
6616
|
+
|
|
6212
6617
|
// src/commands/email/upgrade.ts
|
|
6213
6618
|
init_esm_shims();
|
|
6214
|
-
import * as
|
|
6215
|
-
import * as
|
|
6216
|
-
import
|
|
6619
|
+
import * as clack11 from "@clack/prompts";
|
|
6620
|
+
import * as pulumi11 from "@pulumi/pulumi";
|
|
6621
|
+
import pc12 from "picocolors";
|
|
6217
6622
|
init_events();
|
|
6218
6623
|
init_costs();
|
|
6219
6624
|
init_presets();
|
|
@@ -6223,8 +6628,8 @@ init_prompts();
|
|
|
6223
6628
|
async function upgrade(options) {
|
|
6224
6629
|
const startTime = Date.now();
|
|
6225
6630
|
let upgradeAction = "";
|
|
6226
|
-
|
|
6227
|
-
|
|
6631
|
+
clack11.intro(
|
|
6632
|
+
pc12.bold(
|
|
6228
6633
|
options.preview ? "Wraps Upgrade Preview" : "Wraps Upgrade - Enhance Your Email Infrastructure"
|
|
6229
6634
|
)
|
|
6230
6635
|
);
|
|
@@ -6240,7 +6645,7 @@ async function upgrade(options) {
|
|
|
6240
6645
|
"Validating AWS credentials",
|
|
6241
6646
|
async () => validateAWSCredentials()
|
|
6242
6647
|
);
|
|
6243
|
-
progress.info(`Connected to AWS account: ${
|
|
6648
|
+
progress.info(`Connected to AWS account: ${pc12.cyan(identity.accountId)}`);
|
|
6244
6649
|
let region = options.region;
|
|
6245
6650
|
if (!region) {
|
|
6246
6651
|
const defaultRegion = await getAWSRegion();
|
|
@@ -6248,55 +6653,55 @@ async function upgrade(options) {
|
|
|
6248
6653
|
}
|
|
6249
6654
|
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
6250
6655
|
if (!metadata) {
|
|
6251
|
-
|
|
6252
|
-
`No Wraps connection found for account ${
|
|
6656
|
+
clack11.log.error(
|
|
6657
|
+
`No Wraps connection found for account ${pc12.cyan(identity.accountId)} in region ${pc12.cyan(region)}`
|
|
6253
6658
|
);
|
|
6254
|
-
|
|
6255
|
-
`Use ${
|
|
6659
|
+
clack11.log.info(
|
|
6660
|
+
`Use ${pc12.cyan("wraps email init")} to create new infrastructure or ${pc12.cyan("wraps email connect")} to connect existing.`
|
|
6256
6661
|
);
|
|
6257
6662
|
process.exit(1);
|
|
6258
6663
|
}
|
|
6259
6664
|
progress.info(`Found existing connection created: ${metadata.timestamp}`);
|
|
6260
6665
|
console.log(`
|
|
6261
|
-
${
|
|
6666
|
+
${pc12.bold("Current Configuration:")}
|
|
6262
6667
|
`);
|
|
6263
6668
|
if (metadata.services.email?.preset) {
|
|
6264
|
-
console.log(` Preset: ${
|
|
6669
|
+
console.log(` Preset: ${pc12.cyan(metadata.services.email?.preset)}`);
|
|
6265
6670
|
} else {
|
|
6266
|
-
console.log(` Preset: ${
|
|
6671
|
+
console.log(` Preset: ${pc12.cyan("custom")}`);
|
|
6267
6672
|
}
|
|
6268
6673
|
const config2 = metadata.services.email?.config;
|
|
6269
6674
|
if (!config2) {
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
`Use ${
|
|
6675
|
+
clack11.log.error("No email configuration found in metadata");
|
|
6676
|
+
clack11.log.info(
|
|
6677
|
+
`Use ${pc12.cyan("wraps email init")} to create new infrastructure.`
|
|
6273
6678
|
);
|
|
6274
6679
|
process.exit(1);
|
|
6275
6680
|
}
|
|
6276
6681
|
if (config2.domain) {
|
|
6277
|
-
console.log(` Sending Domain: ${
|
|
6682
|
+
console.log(` Sending Domain: ${pc12.cyan(config2.domain)}`);
|
|
6278
6683
|
}
|
|
6279
6684
|
if (config2.tracking?.enabled) {
|
|
6280
|
-
console.log(` ${
|
|
6685
|
+
console.log(` ${pc12.green("\u2713")} Open & Click Tracking`);
|
|
6281
6686
|
if (config2.tracking.customRedirectDomain) {
|
|
6282
6687
|
console.log(
|
|
6283
|
-
` ${
|
|
6688
|
+
` ${pc12.dim("\u2514\u2500")} Custom domain: ${pc12.cyan(config2.tracking.customRedirectDomain)}`
|
|
6284
6689
|
);
|
|
6285
6690
|
}
|
|
6286
6691
|
}
|
|
6287
6692
|
if (config2.suppressionList?.enabled) {
|
|
6288
|
-
console.log(` ${
|
|
6693
|
+
console.log(` ${pc12.green("\u2713")} Bounce/Complaint Suppression`);
|
|
6289
6694
|
}
|
|
6290
6695
|
if (config2.eventTracking?.enabled) {
|
|
6291
|
-
console.log(` ${
|
|
6696
|
+
console.log(` ${pc12.green("\u2713")} Event Tracking (EventBridge)`);
|
|
6292
6697
|
if (config2.eventTracking.dynamoDBHistory) {
|
|
6293
6698
|
console.log(
|
|
6294
|
-
` ${
|
|
6699
|
+
` ${pc12.dim("\u2514\u2500")} Email History: ${pc12.cyan(config2.eventTracking.archiveRetention || "90days")}`
|
|
6295
6700
|
);
|
|
6296
6701
|
}
|
|
6297
6702
|
}
|
|
6298
6703
|
if (config2.dedicatedIp) {
|
|
6299
|
-
console.log(` ${
|
|
6704
|
+
console.log(` ${pc12.green("\u2713")} Dedicated IP Address`);
|
|
6300
6705
|
}
|
|
6301
6706
|
if (config2.emailArchiving?.enabled) {
|
|
6302
6707
|
const retentionLabel = {
|
|
@@ -6321,15 +6726,15 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6321
6726
|
indefinite: "indefinite",
|
|
6322
6727
|
permanent: "permanent"
|
|
6323
6728
|
}[config2.emailArchiving.retention] || "90 days";
|
|
6324
|
-
console.log(` ${
|
|
6729
|
+
console.log(` ${pc12.green("\u2713")} Email Archiving (${retentionLabel})`);
|
|
6325
6730
|
}
|
|
6326
6731
|
const currentCostData = calculateCosts(config2, 5e4);
|
|
6327
6732
|
console.log(
|
|
6328
6733
|
`
|
|
6329
|
-
Estimated Cost: ${
|
|
6734
|
+
Estimated Cost: ${pc12.cyan(`~${formatCost(currentCostData.total.monthly)}/mo`)}`
|
|
6330
6735
|
);
|
|
6331
6736
|
console.log("");
|
|
6332
|
-
upgradeAction = await
|
|
6737
|
+
upgradeAction = await clack11.select({
|
|
6333
6738
|
message: "What would you like to do?",
|
|
6334
6739
|
options: [
|
|
6335
6740
|
{
|
|
@@ -6369,8 +6774,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6369
6774
|
}
|
|
6370
6775
|
]
|
|
6371
6776
|
});
|
|
6372
|
-
if (
|
|
6373
|
-
|
|
6777
|
+
if (clack11.isCancel(upgradeAction)) {
|
|
6778
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6374
6779
|
process.exit(0);
|
|
6375
6780
|
}
|
|
6376
6781
|
let updatedConfig = { ...config2 };
|
|
@@ -6388,15 +6793,15 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6388
6793
|
disabled: currentPresetIdx >= 0 && idx <= currentPresetIdx ? "Current or lower tier" : void 0
|
|
6389
6794
|
})).filter((p) => !p.disabled);
|
|
6390
6795
|
if (availablePresets.length === 0) {
|
|
6391
|
-
|
|
6796
|
+
clack11.log.warn("Already on highest preset (Enterprise)");
|
|
6392
6797
|
process.exit(0);
|
|
6393
6798
|
}
|
|
6394
|
-
const selectedPreset = await
|
|
6799
|
+
const selectedPreset = await clack11.select({
|
|
6395
6800
|
message: "Select new preset:",
|
|
6396
6801
|
options: availablePresets
|
|
6397
6802
|
});
|
|
6398
|
-
if (
|
|
6399
|
-
|
|
6803
|
+
if (clack11.isCancel(selectedPreset)) {
|
|
6804
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6400
6805
|
process.exit(0);
|
|
6401
6806
|
}
|
|
6402
6807
|
const presetConfig = getPreset(selectedPreset);
|
|
@@ -6406,7 +6811,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6406
6811
|
}
|
|
6407
6812
|
case "archiving": {
|
|
6408
6813
|
if (config2.emailArchiving?.enabled) {
|
|
6409
|
-
const archivingAction = await
|
|
6814
|
+
const archivingAction = await clack11.select({
|
|
6410
6815
|
message: "What would you like to do with email archiving?",
|
|
6411
6816
|
options: [
|
|
6412
6817
|
{
|
|
@@ -6421,17 +6826,17 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6421
6826
|
}
|
|
6422
6827
|
]
|
|
6423
6828
|
});
|
|
6424
|
-
if (
|
|
6425
|
-
|
|
6829
|
+
if (clack11.isCancel(archivingAction)) {
|
|
6830
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6426
6831
|
process.exit(0);
|
|
6427
6832
|
}
|
|
6428
6833
|
if (archivingAction === "disable") {
|
|
6429
|
-
const confirmDisable = await
|
|
6834
|
+
const confirmDisable = await clack11.confirm({
|
|
6430
6835
|
message: "Are you sure? Existing archived emails will remain, but new emails won't be archived.",
|
|
6431
6836
|
initialValue: false
|
|
6432
6837
|
});
|
|
6433
|
-
if (
|
|
6434
|
-
|
|
6838
|
+
if (clack11.isCancel(confirmDisable) || !confirmDisable) {
|
|
6839
|
+
clack11.cancel("Archiving not disabled.");
|
|
6435
6840
|
process.exit(0);
|
|
6436
6841
|
}
|
|
6437
6842
|
updatedConfig = {
|
|
@@ -6442,7 +6847,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6442
6847
|
}
|
|
6443
6848
|
};
|
|
6444
6849
|
} else {
|
|
6445
|
-
const retention = await
|
|
6850
|
+
const retention = await clack11.select({
|
|
6446
6851
|
message: "Email archive retention period:",
|
|
6447
6852
|
options: [
|
|
6448
6853
|
{
|
|
@@ -6478,8 +6883,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6478
6883
|
],
|
|
6479
6884
|
initialValue: config2.emailArchiving.retention
|
|
6480
6885
|
});
|
|
6481
|
-
if (
|
|
6482
|
-
|
|
6886
|
+
if (clack11.isCancel(retention)) {
|
|
6887
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6483
6888
|
process.exit(0);
|
|
6484
6889
|
}
|
|
6485
6890
|
updatedConfig = {
|
|
@@ -6491,19 +6896,19 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6491
6896
|
};
|
|
6492
6897
|
}
|
|
6493
6898
|
} else {
|
|
6494
|
-
const enableArchiving = await
|
|
6899
|
+
const enableArchiving = await clack11.confirm({
|
|
6495
6900
|
message: "Enable email archiving? (Store full email content with HTML for viewing)",
|
|
6496
6901
|
initialValue: true
|
|
6497
6902
|
});
|
|
6498
|
-
if (
|
|
6499
|
-
|
|
6903
|
+
if (clack11.isCancel(enableArchiving)) {
|
|
6904
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6500
6905
|
process.exit(0);
|
|
6501
6906
|
}
|
|
6502
6907
|
if (!enableArchiving) {
|
|
6503
|
-
|
|
6908
|
+
clack11.log.info("Email archiving not enabled.");
|
|
6504
6909
|
process.exit(0);
|
|
6505
6910
|
}
|
|
6506
|
-
const retention = await
|
|
6911
|
+
const retention = await clack11.select({
|
|
6507
6912
|
message: "Email archive retention period:",
|
|
6508
6913
|
options: [
|
|
6509
6914
|
{
|
|
@@ -6539,17 +6944,17 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6539
6944
|
],
|
|
6540
6945
|
initialValue: "90days"
|
|
6541
6946
|
});
|
|
6542
|
-
if (
|
|
6543
|
-
|
|
6947
|
+
if (clack11.isCancel(retention)) {
|
|
6948
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6544
6949
|
process.exit(0);
|
|
6545
6950
|
}
|
|
6546
|
-
|
|
6547
|
-
|
|
6951
|
+
clack11.log.info(
|
|
6952
|
+
pc12.dim(
|
|
6548
6953
|
"Archiving stores full RFC 822 emails with HTML, attachments, and headers"
|
|
6549
6954
|
)
|
|
6550
6955
|
);
|
|
6551
|
-
|
|
6552
|
-
|
|
6956
|
+
clack11.log.info(
|
|
6957
|
+
pc12.dim(
|
|
6553
6958
|
"Cost: $2/GB ingestion + $0.19/GB/month storage (~50KB per email)"
|
|
6554
6959
|
)
|
|
6555
6960
|
);
|
|
@@ -6566,11 +6971,11 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6566
6971
|
}
|
|
6567
6972
|
case "tracking-domain": {
|
|
6568
6973
|
if (!config2.domain) {
|
|
6569
|
-
|
|
6974
|
+
clack11.log.error(
|
|
6570
6975
|
"No sending domain configured. You must configure a sending domain before adding a custom tracking domain."
|
|
6571
6976
|
);
|
|
6572
|
-
|
|
6573
|
-
`Use ${
|
|
6977
|
+
clack11.log.info(
|
|
6978
|
+
`Use ${pc12.cyan("wraps email init")} to set up a sending domain first.`
|
|
6574
6979
|
);
|
|
6575
6980
|
process.exit(1);
|
|
6576
6981
|
}
|
|
@@ -6581,21 +6986,21 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6581
6986
|
);
|
|
6582
6987
|
const sendingDomain = domains.find((d) => d.domain === config2.domain);
|
|
6583
6988
|
if (!sendingDomain?.verified) {
|
|
6584
|
-
|
|
6585
|
-
`Sending domain ${
|
|
6989
|
+
clack11.log.error(
|
|
6990
|
+
`Sending domain ${pc12.cyan(config2.domain)} is not verified.`
|
|
6586
6991
|
);
|
|
6587
|
-
|
|
6992
|
+
clack11.log.info(
|
|
6588
6993
|
"You must verify your sending domain before adding a custom tracking domain."
|
|
6589
6994
|
);
|
|
6590
|
-
|
|
6591
|
-
`Use ${
|
|
6995
|
+
clack11.log.info(
|
|
6996
|
+
`Use ${pc12.cyan("wraps email verify")} to check DNS records and complete verification.`
|
|
6592
6997
|
);
|
|
6593
6998
|
process.exit(1);
|
|
6594
6999
|
}
|
|
6595
7000
|
progress.info(
|
|
6596
|
-
`Sending domain ${
|
|
7001
|
+
`Sending domain ${pc12.cyan(config2.domain)} is verified ${pc12.green("\u2713")}`
|
|
6597
7002
|
);
|
|
6598
|
-
const trackingDomain = await
|
|
7003
|
+
const trackingDomain = await clack11.text({
|
|
6599
7004
|
message: "Custom tracking redirect domain:",
|
|
6600
7005
|
placeholder: "track.yourdomain.com",
|
|
6601
7006
|
initialValue: config2.tracking?.customRedirectDomain || "",
|
|
@@ -6605,62 +7010,62 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6605
7010
|
}
|
|
6606
7011
|
}
|
|
6607
7012
|
});
|
|
6608
|
-
if (
|
|
6609
|
-
|
|
7013
|
+
if (clack11.isCancel(trackingDomain)) {
|
|
7014
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6610
7015
|
process.exit(0);
|
|
6611
7016
|
}
|
|
6612
|
-
const enableHttps = await
|
|
7017
|
+
const enableHttps = await clack11.confirm({
|
|
6613
7018
|
message: "Enable HTTPS tracking with CloudFront + SSL certificate?",
|
|
6614
7019
|
initialValue: true
|
|
6615
7020
|
});
|
|
6616
|
-
if (
|
|
6617
|
-
|
|
7021
|
+
if (clack11.isCancel(enableHttps)) {
|
|
7022
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6618
7023
|
process.exit(0);
|
|
6619
7024
|
}
|
|
6620
7025
|
if (enableHttps) {
|
|
6621
|
-
|
|
6622
|
-
|
|
7026
|
+
clack11.log.info(
|
|
7027
|
+
pc12.dim(
|
|
6623
7028
|
"HTTPS tracking creates a CloudFront distribution with an SSL certificate."
|
|
6624
7029
|
)
|
|
6625
7030
|
);
|
|
6626
|
-
|
|
6627
|
-
|
|
7031
|
+
clack11.log.info(
|
|
7032
|
+
pc12.dim(
|
|
6628
7033
|
"This ensures all tracking links use secure HTTPS connections."
|
|
6629
7034
|
)
|
|
6630
7035
|
);
|
|
6631
|
-
const { findHostedZone:
|
|
7036
|
+
const { findHostedZone: findHostedZone3 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
6632
7037
|
const hostedZone = await progress.execute(
|
|
6633
7038
|
"Checking for Route53 hosted zone",
|
|
6634
|
-
async () => await
|
|
7039
|
+
async () => await findHostedZone3(trackingDomain || config2.domain, region)
|
|
6635
7040
|
);
|
|
6636
7041
|
if (hostedZone) {
|
|
6637
7042
|
progress.info(
|
|
6638
|
-
`Found Route53 hosted zone: ${
|
|
7043
|
+
`Found Route53 hosted zone: ${pc12.cyan(hostedZone.name)} ${pc12.green("\u2713")}`
|
|
6639
7044
|
);
|
|
6640
|
-
|
|
6641
|
-
|
|
7045
|
+
clack11.log.info(
|
|
7046
|
+
pc12.dim(
|
|
6642
7047
|
"DNS records (SSL certificate validation + CloudFront) will be created automatically."
|
|
6643
7048
|
)
|
|
6644
7049
|
);
|
|
6645
7050
|
} else {
|
|
6646
|
-
|
|
6647
|
-
`No Route53 hosted zone found for ${
|
|
7051
|
+
clack11.log.warn(
|
|
7052
|
+
`No Route53 hosted zone found for ${pc12.cyan(trackingDomain || config2.domain)}`
|
|
6648
7053
|
);
|
|
6649
|
-
|
|
6650
|
-
|
|
7054
|
+
clack11.log.info(
|
|
7055
|
+
pc12.dim(
|
|
6651
7056
|
"You'll need to manually create DNS records for SSL certificate validation and CloudFront."
|
|
6652
7057
|
)
|
|
6653
7058
|
);
|
|
6654
|
-
|
|
6655
|
-
|
|
7059
|
+
clack11.log.info(
|
|
7060
|
+
pc12.dim("DNS record details will be shown after deployment.")
|
|
6656
7061
|
);
|
|
6657
7062
|
}
|
|
6658
|
-
const confirmHttps = await
|
|
7063
|
+
const confirmHttps = await clack11.confirm({
|
|
6659
7064
|
message: hostedZone ? "Proceed with automatic HTTPS setup?" : "Proceed with manual HTTPS setup (requires DNS configuration)?",
|
|
6660
7065
|
initialValue: true
|
|
6661
7066
|
});
|
|
6662
|
-
if (
|
|
6663
|
-
|
|
7067
|
+
if (clack11.isCancel(confirmHttps) || !confirmHttps) {
|
|
7068
|
+
clack11.log.info("HTTPS tracking not enabled. Using HTTP tracking.");
|
|
6664
7069
|
updatedConfig = {
|
|
6665
7070
|
...config2,
|
|
6666
7071
|
tracking: {
|
|
@@ -6682,8 +7087,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6682
7087
|
};
|
|
6683
7088
|
}
|
|
6684
7089
|
} else {
|
|
6685
|
-
|
|
6686
|
-
|
|
7090
|
+
clack11.log.info(
|
|
7091
|
+
pc12.dim(
|
|
6687
7092
|
"Using HTTP tracking (standard). Links will use http:// protocol."
|
|
6688
7093
|
)
|
|
6689
7094
|
);
|
|
@@ -6701,7 +7106,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6701
7106
|
break;
|
|
6702
7107
|
}
|
|
6703
7108
|
case "retention": {
|
|
6704
|
-
const retention = await
|
|
7109
|
+
const retention = await clack11.select({
|
|
6705
7110
|
message: "Email history retention period (event data in DynamoDB):",
|
|
6706
7111
|
options: [
|
|
6707
7112
|
{ value: "7days", label: "7 days", hint: "Minimal storage cost" },
|
|
@@ -6725,17 +7130,17 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6725
7130
|
],
|
|
6726
7131
|
initialValue: config2.eventTracking?.archiveRetention || "90days"
|
|
6727
7132
|
});
|
|
6728
|
-
if (
|
|
6729
|
-
|
|
7133
|
+
if (clack11.isCancel(retention)) {
|
|
7134
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6730
7135
|
process.exit(0);
|
|
6731
7136
|
}
|
|
6732
|
-
|
|
6733
|
-
|
|
7137
|
+
clack11.log.info(
|
|
7138
|
+
pc12.dim(
|
|
6734
7139
|
"Note: This is for event data (sent, delivered, opened, etc.) stored in DynamoDB."
|
|
6735
7140
|
)
|
|
6736
7141
|
);
|
|
6737
|
-
|
|
6738
|
-
|
|
7142
|
+
clack11.log.info(
|
|
7143
|
+
pc12.dim(
|
|
6739
7144
|
"For full email content storage, use 'Enable email archiving' option."
|
|
6740
7145
|
)
|
|
6741
7146
|
);
|
|
@@ -6752,7 +7157,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6752
7157
|
break;
|
|
6753
7158
|
}
|
|
6754
7159
|
case "events": {
|
|
6755
|
-
const selectedEvents = await
|
|
7160
|
+
const selectedEvents = await clack11.multiselect({
|
|
6756
7161
|
message: "Select SES event types to track:",
|
|
6757
7162
|
options: [
|
|
6758
7163
|
{ value: "SEND", label: "Send", hint: "Email sent to SES" },
|
|
@@ -6796,8 +7201,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6796
7201
|
],
|
|
6797
7202
|
required: true
|
|
6798
7203
|
});
|
|
6799
|
-
if (
|
|
6800
|
-
|
|
7204
|
+
if (clack11.isCancel(selectedEvents)) {
|
|
7205
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6801
7206
|
process.exit(0);
|
|
6802
7207
|
}
|
|
6803
7208
|
updatedConfig = {
|
|
@@ -6812,16 +7217,16 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6812
7217
|
break;
|
|
6813
7218
|
}
|
|
6814
7219
|
case "dedicated-ip": {
|
|
6815
|
-
const confirmed = await
|
|
7220
|
+
const confirmed = await clack11.confirm({
|
|
6816
7221
|
message: "Enable dedicated IP? (Requires 100k+ emails/day, adds ~$50-100/mo)",
|
|
6817
7222
|
initialValue: false
|
|
6818
7223
|
});
|
|
6819
|
-
if (
|
|
6820
|
-
|
|
7224
|
+
if (clack11.isCancel(confirmed)) {
|
|
7225
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6821
7226
|
process.exit(0);
|
|
6822
7227
|
}
|
|
6823
7228
|
if (!confirmed) {
|
|
6824
|
-
|
|
7229
|
+
clack11.log.info("Dedicated IP not enabled.");
|
|
6825
7230
|
process.exit(0);
|
|
6826
7231
|
}
|
|
6827
7232
|
updatedConfig = {
|
|
@@ -6842,28 +7247,28 @@ ${pc10.bold("Current Configuration:")}
|
|
|
6842
7247
|
const newCostData = calculateCosts(updatedConfig, 5e4);
|
|
6843
7248
|
const costDiff = newCostData.total.monthly - currentCostData.total.monthly;
|
|
6844
7249
|
console.log(`
|
|
6845
|
-
${
|
|
7250
|
+
${pc12.bold("Cost Impact:")}`);
|
|
6846
7251
|
console.log(
|
|
6847
|
-
` Current: ${
|
|
7252
|
+
` Current: ${pc12.cyan(`${formatCost(currentCostData.total.monthly)}/mo`)}`
|
|
6848
7253
|
);
|
|
6849
7254
|
console.log(
|
|
6850
|
-
` New: ${
|
|
7255
|
+
` New: ${pc12.cyan(`${formatCost(newCostData.total.monthly)}/mo`)}`
|
|
6851
7256
|
);
|
|
6852
7257
|
if (costDiff > 0) {
|
|
6853
|
-
console.log(` Change: ${
|
|
7258
|
+
console.log(` Change: ${pc12.yellow(`+${formatCost(costDiff)}/mo`)}`);
|
|
6854
7259
|
} else if (costDiff < 0) {
|
|
6855
7260
|
console.log(
|
|
6856
|
-
` Change: ${
|
|
7261
|
+
` Change: ${pc12.green(`${formatCost(Math.abs(costDiff))}/mo`)}`
|
|
6857
7262
|
);
|
|
6858
7263
|
}
|
|
6859
7264
|
console.log("");
|
|
6860
7265
|
if (!(options.yes || options.preview)) {
|
|
6861
|
-
const confirmed = await
|
|
7266
|
+
const confirmed = await clack11.confirm({
|
|
6862
7267
|
message: "Proceed with upgrade?",
|
|
6863
7268
|
initialValue: true
|
|
6864
7269
|
});
|
|
6865
|
-
if (
|
|
6866
|
-
|
|
7270
|
+
if (clack11.isCancel(confirmed) || !confirmed) {
|
|
7271
|
+
clack11.cancel("Upgrade cancelled.");
|
|
6867
7272
|
process.exit(0);
|
|
6868
7273
|
}
|
|
6869
7274
|
}
|
|
@@ -6885,7 +7290,7 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6885
7290
|
"Generating upgrade preview",
|
|
6886
7291
|
async () => {
|
|
6887
7292
|
await ensurePulumiWorkDir();
|
|
6888
|
-
const stack = await
|
|
7293
|
+
const stack = await pulumi11.automation.LocalWorkspace.createOrSelectStack(
|
|
6889
7294
|
{
|
|
6890
7295
|
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
6891
7296
|
projectName: "wraps-email",
|
|
@@ -6935,8 +7340,8 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6935
7340
|
costEstimate: costComparison,
|
|
6936
7341
|
commandName: "wraps email upgrade"
|
|
6937
7342
|
});
|
|
6938
|
-
|
|
6939
|
-
|
|
7343
|
+
clack11.outro(
|
|
7344
|
+
pc12.green("Preview complete. Run without --preview to upgrade.")
|
|
6940
7345
|
);
|
|
6941
7346
|
trackServiceUpgrade("email", {
|
|
6942
7347
|
from_preset: metadata.services.email?.preset,
|
|
@@ -6960,7 +7365,7 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6960
7365
|
"Updating Wraps infrastructure (this may take 2-3 minutes)",
|
|
6961
7366
|
async () => {
|
|
6962
7367
|
await ensurePulumiWorkDir();
|
|
6963
|
-
const stack = await
|
|
7368
|
+
const stack = await pulumi11.automation.LocalWorkspace.createOrSelectStack(
|
|
6964
7369
|
{
|
|
6965
7370
|
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
6966
7371
|
projectName: "wraps-email",
|
|
@@ -7035,8 +7440,8 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
7035
7440
|
throw new Error(`Pulumi upgrade failed: ${error.message}`);
|
|
7036
7441
|
}
|
|
7037
7442
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
7038
|
-
const { findHostedZone:
|
|
7039
|
-
const hostedZone = await
|
|
7443
|
+
const { findHostedZone: findHostedZone3, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
7444
|
+
const hostedZone = await findHostedZone3(outputs.domain, region);
|
|
7040
7445
|
if (hostedZone) {
|
|
7041
7446
|
try {
|
|
7042
7447
|
progress.start("Creating DNS records in Route53");
|
|
@@ -7101,21 +7506,21 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
7101
7506
|
httpsTrackingEnabled: outputs.httpsTrackingEnabled
|
|
7102
7507
|
});
|
|
7103
7508
|
console.log(`
|
|
7104
|
-
${
|
|
7509
|
+
${pc12.green("\u2713")} ${pc12.bold("Upgrade complete!")}
|
|
7105
7510
|
`);
|
|
7106
7511
|
if (upgradeAction === "preset" && newPreset) {
|
|
7107
7512
|
console.log(
|
|
7108
|
-
`Upgraded to ${
|
|
7513
|
+
`Upgraded to ${pc12.cyan(newPreset)} preset (${pc12.green(`${formatCost(newCostData.total.monthly)}/mo`)})
|
|
7109
7514
|
`
|
|
7110
7515
|
);
|
|
7111
7516
|
} else {
|
|
7112
7517
|
console.log(
|
|
7113
|
-
`Updated configuration (${
|
|
7518
|
+
`Updated configuration (${pc12.green(`${formatCost(newCostData.total.monthly)}/mo`)})
|
|
7114
7519
|
`
|
|
7115
7520
|
);
|
|
7116
7521
|
}
|
|
7117
7522
|
if (needsCertificateValidation) {
|
|
7118
|
-
console.log(
|
|
7523
|
+
console.log(pc12.bold("\u26A0\uFE0F HTTPS Tracking - Next Steps:\n"));
|
|
7119
7524
|
console.log(
|
|
7120
7525
|
" 1. Add the SSL certificate validation DNS record shown above to your DNS provider"
|
|
7121
7526
|
);
|
|
@@ -7123,17 +7528,17 @@ ${pc10.green("\u2713")} ${pc10.bold("Upgrade complete!")}
|
|
|
7123
7528
|
" 2. Wait for DNS propagation and certificate validation (5-30 minutes)"
|
|
7124
7529
|
);
|
|
7125
7530
|
console.log(
|
|
7126
|
-
` 3. Run ${
|
|
7531
|
+
` 3. Run ${pc12.cyan("wraps email upgrade")} again to complete CloudFront setup
|
|
7127
7532
|
`
|
|
7128
7533
|
);
|
|
7129
7534
|
console.log(
|
|
7130
|
-
|
|
7535
|
+
pc12.dim(
|
|
7131
7536
|
" Note: CloudFront distribution will be created once the certificate is validated.\n"
|
|
7132
7537
|
)
|
|
7133
7538
|
);
|
|
7134
7539
|
} else if (outputs.httpsTrackingEnabled && outputs.cloudFrontDomain) {
|
|
7135
7540
|
console.log(
|
|
7136
|
-
|
|
7541
|
+
pc12.green("\u2713") + " " + pc12.bold("HTTPS tracking is fully configured and ready to use!\n")
|
|
7137
7542
|
);
|
|
7138
7543
|
}
|
|
7139
7544
|
const enabledFeatures = [];
|
|
@@ -7158,11 +7563,11 @@ ${pc10.green("\u2713")} ${pc10.bold("Upgrade complete!")}
|
|
|
7158
7563
|
|
|
7159
7564
|
// src/commands/shared/dashboard.ts
|
|
7160
7565
|
init_esm_shims();
|
|
7161
|
-
import * as
|
|
7162
|
-
import * as
|
|
7566
|
+
import * as clack12 from "@clack/prompts";
|
|
7567
|
+
import * as pulumi12 from "@pulumi/pulumi";
|
|
7163
7568
|
import getPort from "get-port";
|
|
7164
7569
|
import open from "open";
|
|
7165
|
-
import
|
|
7570
|
+
import pc13 from "picocolors";
|
|
7166
7571
|
|
|
7167
7572
|
// src/console/server.ts
|
|
7168
7573
|
init_esm_shims();
|
|
@@ -8387,7 +8792,7 @@ async function startConsoleServer(config2) {
|
|
|
8387
8792
|
init_events();
|
|
8388
8793
|
init_aws();
|
|
8389
8794
|
async function dashboard(options) {
|
|
8390
|
-
|
|
8795
|
+
clack12.intro(pc13.bold("Wraps Dashboard"));
|
|
8391
8796
|
const progress = new DeploymentProgress();
|
|
8392
8797
|
const identity = await progress.execute(
|
|
8393
8798
|
"Validating AWS credentials",
|
|
@@ -8397,16 +8802,16 @@ async function dashboard(options) {
|
|
|
8397
8802
|
let stackOutputs = {};
|
|
8398
8803
|
try {
|
|
8399
8804
|
await ensurePulumiWorkDir();
|
|
8400
|
-
const stack = await
|
|
8805
|
+
const stack = await pulumi12.automation.LocalWorkspace.selectStack({
|
|
8401
8806
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
8402
8807
|
workDir: getPulumiWorkDir()
|
|
8403
8808
|
});
|
|
8404
8809
|
stackOutputs = await stack.outputs();
|
|
8405
8810
|
} catch (_error) {
|
|
8406
8811
|
progress.stop();
|
|
8407
|
-
|
|
8812
|
+
clack12.log.error("No Wraps infrastructure found");
|
|
8408
8813
|
console.log(
|
|
8409
|
-
`\\nRun ${
|
|
8814
|
+
`\\nRun ${pc13.cyan("wraps email init")} to deploy infrastructure first.\\n`
|
|
8410
8815
|
);
|
|
8411
8816
|
process.exit(1);
|
|
8412
8817
|
}
|
|
@@ -8415,9 +8820,9 @@ async function dashboard(options) {
|
|
|
8415
8820
|
const archivingEnabled = stackOutputs.archivingEnabled?.value ?? false;
|
|
8416
8821
|
const port = options.port || await getPort({ port: [5555, 5556, 5557, 5558, 5559] });
|
|
8417
8822
|
progress.stop();
|
|
8418
|
-
|
|
8823
|
+
clack12.log.success("Starting dashboard server...");
|
|
8419
8824
|
console.log(
|
|
8420
|
-
`${
|
|
8825
|
+
`${pc13.dim("Using current AWS credentials (no role assumption)")}\\n`
|
|
8421
8826
|
);
|
|
8422
8827
|
const { url } = await startConsoleServer({
|
|
8423
8828
|
port,
|
|
@@ -8430,8 +8835,8 @@ async function dashboard(options) {
|
|
|
8430
8835
|
archiveArn,
|
|
8431
8836
|
archivingEnabled
|
|
8432
8837
|
});
|
|
8433
|
-
console.log(`\\n${
|
|
8434
|
-
console.log(`${
|
|
8838
|
+
console.log(`\\n${pc13.bold("Dashboard:")} ${pc13.cyan(url)}`);
|
|
8839
|
+
console.log(`${pc13.dim("Press Ctrl+C to stop")}\\n`);
|
|
8435
8840
|
if (!options.noOpen) {
|
|
8436
8841
|
await open(url);
|
|
8437
8842
|
}
|
|
@@ -8446,210 +8851,147 @@ async function dashboard(options) {
|
|
|
8446
8851
|
|
|
8447
8852
|
// src/commands/shared/destroy.ts
|
|
8448
8853
|
init_esm_shims();
|
|
8449
|
-
init_events();
|
|
8450
8854
|
init_aws();
|
|
8451
|
-
import * as
|
|
8452
|
-
import
|
|
8453
|
-
import pc12 from "picocolors";
|
|
8855
|
+
import * as clack13 from "@clack/prompts";
|
|
8856
|
+
import pc14 from "picocolors";
|
|
8454
8857
|
async function destroy(options) {
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
"
|
|
8464
|
-
|
|
8465
|
-
|
|
8858
|
+
clack13.intro(pc14.bold("Wraps Infrastructure Teardown"));
|
|
8859
|
+
const spinner3 = clack13.spinner();
|
|
8860
|
+
spinner3.start("Validating AWS credentials");
|
|
8861
|
+
let identity;
|
|
8862
|
+
try {
|
|
8863
|
+
identity = await validateAWSCredentials();
|
|
8864
|
+
spinner3.stop("AWS credentials validated");
|
|
8865
|
+
} catch (error) {
|
|
8866
|
+
spinner3.stop("AWS credentials validation failed");
|
|
8867
|
+
throw error;
|
|
8868
|
+
}
|
|
8466
8869
|
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
|
-
}
|
|
8870
|
+
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
8871
|
+
const deployedServices = [];
|
|
8872
|
+
if (metadata?.services?.email) {
|
|
8873
|
+
deployedServices.push("email");
|
|
8478
8874
|
}
|
|
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
|
-
});
|
|
8875
|
+
if (deployedServices.length === 0) {
|
|
8876
|
+
clack13.log.warn("No Wraps services found in this region");
|
|
8877
|
+
console.log(
|
|
8878
|
+
`
|
|
8879
|
+
Run ${pc14.cyan("wraps email init")} to deploy infrastructure.
|
|
8880
|
+
`
|
|
8881
|
+
);
|
|
8882
|
+
process.exit(0);
|
|
8883
|
+
}
|
|
8884
|
+
if (deployedServices.length === 1) {
|
|
8885
|
+
const service = deployedServices[0];
|
|
8886
|
+
clack13.log.info(`Found ${pc14.cyan(service)} service deployed`);
|
|
8887
|
+
if (service === "email") {
|
|
8888
|
+
await emailDestroy(options);
|
|
8511
8889
|
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
8890
|
}
|
|
8521
8891
|
}
|
|
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);
|
|
8892
|
+
const serviceToDestroy = await clack13.select({
|
|
8893
|
+
message: "Which service would you like to destroy?",
|
|
8894
|
+
options: [
|
|
8895
|
+
...deployedServices.map((s) => ({
|
|
8896
|
+
value: s,
|
|
8897
|
+
label: s.charAt(0).toUpperCase() + s.slice(1),
|
|
8898
|
+
hint: s === "email" ? "AWS SES email infrastructure" : void 0
|
|
8899
|
+
})),
|
|
8900
|
+
{
|
|
8901
|
+
value: "all",
|
|
8902
|
+
label: "All services",
|
|
8903
|
+
hint: "Destroy all Wraps infrastructure"
|
|
8540
8904
|
}
|
|
8541
|
-
|
|
8542
|
-
}
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8905
|
+
]
|
|
8906
|
+
});
|
|
8907
|
+
if (clack13.isCancel(serviceToDestroy)) {
|
|
8908
|
+
clack13.cancel("Operation cancelled.");
|
|
8909
|
+
process.exit(0);
|
|
8910
|
+
}
|
|
8911
|
+
if (serviceToDestroy === "email" || serviceToDestroy === "all") {
|
|
8912
|
+
if (deployedServices.includes("email")) {
|
|
8913
|
+
await emailDestroy(options);
|
|
8548
8914
|
}
|
|
8549
|
-
trackError("DESTROY_FAILED", "destroy", { step: "destroy" });
|
|
8550
|
-
clack11.log.error("Infrastructure destruction failed");
|
|
8551
|
-
throw error;
|
|
8552
8915
|
}
|
|
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
|
-
});
|
|
8916
|
+
if (serviceToDestroy === "all") {
|
|
8917
|
+
clack13.outro(pc14.green("All Wraps infrastructure has been removed"));
|
|
8918
|
+
}
|
|
8565
8919
|
}
|
|
8566
8920
|
|
|
8567
8921
|
// src/commands/shared/status.ts
|
|
8568
8922
|
init_esm_shims();
|
|
8569
8923
|
init_events();
|
|
8570
8924
|
init_aws();
|
|
8571
|
-
import * as
|
|
8572
|
-
import * as
|
|
8573
|
-
import
|
|
8925
|
+
import * as clack14 from "@clack/prompts";
|
|
8926
|
+
import * as pulumi13 from "@pulumi/pulumi";
|
|
8927
|
+
import pc15 from "picocolors";
|
|
8574
8928
|
async function status(_options) {
|
|
8575
8929
|
const startTime = Date.now();
|
|
8576
8930
|
const progress = new DeploymentProgress();
|
|
8931
|
+
clack14.intro(pc15.bold("Wraps Infrastructure Status"));
|
|
8577
8932
|
const identity = await progress.execute(
|
|
8578
8933
|
"Loading infrastructure status",
|
|
8579
8934
|
async () => validateAWSCredentials()
|
|
8580
8935
|
);
|
|
8936
|
+
progress.info(`AWS Account: ${pc15.cyan(identity.accountId)}`);
|
|
8581
8937
|
const region = await getAWSRegion();
|
|
8582
|
-
|
|
8938
|
+
progress.info(`Region: ${pc15.cyan(region)}`);
|
|
8939
|
+
const services = [];
|
|
8583
8940
|
try {
|
|
8584
8941
|
await ensurePulumiWorkDir();
|
|
8585
|
-
const stack = await
|
|
8942
|
+
const stack = await pulumi13.automation.LocalWorkspace.selectStack({
|
|
8586
8943
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
8587
8944
|
workDir: getPulumiWorkDir()
|
|
8588
8945
|
});
|
|
8589
|
-
|
|
8946
|
+
const outputs = await stack.outputs();
|
|
8947
|
+
if (outputs.roleArn?.value) {
|
|
8948
|
+
const domainCount = outputs.domains?.value?.length || 0;
|
|
8949
|
+
services.push({
|
|
8950
|
+
name: "Email",
|
|
8951
|
+
status: "deployed",
|
|
8952
|
+
details: domainCount > 0 ? `${domainCount} domain(s)` : void 0
|
|
8953
|
+
});
|
|
8954
|
+
} else {
|
|
8955
|
+
services.push({ name: "Email", status: "not_deployed" });
|
|
8956
|
+
}
|
|
8590
8957
|
} catch (_error) {
|
|
8591
|
-
|
|
8592
|
-
|
|
8958
|
+
services.push({ name: "Email", status: "not_deployed" });
|
|
8959
|
+
}
|
|
8960
|
+
progress.stop();
|
|
8961
|
+
console.log();
|
|
8962
|
+
clack14.note(
|
|
8963
|
+
services.map((s) => {
|
|
8964
|
+
if (s.status === "deployed") {
|
|
8965
|
+
const details = s.details ? pc15.dim(` (${s.details})`) : "";
|
|
8966
|
+
return ` ${pc15.green("\u2713")} ${s.name}${details}`;
|
|
8967
|
+
}
|
|
8968
|
+
return ` ${pc15.dim("\u25CB")} ${s.name} ${pc15.dim("(not deployed)")}`;
|
|
8969
|
+
}).join("\n"),
|
|
8970
|
+
"Services"
|
|
8971
|
+
);
|
|
8972
|
+
const hasDeployedServices = services.some((s) => s.status === "deployed");
|
|
8973
|
+
if (hasDeployedServices) {
|
|
8974
|
+
console.log(`
|
|
8975
|
+
${pc15.bold("Details:")}`);
|
|
8976
|
+
if (services.find((s) => s.name === "Email")?.status === "deployed") {
|
|
8977
|
+
console.log(
|
|
8978
|
+
` ${pc15.dim("Email:")} ${pc15.cyan("wraps email status")}`
|
|
8979
|
+
);
|
|
8980
|
+
}
|
|
8981
|
+
} else {
|
|
8982
|
+
console.log(`
|
|
8983
|
+
${pc15.bold("Get started:")}`);
|
|
8593
8984
|
console.log(
|
|
8594
|
-
`
|
|
8595
|
-
Run ${pc13.cyan("wraps email init")} to deploy infrastructure.
|
|
8596
|
-
`
|
|
8985
|
+
` ${pc15.dim("Deploy email:")} ${pc15.cyan("wraps email init")}`
|
|
8597
8986
|
);
|
|
8598
|
-
process.exit(1);
|
|
8599
8987
|
}
|
|
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
|
-
});
|
|
8988
|
+
console.log(`
|
|
8989
|
+
${pc15.bold("Dashboard:")} ${pc15.blue("https://app.wraps.dev")}`);
|
|
8990
|
+
console.log(`${pc15.bold("Docs:")} ${pc15.blue("https://wraps.dev/docs")}
|
|
8991
|
+
`);
|
|
8649
8992
|
trackCommand("status", {
|
|
8650
8993
|
success: true,
|
|
8651
|
-
|
|
8652
|
-
integration_level: integrationLevel,
|
|
8994
|
+
services_deployed: services.filter((s) => s.status === "deployed").length,
|
|
8653
8995
|
duration_ms: Date.now() - startTime
|
|
8654
8996
|
});
|
|
8655
8997
|
}
|
|
@@ -8657,56 +8999,56 @@ Run ${pc13.cyan("wraps email init")} to deploy infrastructure.
|
|
|
8657
8999
|
// src/commands/telemetry.ts
|
|
8658
9000
|
init_esm_shims();
|
|
8659
9001
|
init_client();
|
|
8660
|
-
import * as
|
|
8661
|
-
import
|
|
9002
|
+
import * as clack15 from "@clack/prompts";
|
|
9003
|
+
import pc16 from "picocolors";
|
|
8662
9004
|
async function telemetryEnable() {
|
|
8663
9005
|
const client = getTelemetryClient();
|
|
8664
9006
|
client.enable();
|
|
8665
|
-
|
|
8666
|
-
console.log(` Config: ${
|
|
9007
|
+
clack15.log.success(pc16.green("Telemetry enabled"));
|
|
9008
|
+
console.log(` Config: ${pc16.dim(client.getConfigPath())}`);
|
|
8667
9009
|
console.log(`
|
|
8668
|
-
${
|
|
9010
|
+
${pc16.dim("Thank you for helping improve Wraps!")}
|
|
8669
9011
|
`);
|
|
8670
9012
|
}
|
|
8671
9013
|
async function telemetryDisable() {
|
|
8672
9014
|
const client = getTelemetryClient();
|
|
8673
9015
|
client.disable();
|
|
8674
|
-
|
|
8675
|
-
console.log(` Config: ${
|
|
9016
|
+
clack15.log.success(pc16.green("Telemetry disabled"));
|
|
9017
|
+
console.log(` Config: ${pc16.dim(client.getConfigPath())}`);
|
|
8676
9018
|
console.log(
|
|
8677
9019
|
`
|
|
8678
|
-
${
|
|
9020
|
+
${pc16.dim("You can re-enable with:")} wraps telemetry enable
|
|
8679
9021
|
`
|
|
8680
9022
|
);
|
|
8681
9023
|
}
|
|
8682
9024
|
async function telemetryStatus() {
|
|
8683
9025
|
const client = getTelemetryClient();
|
|
8684
|
-
|
|
8685
|
-
const status2 = client.isEnabled() ?
|
|
9026
|
+
clack15.intro(pc16.bold("Telemetry Status"));
|
|
9027
|
+
const status2 = client.isEnabled() ? pc16.green("Enabled") : pc16.red("Disabled");
|
|
8686
9028
|
console.log();
|
|
8687
|
-
console.log(` ${
|
|
8688
|
-
console.log(` ${
|
|
9029
|
+
console.log(` ${pc16.bold("Status:")} ${status2}`);
|
|
9030
|
+
console.log(` ${pc16.bold("Config file:")} ${pc16.dim(client.getConfigPath())}`);
|
|
8689
9031
|
if (client.isEnabled()) {
|
|
8690
9032
|
console.log();
|
|
8691
|
-
console.log(
|
|
8692
|
-
console.log(` ${
|
|
9033
|
+
console.log(pc16.bold(" How to opt-out:"));
|
|
9034
|
+
console.log(` ${pc16.cyan("wraps telemetry disable")}`);
|
|
8693
9035
|
console.log(
|
|
8694
|
-
` ${
|
|
9036
|
+
` ${pc16.dim("Or set:")} ${pc16.cyan("WRAPS_TELEMETRY_DISABLED=1")}`
|
|
8695
9037
|
);
|
|
8696
|
-
console.log(` ${
|
|
9038
|
+
console.log(` ${pc16.dim("Or set:")} ${pc16.cyan("DO_NOT_TRACK=1")}`);
|
|
8697
9039
|
} else {
|
|
8698
9040
|
console.log();
|
|
8699
|
-
console.log(
|
|
8700
|
-
console.log(` ${
|
|
9041
|
+
console.log(pc16.bold(" How to opt-in:"));
|
|
9042
|
+
console.log(` ${pc16.cyan("wraps telemetry enable")}`);
|
|
8701
9043
|
}
|
|
8702
9044
|
console.log();
|
|
8703
|
-
console.log(
|
|
9045
|
+
console.log(pc16.bold(" Debug mode:"));
|
|
8704
9046
|
console.log(
|
|
8705
|
-
` ${
|
|
9047
|
+
` ${pc16.dim("See what would be sent:")} ${pc16.cyan("WRAPS_TELEMETRY_DEBUG=1 wraps <command>")}`
|
|
8706
9048
|
);
|
|
8707
9049
|
console.log();
|
|
8708
9050
|
console.log(
|
|
8709
|
-
` ${
|
|
9051
|
+
` ${pc16.dim("Learn more:")} ${pc16.cyan("https://wraps.dev/docs/telemetry")}`
|
|
8710
9052
|
);
|
|
8711
9053
|
console.log();
|
|
8712
9054
|
}
|
|
@@ -8724,19 +9066,41 @@ function printCompletionScript() {
|
|
|
8724
9066
|
console.log("# ========================\n");
|
|
8725
9067
|
console.log("# Tab completion will be available in a future release.\n");
|
|
8726
9068
|
console.log("# For now, here are the available commands:\n");
|
|
8727
|
-
console.log("# Commands:");
|
|
9069
|
+
console.log("# Email Commands:");
|
|
8728
9070
|
console.log(
|
|
8729
9071
|
"# wraps email init [--provider vercel|aws|railway|other] [--region <region>] [--domain <domain>]"
|
|
8730
9072
|
);
|
|
8731
|
-
console.log("# wraps
|
|
8732
|
-
console.log("# wraps
|
|
9073
|
+
console.log("# wraps email connect [--region <region>]");
|
|
9074
|
+
console.log("# wraps email status [--account <account-id>]");
|
|
9075
|
+
console.log("# wraps email verify --domain <domain>");
|
|
9076
|
+
console.log("# wraps email sync");
|
|
9077
|
+
console.log("# wraps email upgrade");
|
|
9078
|
+
console.log("# wraps email restore [--region <region>] [--force]");
|
|
9079
|
+
console.log("# wraps email destroy [--force] [--preview]");
|
|
9080
|
+
console.log("# wraps email domains add --domain <domain>");
|
|
9081
|
+
console.log("# wraps email domains list");
|
|
9082
|
+
console.log("# wraps email domains verify --domain <domain>");
|
|
9083
|
+
console.log("# wraps email domains get-dkim --domain <domain>");
|
|
9084
|
+
console.log("# wraps email domains remove --domain <domain> [--force]\n");
|
|
9085
|
+
console.log("# Global Commands:");
|
|
9086
|
+
console.log("# wraps status");
|
|
9087
|
+
console.log("# wraps destroy [--force] [--preview]");
|
|
9088
|
+
console.log("# wraps console [--port <port>] [--no-open]");
|
|
9089
|
+
console.log("# wraps completion");
|
|
9090
|
+
console.log("# wraps telemetry [enable|disable|status]\n");
|
|
9091
|
+
console.log("# Dashboard Commands:");
|
|
9092
|
+
console.log("# wraps dashboard update-role [--region <region>] [--force]\n");
|
|
8733
9093
|
console.log("# Flags:");
|
|
8734
|
-
console.log("# --provider : vercel, aws, railway, other");
|
|
9094
|
+
console.log("# -p, --provider : vercel, aws, railway, other");
|
|
8735
9095
|
console.log(
|
|
8736
|
-
"# --region : us-east-1, us-east-2, us-west-1, us-west-2, eu-west-1, eu-west-2, etc."
|
|
9096
|
+
"# -r, --region : us-east-1, us-east-2, us-west-1, us-west-2, eu-west-1, eu-west-2, etc."
|
|
8737
9097
|
);
|
|
8738
|
-
console.log("# --domain : Your domain name (e.g., myapp.com)");
|
|
8739
|
-
console.log("# --account
|
|
9098
|
+
console.log("# -d, --domain : Your domain name (e.g., myapp.com)");
|
|
9099
|
+
console.log("# --account : AWS account ID or alias");
|
|
9100
|
+
console.log("# --preset : starter, production, enterprise, custom");
|
|
9101
|
+
console.log("# -y, --yes : Skip confirmation prompts");
|
|
9102
|
+
console.log("# -f, --force : Force destructive operations");
|
|
9103
|
+
console.log("# --preview : Preview changes without deploying\n");
|
|
8740
9104
|
}
|
|
8741
9105
|
|
|
8742
9106
|
// src/cli.ts
|
|
@@ -8753,62 +9117,66 @@ function showVersion() {
|
|
|
8753
9117
|
process.exit(0);
|
|
8754
9118
|
}
|
|
8755
9119
|
function showHelp() {
|
|
8756
|
-
|
|
9120
|
+
clack16.intro(pc17.bold(`WRAPS CLI v${VERSION}`));
|
|
8757
9121
|
console.log("Deploy AWS infrastructure to your account\n");
|
|
8758
9122
|
console.log("Usage: wraps [service] <command> [options]\n");
|
|
8759
9123
|
console.log("Services:");
|
|
8760
|
-
console.log(` ${
|
|
9124
|
+
console.log(` ${pc17.cyan("email")} Email infrastructure (AWS SES)
|
|
9125
|
+
`);
|
|
9126
|
+
console.log("Email Commands:");
|
|
8761
9127
|
console.log(
|
|
8762
|
-
` ${
|
|
8763
|
-
`
|
|
9128
|
+
` ${pc17.cyan("email init")} Deploy new email infrastructure`
|
|
8764
9129
|
);
|
|
8765
|
-
console.log("Email Commands:");
|
|
8766
9130
|
console.log(
|
|
8767
|
-
` ${
|
|
9131
|
+
` ${pc17.cyan("email connect")} Connect to existing AWS SES`
|
|
8768
9132
|
);
|
|
9133
|
+
console.log(` ${pc17.cyan("email status")} Show email infrastructure details`);
|
|
9134
|
+
console.log(` ${pc17.cyan("email verify")} Verify domain DNS records`);
|
|
8769
9135
|
console.log(
|
|
8770
|
-
` ${
|
|
9136
|
+
` ${pc17.cyan("email sync")} Apply CLI updates to infrastructure`
|
|
8771
9137
|
);
|
|
8772
|
-
console.log(` ${
|
|
9138
|
+
console.log(` ${pc17.cyan("email upgrade")} Add features`);
|
|
8773
9139
|
console.log(
|
|
8774
|
-
` ${
|
|
9140
|
+
` ${pc17.cyan("email restore")} Restore original configuration`
|
|
8775
9141
|
);
|
|
8776
|
-
console.log(` ${pc15.cyan("email upgrade")} Add features`);
|
|
8777
9142
|
console.log(
|
|
8778
|
-
` ${
|
|
8779
|
-
`
|
|
9143
|
+
` ${pc17.cyan("email destroy")} Remove email infrastructure`
|
|
8780
9144
|
);
|
|
9145
|
+
console.log(` ${pc17.cyan("email domains add")} Add a domain to SES`);
|
|
9146
|
+
console.log(` ${pc17.cyan("email domains list")} List all domains`);
|
|
9147
|
+
console.log(` ${pc17.cyan("email domains remove")} Remove a domain
|
|
9148
|
+
`);
|
|
8781
9149
|
console.log("Console & Dashboard:");
|
|
8782
|
-
console.log(` ${
|
|
9150
|
+
console.log(` ${pc17.cyan("console")} Start local web console`);
|
|
8783
9151
|
console.log(
|
|
8784
|
-
` ${
|
|
9152
|
+
` ${pc17.cyan("dashboard update-role")} Update hosted dashboard IAM permissions
|
|
8785
9153
|
`
|
|
8786
9154
|
);
|
|
8787
9155
|
console.log("Global Commands:");
|
|
8788
|
-
console.log(` ${
|
|
8789
|
-
console.log(` ${
|
|
8790
|
-
console.log(` ${
|
|
9156
|
+
console.log(` ${pc17.cyan("status")} Show overview of all services`);
|
|
9157
|
+
console.log(` ${pc17.cyan("destroy")} Remove deployed infrastructure`);
|
|
9158
|
+
console.log(` ${pc17.cyan("completion")} Generate shell completion script`);
|
|
8791
9159
|
console.log(
|
|
8792
|
-
` ${
|
|
9160
|
+
` ${pc17.cyan("telemetry")} Manage anonymous telemetry settings
|
|
8793
9161
|
`
|
|
8794
9162
|
);
|
|
8795
9163
|
console.log("Options:");
|
|
8796
9164
|
console.log(
|
|
8797
|
-
` ${
|
|
9165
|
+
` ${pc17.dim("-p, --provider")} Hosting provider (vercel, aws, railway, other)`
|
|
8798
9166
|
);
|
|
8799
|
-
console.log(` ${
|
|
8800
|
-
console.log(` ${
|
|
8801
|
-
console.log(` ${
|
|
8802
|
-
console.log(` ${
|
|
8803
|
-
console.log(` ${
|
|
8804
|
-
console.log(` ${
|
|
9167
|
+
console.log(` ${pc17.dim("-r, --region")} AWS region`);
|
|
9168
|
+
console.log(` ${pc17.dim("-d, --domain")} Domain name`);
|
|
9169
|
+
console.log(` ${pc17.dim("--account")} AWS account ID or alias`);
|
|
9170
|
+
console.log(` ${pc17.dim("--preset")} Configuration preset`);
|
|
9171
|
+
console.log(` ${pc17.dim("-y, --yes")} Skip confirmation prompts`);
|
|
9172
|
+
console.log(` ${pc17.dim("-f, --force")} Force destructive operations`);
|
|
8805
9173
|
console.log(
|
|
8806
|
-
` ${
|
|
9174
|
+
` ${pc17.dim("--preview")} Preview changes without deploying`
|
|
8807
9175
|
);
|
|
8808
|
-
console.log(` ${
|
|
9176
|
+
console.log(` ${pc17.dim("-v, --version")} Show version number
|
|
8809
9177
|
`);
|
|
8810
9178
|
console.log(
|
|
8811
|
-
`Run ${
|
|
9179
|
+
`Run ${pc17.cyan("wraps <service> <command> --help")} for more information.
|
|
8812
9180
|
`
|
|
8813
9181
|
);
|
|
8814
9182
|
process.exit(0);
|
|
@@ -8875,37 +9243,9 @@ var flags = args.parse(process.argv);
|
|
|
8875
9243
|
var [primaryCommand, subCommand] = args.sub;
|
|
8876
9244
|
if (!primaryCommand) {
|
|
8877
9245
|
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({
|
|
9246
|
+
clack16.intro(pc17.bold(`WRAPS CLI v${VERSION}`));
|
|
9247
|
+
console.log("Welcome! Let's get started deploying your email infrastructure.\n");
|
|
9248
|
+
const action = await clack16.select({
|
|
8909
9249
|
message: "What would you like to do?",
|
|
8910
9250
|
options: [
|
|
8911
9251
|
{
|
|
@@ -8920,8 +9260,8 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
|
|
|
8920
9260
|
}
|
|
8921
9261
|
]
|
|
8922
9262
|
});
|
|
8923
|
-
if (
|
|
8924
|
-
|
|
9263
|
+
if (clack16.isCancel(action)) {
|
|
9264
|
+
clack16.cancel("Operation cancelled.");
|
|
8925
9265
|
process.exit(0);
|
|
8926
9266
|
}
|
|
8927
9267
|
if (action === "init") {
|
|
@@ -8950,20 +9290,20 @@ async function run() {
|
|
|
8950
9290
|
const telemetry = getTelemetryClient();
|
|
8951
9291
|
if (telemetry.shouldShowNotification()) {
|
|
8952
9292
|
console.log();
|
|
8953
|
-
|
|
9293
|
+
clack16.log.info(pc17.bold("Anonymous Telemetry"));
|
|
8954
9294
|
console.log(
|
|
8955
|
-
` Wraps collects ${
|
|
9295
|
+
` Wraps collects ${pc17.cyan("anonymous usage data")} to improve the CLI.`
|
|
8956
9296
|
);
|
|
8957
9297
|
console.log(
|
|
8958
|
-
` We ${
|
|
9298
|
+
` We ${pc17.bold("never")} collect: domains, AWS credentials, email content, or PII.`
|
|
8959
9299
|
);
|
|
8960
9300
|
console.log(
|
|
8961
|
-
` We ${
|
|
9301
|
+
` We ${pc17.bold("only")} collect: command names, success/failure, CLI version, OS.`
|
|
8962
9302
|
);
|
|
8963
9303
|
console.log();
|
|
8964
|
-
console.log(` Opt-out anytime: ${
|
|
8965
|
-
console.log(` Or set: ${
|
|
8966
|
-
console.log(` Learn more: ${
|
|
9304
|
+
console.log(` Opt-out anytime: ${pc17.cyan("wraps telemetry disable")}`);
|
|
9305
|
+
console.log(` Or set: ${pc17.cyan("WRAPS_TELEMETRY_DISABLED=1")}`);
|
|
9306
|
+
console.log(` Learn more: ${pc17.cyan("https://wraps.dev/docs/telemetry")}`);
|
|
8967
9307
|
console.log();
|
|
8968
9308
|
telemetry.markNotificationShown();
|
|
8969
9309
|
}
|
|
@@ -9010,15 +9350,33 @@ async function run() {
|
|
|
9010
9350
|
preview: flags.preview
|
|
9011
9351
|
});
|
|
9012
9352
|
break;
|
|
9353
|
+
case "status":
|
|
9354
|
+
await emailStatus({
|
|
9355
|
+
account: flags.account
|
|
9356
|
+
});
|
|
9357
|
+
break;
|
|
9358
|
+
case "verify": {
|
|
9359
|
+
if (!flags.domain) {
|
|
9360
|
+
clack16.log.error("--domain flag is required");
|
|
9361
|
+
console.log(
|
|
9362
|
+
`
|
|
9363
|
+
Usage: ${pc17.cyan("wraps email verify --domain yourapp.com")}
|
|
9364
|
+
`
|
|
9365
|
+
);
|
|
9366
|
+
process.exit(1);
|
|
9367
|
+
}
|
|
9368
|
+
await verifyDomain({ domain: flags.domain });
|
|
9369
|
+
break;
|
|
9370
|
+
}
|
|
9013
9371
|
case "domains": {
|
|
9014
9372
|
const domainsSubCommand = args.sub[2];
|
|
9015
9373
|
switch (domainsSubCommand) {
|
|
9016
9374
|
case "add": {
|
|
9017
9375
|
if (!flags.domain) {
|
|
9018
|
-
|
|
9376
|
+
clack16.log.error("--domain flag is required");
|
|
9019
9377
|
console.log(
|
|
9020
9378
|
`
|
|
9021
|
-
Usage: ${
|
|
9379
|
+
Usage: ${pc17.cyan("wraps email domains add --domain yourapp.com")}
|
|
9022
9380
|
`
|
|
9023
9381
|
);
|
|
9024
9382
|
process.exit(1);
|
|
@@ -9031,10 +9389,10 @@ Usage: ${pc15.cyan("wraps email domains add --domain yourapp.com")}
|
|
|
9031
9389
|
break;
|
|
9032
9390
|
case "verify": {
|
|
9033
9391
|
if (!flags.domain) {
|
|
9034
|
-
|
|
9392
|
+
clack16.log.error("--domain flag is required");
|
|
9035
9393
|
console.log(
|
|
9036
9394
|
`
|
|
9037
|
-
Usage: ${
|
|
9395
|
+
Usage: ${pc17.cyan("wraps email domains verify --domain yourapp.com")}
|
|
9038
9396
|
`
|
|
9039
9397
|
);
|
|
9040
9398
|
process.exit(1);
|
|
@@ -9044,10 +9402,10 @@ Usage: ${pc15.cyan("wraps email domains verify --domain yourapp.com")}
|
|
|
9044
9402
|
}
|
|
9045
9403
|
case "get-dkim": {
|
|
9046
9404
|
if (!flags.domain) {
|
|
9047
|
-
|
|
9405
|
+
clack16.log.error("--domain flag is required");
|
|
9048
9406
|
console.log(
|
|
9049
9407
|
`
|
|
9050
|
-
Usage: ${
|
|
9408
|
+
Usage: ${pc17.cyan("wraps email domains get-dkim --domain yourapp.com")}
|
|
9051
9409
|
`
|
|
9052
9410
|
);
|
|
9053
9411
|
process.exit(1);
|
|
@@ -9057,10 +9415,10 @@ Usage: ${pc15.cyan("wraps email domains get-dkim --domain yourapp.com")}
|
|
|
9057
9415
|
}
|
|
9058
9416
|
case "remove": {
|
|
9059
9417
|
if (!flags.domain) {
|
|
9060
|
-
|
|
9418
|
+
clack16.log.error("--domain flag is required");
|
|
9061
9419
|
console.log(
|
|
9062
9420
|
`
|
|
9063
|
-
Usage: ${
|
|
9421
|
+
Usage: ${pc17.cyan("wraps email domains remove --domain yourapp.com --force")}
|
|
9064
9422
|
`
|
|
9065
9423
|
);
|
|
9066
9424
|
process.exit(1);
|
|
@@ -9072,23 +9430,29 @@ Usage: ${pc15.cyan("wraps email domains remove --domain yourapp.com --force")}
|
|
|
9072
9430
|
break;
|
|
9073
9431
|
}
|
|
9074
9432
|
default:
|
|
9075
|
-
|
|
9433
|
+
clack16.log.error(
|
|
9076
9434
|
`Unknown domains command: ${domainsSubCommand || "(none)"}`
|
|
9077
9435
|
);
|
|
9078
9436
|
console.log(
|
|
9079
9437
|
`
|
|
9080
|
-
Available commands: ${
|
|
9438
|
+
Available commands: ${pc17.cyan("add")}, ${pc17.cyan("list")}, ${pc17.cyan("verify")}, ${pc17.cyan("get-dkim")}, ${pc17.cyan("remove")}
|
|
9081
9439
|
`
|
|
9082
9440
|
);
|
|
9083
9441
|
process.exit(1);
|
|
9084
9442
|
}
|
|
9085
9443
|
break;
|
|
9086
9444
|
}
|
|
9445
|
+
case "destroy":
|
|
9446
|
+
await emailDestroy({
|
|
9447
|
+
force: flags.force,
|
|
9448
|
+
preview: flags.preview
|
|
9449
|
+
});
|
|
9450
|
+
break;
|
|
9087
9451
|
default:
|
|
9088
|
-
|
|
9452
|
+
clack16.log.error(`Unknown email command: ${subCommand}`);
|
|
9089
9453
|
console.log(
|
|
9090
9454
|
`
|
|
9091
|
-
Run ${
|
|
9455
|
+
Run ${pc17.cyan("wraps --help")} for available commands.
|
|
9092
9456
|
`
|
|
9093
9457
|
);
|
|
9094
9458
|
process.exit(1);
|
|
@@ -9111,11 +9475,11 @@ Run ${pc15.cyan("wraps --help")} for available commands.
|
|
|
9111
9475
|
});
|
|
9112
9476
|
break;
|
|
9113
9477
|
default:
|
|
9114
|
-
|
|
9478
|
+
clack16.log.error(`Unknown dashboard command: ${subCommand}`);
|
|
9115
9479
|
console.log(`
|
|
9116
|
-
Available commands: ${
|
|
9480
|
+
Available commands: ${pc17.cyan("update-role")}
|
|
9117
9481
|
`);
|
|
9118
|
-
console.log(`Run ${
|
|
9482
|
+
console.log(`Run ${pc17.cyan("wraps --help")} for more information.
|
|
9119
9483
|
`);
|
|
9120
9484
|
process.exit(1);
|
|
9121
9485
|
}
|
|
@@ -9127,15 +9491,6 @@ Available commands: ${pc15.cyan("update-role")}
|
|
|
9127
9491
|
});
|
|
9128
9492
|
return;
|
|
9129
9493
|
}
|
|
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
9494
|
switch (primaryCommand) {
|
|
9140
9495
|
// Global commands (work across all services)
|
|
9141
9496
|
case "status":
|
|
@@ -9151,8 +9506,8 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
|
|
|
9151
9506
|
break;
|
|
9152
9507
|
case "dashboard":
|
|
9153
9508
|
if (!subCommand) {
|
|
9154
|
-
|
|
9155
|
-
`'wraps dashboard' is deprecated. Use ${
|
|
9509
|
+
clack16.log.warn(
|
|
9510
|
+
`'wraps dashboard' is deprecated. Use ${pc17.cyan("wraps console")} instead.`
|
|
9156
9511
|
);
|
|
9157
9512
|
await dashboard({
|
|
9158
9513
|
port: flags.port,
|
|
@@ -9182,10 +9537,10 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
|
|
|
9182
9537
|
await telemetryStatus();
|
|
9183
9538
|
break;
|
|
9184
9539
|
default:
|
|
9185
|
-
|
|
9540
|
+
clack16.log.error(`Unknown telemetry command: ${subCommand}`);
|
|
9186
9541
|
console.log(
|
|
9187
9542
|
`
|
|
9188
|
-
Available commands: ${
|
|
9543
|
+
Available commands: ${pc17.cyan("enable")}, ${pc17.cyan("disable")}, ${pc17.cyan("status")}
|
|
9189
9544
|
`
|
|
9190
9545
|
);
|
|
9191
9546
|
process.exit(1);
|
|
@@ -9194,7 +9549,6 @@ Available commands: ${pc15.cyan("enable")}, ${pc15.cyan("disable")}, ${pc15.cyan
|
|
|
9194
9549
|
}
|
|
9195
9550
|
// Show help for service without subcommand
|
|
9196
9551
|
case "email":
|
|
9197
|
-
case "sms":
|
|
9198
9552
|
console.log(
|
|
9199
9553
|
`
|
|
9200
9554
|
Please specify a command for ${primaryCommand} service.
|
|
@@ -9203,10 +9557,10 @@ Please specify a command for ${primaryCommand} service.
|
|
|
9203
9557
|
showHelp();
|
|
9204
9558
|
break;
|
|
9205
9559
|
default:
|
|
9206
|
-
|
|
9560
|
+
clack16.log.error(`Unknown command: ${primaryCommand}`);
|
|
9207
9561
|
console.log(
|
|
9208
9562
|
`
|
|
9209
|
-
Run ${
|
|
9563
|
+
Run ${pc17.cyan("wraps --help")} for available commands.
|
|
9210
9564
|
`
|
|
9211
9565
|
);
|
|
9212
9566
|
process.exit(1);
|