@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 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.1",
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 metrics dashboard",
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: "Enable real-time event tracking (EventBridge)?",
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
- dynamoDBHistory = await clack4.confirm({
2227
- message: "Store email history in DynamoDB?",
2228
- initialValue: existingConfig?.eventTracking?.dynamoDBHistory ?? true
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(dynamoDBHistory)) {
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 reputationMetrics = await clack4.confirm({
2269
- message: "Enable reputation metrics dashboard?",
2270
- initialValue: existingConfig?.reputationMetrics ?? true
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(reputationMetrics)) {
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: Boolean(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 clack14 from "@clack/prompts";
2721
+ import * as clack16 from "@clack/prompts";
2698
2722
  import args from "args";
2699
- import pc15 from "picocolors";
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@${domain}"`
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@${domain.domain}"`
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
- mailFromDomain = config2.mailFromDomain || `mail.${config2.domain}`;
4170
- new aws5.sesv2.EmailIdentityMailFromAttributes(
4171
- "wraps-email-mail-from",
4172
- {
4173
- emailIdentity: config2.domain,
4174
- mailFromDomain,
4175
- behaviorOnMxFailure: "USE_DEFAULT_VALUE"
4176
- // Fallback to amazonses.com if MX record fails
4177
- },
4178
- {
4179
- dependsOn: [domainIdentity]
4180
- // Ensure domain identity exists first
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: findHostedZone2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
4319
- const hostedZone = await findHostedZone2(
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: emailConfig.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: findHostedZone2, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
5181
- const hostedZone = await findHostedZone2(outputs.domain, region);
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 clack6 from "@clack/prompts";
5273
- import pc7 from "picocolors";
5586
+ import * as clack7 from "@clack/prompts";
5587
+ import pc8 from "picocolors";
5274
5588
  async function verifyDomain(options) {
5275
- clack6.intro(pc7.bold(`Verifying ${options.domain}`));
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
- clack6.log.error(`Domain ${options.domain} not found in SES`);
5610
+ clack7.log.error(`Domain ${options.domain} not found in SES`);
5297
5611
  console.log(
5298
5612
  `
5299
- Run ${pc7.cyan(`wraps email init --domain ${options.domain}`)} to add this domain.
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
- `${pc7.bold("Domain:")} ${options.domain}`,
5405
- `${pc7.bold("Verification Status:")} ${verificationStatus === "verified" ? pc7.green("\u2713 Verified") : pc7.yellow("\u23F1 Pending")}`,
5406
- `${pc7.bold("DKIM Status:")} ${dkimStatus === "SUCCESS" ? pc7.green("\u2713 Success") : pc7.yellow(`\u23F1 ${dkimStatus}`)}`
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
- `${pc7.bold("MAIL FROM Domain:")} ${mailFromDomain}`,
5411
- `${pc7.bold("MAIL FROM Status:")} ${mailFromStatus === "SUCCESS" ? pc7.green("\u2713 Success") : mailFromStatus === "NOT_CONFIGURED" ? pc7.yellow("\u23F1 Not Configured") : pc7.yellow(`\u23F1 ${mailFromStatus}`)}`
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
- clack6.note(statusLines.join("\n"), "SES Status");
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 = pc7.green;
5734
+ statusColor = pc8.green;
5421
5735
  } else if (record.status === "incorrect") {
5422
5736
  statusIcon = "\u2717";
5423
- statusColor = pc7.red;
5737
+ statusColor = pc8.red;
5424
5738
  } else {
5425
5739
  statusIcon = "\u2717";
5426
- statusColor = pc7.red;
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
- clack6.note(dnsLines.join("\n"), "DNS Records");
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
- clack6.outro(
5438
- pc7.green("\u2713 Domain is fully verified and ready to send emails!")
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
- clack6.outro(
5443
- pc7.red("\u2717 Some DNS records are incorrect. Please update them.")
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 ${pc7.cyan("wraps email status")} to see the correct DNS records.
5761
+ Run ${pc8.cyan("wraps email status")} to see the correct DNS records.
5448
5762
  `
5449
5763
  );
5450
5764
  } else {
5451
- clack6.outro(
5452
- pc7.yellow("\u23F1 Waiting for DNS propagation and SES verification")
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
- clack6.intro(pc7.bold(`Adding domain ${options.domain} to SES`));
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
- clack6.log.warn(`Domain ${options.domain} already exists in SES`);
5790
+ clack7.log.warn(`Domain ${options.domain} already exists in SES`);
5477
5791
  console.log(
5478
5792
  `
5479
- Run ${pc7.cyan(`wraps email domains verify --domain ${options.domain}`)} to check verification status.
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
- clack6.outro(pc7.green(`\u2713 Domain ${options.domain} added successfully!`));
5818
+ clack7.outro(pc8.green(`\u2713 Domain ${options.domain} added successfully!`));
5505
5819
  console.log(`
5506
- ${pc7.bold("Next steps:")}
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(` ${pc7.cyan(`${token}._domainkey.${options.domain}`)}`);
5824
+ console.log(` ${pc8.cyan(`${token}._domainkey.${options.domain}`)}`);
5511
5825
  console.log(
5512
- ` ${pc7.dim("Type:")} CNAME ${pc7.dim("Value:")} ${token}.dkim.amazonses.com
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: ${pc7.cyan(`wraps email domains verify --domain ${options.domain}`)}`
5831
+ `2. Verify DNS propagation: ${pc8.cyan(`wraps email domains verify --domain ${options.domain}`)}`
5518
5832
  );
5519
- console.log(`3. Check status: ${pc7.cyan("wraps email 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
- clack6.intro(pc7.bold("SES Email Domains"));
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
- clack6.outro("No domains found in SES");
5868
+ clack7.outro("No domains found in SES");
5555
5869
  console.log(
5556
5870
  `
5557
- Run ${pc7.cyan("wraps email domains add <domain>")} to add a domain.
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 ? pc7.green("\u2713") : pc7.yellow("\u23F1");
5586
- const dkimIcon = domain.dkimStatus === "SUCCESS" ? pc7.green("\u2713") : pc7.yellow("\u23F1");
5587
- return ` ${statusIcon} ${pc7.bold(domain.name)} DKIM: ${dkimIcon} ${domain.dkimStatus}`;
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
- clack6.note(
5903
+ clack7.note(
5590
5904
  domainLines.join("\n"),
5591
5905
  `${domains.length} domain(s) in ${region}`
5592
5906
  );
5593
- clack6.outro(
5594
- pc7.dim(
5595
- `Run ${pc7.cyan("wraps email domains verify --domain <domain>")} for details`
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
- clack6.intro(pc7.bold(`DKIM Tokens for ${options.domain}`));
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
- clack6.outro(pc7.yellow("No DKIM tokens found for this domain"));
5941
+ clack7.outro(pc8.yellow("No DKIM tokens found for this domain"));
5628
5942
  return;
5629
5943
  }
5630
- const statusLine = `${pc7.bold("DKIM Status:")} ${dkimStatus === "SUCCESS" ? pc7.green("\u2713 Verified") : pc7.yellow(`\u23F1 ${dkimStatus}`)}`;
5631
- clack6.note(statusLine, "Status");
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
- ${pc7.bold("DNS Records to add:")}
5947
+ ${pc8.bold("DNS Records to add:")}
5634
5948
  `);
5635
5949
  for (const token of dkimTokens) {
5636
- console.log(`${pc7.cyan(`${token}._domainkey.${options.domain}`)}`);
5637
- console.log(` ${pc7.dim("Type:")} CNAME`);
5638
- console.log(` ${pc7.dim("Value:")} ${token}.dkim.amazonses.com
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
- `${pc7.dim("After adding these records, run:")} ${pc7.cyan(`wraps email domains verify --domain ${options.domain}`)}
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
- clack6.log.error(`Domain ${options.domain} not found in SES`);
5969
+ clack7.log.error(`Domain ${options.domain} not found in SES`);
5656
5970
  console.log(
5657
5971
  `
5658
- Run ${pc7.cyan(`wraps email domains add ${options.domain}`)} to add this domain.
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
- clack6.intro(pc7.bold(`Remove domain ${options.domain} from SES`));
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 clack6.confirm({
5681
- message: `Are you sure you want to remove ${pc7.red(options.domain)} from SES?`,
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 (clack6.isCancel(shouldContinue) || !shouldContinue) {
5685
- clack6.cancel("Operation cancelled");
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
- clack6.outro(pc7.green(`\u2713 Domain ${options.domain} removed successfully`));
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
- clack6.log.error(`Domain ${options.domain} not found in SES`);
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 clack7 from "@clack/prompts";
5718
- import * as pulumi7 from "@pulumi/pulumi";
5719
- import pc8 from "picocolors";
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
- clack7.intro(
5729
- pc8.bold(
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: ${pc8.cyan(identity.accountId)}`);
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
- clack7.log.warn(
5769
- `Connection already exists for account ${pc8.cyan(identity.accountId)} in region ${pc8.cyan(region)}`
6082
+ clack8.log.warn(
6083
+ `Connection already exists for account ${pc9.cyan(identity.accountId)} in region ${pc9.cyan(region)}`
5770
6084
  );
5771
- clack7.log.info(`Created: ${existingConnection.timestamp}`);
5772
- clack7.log.info(`Use ${pc8.cyan("wraps status")} to view current setup`);
5773
- clack7.log.info(`Use ${pc8.cyan("wraps upgrade")} to add more features`);
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
- ${pc8.bold("Cost Estimate:")}`);
6108
+ ${pc9.bold("Cost Estimate:")}`);
5795
6109
  const costSummary = getCostSummary(emailConfig, estimatedVolume);
5796
- clack7.log.info(costSummary);
6110
+ clack8.log.info(costSummary);
5797
6111
  const warnings = validateConfig(emailConfig);
5798
6112
  if (warnings.length > 0) {
5799
6113
  progress.info(`
5800
- ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
6114
+ ${pc9.yellow(pc9.bold("Configuration Warnings:"))}`);
5801
6115
  for (const warning of warnings) {
5802
- clack7.log.warn(warning);
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
- clack7.cancel("Deployment cancelled.");
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 pulumi7.automation.LocalWorkspace.createOrSelectStack(
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
- clack7.outro(
5876
- pc8.green("Preview complete. Run without --preview to deploy.")
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 pulumi7.automation.LocalWorkspace.createOrSelectStack(
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: findHostedZone2, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
5976
- const hostedZone = await findHostedZone2(outputs.domain, region);
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
- clack7.log.warn(`Could not auto-create DNS records: ${error.message}`);
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 clack8 from "@clack/prompts";
6046
- import * as pulumi8 from "@pulumi/pulumi";
6047
- import pc9 from "picocolors";
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
- clack8.intro(
6051
- pc9.bold(
6364
+ clack9.intro(
6365
+ pc10.bold(
6052
6366
  options.preview ? "Wraps Restore Preview" : "Wraps Restore - Remove Wraps Infrastructure"
6053
6367
  )
6054
6368
  );
6055
- clack8.log.info(
6056
- `${pc9.yellow("Note:")} This will remove all Wraps-managed infrastructure.`
6369
+ clack9.log.info(
6370
+ `${pc10.yellow("Note:")} This will remove all Wraps-managed infrastructure.`
6057
6371
  );
6058
- clack8.log.info(
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: ${pc9.cyan(identity.accountId)}`);
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
- clack8.log.error(
6075
- `No Wraps connection found for account ${pc9.cyan(identity.accountId)} in region ${pc9.cyan(region)}`
6388
+ clack9.log.error(
6389
+ `No Wraps connection found for account ${pc10.cyan(identity.accountId)} in region ${pc10.cyan(region)}`
6076
6390
  );
6077
- clack8.log.info(
6078
- `Use ${pc9.cyan("wraps email init")} or ${pc9.cyan("wraps email connect")} to create a connection first.`
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
- ${pc9.bold("The following Wraps resources will be removed:")}
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(` ${pc9.cyan("\u2713")} Configuration Set (wraps-email-tracking)`);
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(` ${pc9.cyan("\u2713")} DynamoDB Table (wraps-email-history)`);
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(` ${pc9.cyan("\u2713")} EventBridge Rules`);
6096
- console.log(` ${pc9.cyan("\u2713")} SQS Queues`);
6097
- console.log(` ${pc9.cyan("\u2713")} Lambda Functions`);
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(` ${pc9.cyan("\u2713")} IAM Role (wraps-email-role)`);
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 clack8.confirm({
6416
+ const confirmed = await clack9.confirm({
6103
6417
  message: "Proceed with removal? This cannot be undone.",
6104
6418
  initialValue: false
6105
6419
  });
6106
- if (clack8.isCancel(confirmed) || !confirmed) {
6107
- clack8.cancel("Removal cancelled.");
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 pulumi8.automation.LocalWorkspace.selectStack(
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
- clack8.outro(
6144
- pc9.green(
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 pulumi8.automation.LocalWorkspace.selectStack(
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
- ${pc9.green("\u2713")} ${pc9.bold("Infrastructure removed successfully!")}
6512
+ ${pc10.green("\u2713")} ${pc10.bold("Infrastructure removed successfully!")}
6199
6513
  `
6200
6514
  );
6201
6515
  console.log(
6202
- `${pc9.dim("All Wraps resources have been deleted from your AWS account.")}`
6516
+ `${pc10.dim("All Wraps resources have been deleted from your AWS account.")}`
6203
6517
  );
6204
- console.log(`${pc9.dim("Your original AWS resources remain unchanged.")}
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 clack9 from "@clack/prompts";
6215
- import * as pulumi9 from "@pulumi/pulumi";
6216
- import pc10 from "picocolors";
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
- clack9.intro(
6227
- pc10.bold(
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: ${pc10.cyan(identity.accountId)}`);
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
- clack9.log.error(
6252
- `No Wraps connection found for account ${pc10.cyan(identity.accountId)} in region ${pc10.cyan(region)}`
6656
+ clack11.log.error(
6657
+ `No Wraps connection found for account ${pc12.cyan(identity.accountId)} in region ${pc12.cyan(region)}`
6253
6658
  );
6254
- clack9.log.info(
6255
- `Use ${pc10.cyan("wraps email init")} to create new infrastructure or ${pc10.cyan("wraps email connect")} to connect existing.`
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
- ${pc10.bold("Current Configuration:")}
6666
+ ${pc12.bold("Current Configuration:")}
6262
6667
  `);
6263
6668
  if (metadata.services.email?.preset) {
6264
- console.log(` Preset: ${pc10.cyan(metadata.services.email?.preset)}`);
6669
+ console.log(` Preset: ${pc12.cyan(metadata.services.email?.preset)}`);
6265
6670
  } else {
6266
- console.log(` Preset: ${pc10.cyan("custom")}`);
6671
+ console.log(` Preset: ${pc12.cyan("custom")}`);
6267
6672
  }
6268
6673
  const config2 = metadata.services.email?.config;
6269
6674
  if (!config2) {
6270
- clack9.log.error("No email configuration found in metadata");
6271
- clack9.log.info(
6272
- `Use ${pc10.cyan("wraps email init")} to create new infrastructure.`
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: ${pc10.cyan(config2.domain)}`);
6682
+ console.log(` Sending Domain: ${pc12.cyan(config2.domain)}`);
6278
6683
  }
6279
6684
  if (config2.tracking?.enabled) {
6280
- console.log(` ${pc10.green("\u2713")} Open & Click Tracking`);
6685
+ console.log(` ${pc12.green("\u2713")} Open & Click Tracking`);
6281
6686
  if (config2.tracking.customRedirectDomain) {
6282
6687
  console.log(
6283
- ` ${pc10.dim("\u2514\u2500")} Custom domain: ${pc10.cyan(config2.tracking.customRedirectDomain)}`
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(` ${pc10.green("\u2713")} Bounce/Complaint Suppression`);
6693
+ console.log(` ${pc12.green("\u2713")} Bounce/Complaint Suppression`);
6289
6694
  }
6290
6695
  if (config2.eventTracking?.enabled) {
6291
- console.log(` ${pc10.green("\u2713")} Event Tracking (EventBridge)`);
6696
+ console.log(` ${pc12.green("\u2713")} Event Tracking (EventBridge)`);
6292
6697
  if (config2.eventTracking.dynamoDBHistory) {
6293
6698
  console.log(
6294
- ` ${pc10.dim("\u2514\u2500")} Email History: ${pc10.cyan(config2.eventTracking.archiveRetention || "90days")}`
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(` ${pc10.green("\u2713")} Dedicated IP Address`);
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(` ${pc10.green("\u2713")} Email Archiving (${retentionLabel})`);
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: ${pc10.cyan(`~${formatCost(currentCostData.total.monthly)}/mo`)}`
6734
+ Estimated Cost: ${pc12.cyan(`~${formatCost(currentCostData.total.monthly)}/mo`)}`
6330
6735
  );
6331
6736
  console.log("");
6332
- upgradeAction = await clack9.select({
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 (clack9.isCancel(upgradeAction)) {
6373
- clack9.cancel("Upgrade cancelled.");
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
- clack9.log.warn("Already on highest preset (Enterprise)");
6796
+ clack11.log.warn("Already on highest preset (Enterprise)");
6392
6797
  process.exit(0);
6393
6798
  }
6394
- const selectedPreset = await clack9.select({
6799
+ const selectedPreset = await clack11.select({
6395
6800
  message: "Select new preset:",
6396
6801
  options: availablePresets
6397
6802
  });
6398
- if (clack9.isCancel(selectedPreset)) {
6399
- clack9.cancel("Upgrade cancelled.");
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 clack9.select({
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 (clack9.isCancel(archivingAction)) {
6425
- clack9.cancel("Upgrade cancelled.");
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 clack9.confirm({
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 (clack9.isCancel(confirmDisable) || !confirmDisable) {
6434
- clack9.cancel("Archiving not disabled.");
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 clack9.select({
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 (clack9.isCancel(retention)) {
6482
- clack9.cancel("Upgrade cancelled.");
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 clack9.confirm({
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 (clack9.isCancel(enableArchiving)) {
6499
- clack9.cancel("Upgrade cancelled.");
6903
+ if (clack11.isCancel(enableArchiving)) {
6904
+ clack11.cancel("Upgrade cancelled.");
6500
6905
  process.exit(0);
6501
6906
  }
6502
6907
  if (!enableArchiving) {
6503
- clack9.log.info("Email archiving not enabled.");
6908
+ clack11.log.info("Email archiving not enabled.");
6504
6909
  process.exit(0);
6505
6910
  }
6506
- const retention = await clack9.select({
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 (clack9.isCancel(retention)) {
6543
- clack9.cancel("Upgrade cancelled.");
6947
+ if (clack11.isCancel(retention)) {
6948
+ clack11.cancel("Upgrade cancelled.");
6544
6949
  process.exit(0);
6545
6950
  }
6546
- clack9.log.info(
6547
- pc10.dim(
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
- clack9.log.info(
6552
- pc10.dim(
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
- clack9.log.error(
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
- clack9.log.info(
6573
- `Use ${pc10.cyan("wraps email init")} to set up a sending domain first.`
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
- clack9.log.error(
6585
- `Sending domain ${pc10.cyan(config2.domain)} is not verified.`
6989
+ clack11.log.error(
6990
+ `Sending domain ${pc12.cyan(config2.domain)} is not verified.`
6586
6991
  );
6587
- clack9.log.info(
6992
+ clack11.log.info(
6588
6993
  "You must verify your sending domain before adding a custom tracking domain."
6589
6994
  );
6590
- clack9.log.info(
6591
- `Use ${pc10.cyan("wraps verify")} to check DNS records and complete verification.`
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 ${pc10.cyan(config2.domain)} is verified ${pc10.green("\u2713")}`
7001
+ `Sending domain ${pc12.cyan(config2.domain)} is verified ${pc12.green("\u2713")}`
6597
7002
  );
6598
- const trackingDomain = await clack9.text({
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 (clack9.isCancel(trackingDomain)) {
6609
- clack9.cancel("Upgrade cancelled.");
7013
+ if (clack11.isCancel(trackingDomain)) {
7014
+ clack11.cancel("Upgrade cancelled.");
6610
7015
  process.exit(0);
6611
7016
  }
6612
- const enableHttps = await clack9.confirm({
7017
+ const enableHttps = await clack11.confirm({
6613
7018
  message: "Enable HTTPS tracking with CloudFront + SSL certificate?",
6614
7019
  initialValue: true
6615
7020
  });
6616
- if (clack9.isCancel(enableHttps)) {
6617
- clack9.cancel("Upgrade cancelled.");
7021
+ if (clack11.isCancel(enableHttps)) {
7022
+ clack11.cancel("Upgrade cancelled.");
6618
7023
  process.exit(0);
6619
7024
  }
6620
7025
  if (enableHttps) {
6621
- clack9.log.info(
6622
- pc10.dim(
7026
+ clack11.log.info(
7027
+ pc12.dim(
6623
7028
  "HTTPS tracking creates a CloudFront distribution with an SSL certificate."
6624
7029
  )
6625
7030
  );
6626
- clack9.log.info(
6627
- pc10.dim(
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: findHostedZone2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
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 findHostedZone2(trackingDomain || config2.domain, region)
7039
+ async () => await findHostedZone3(trackingDomain || config2.domain, region)
6635
7040
  );
6636
7041
  if (hostedZone) {
6637
7042
  progress.info(
6638
- `Found Route53 hosted zone: ${pc10.cyan(hostedZone.name)} ${pc10.green("\u2713")}`
7043
+ `Found Route53 hosted zone: ${pc12.cyan(hostedZone.name)} ${pc12.green("\u2713")}`
6639
7044
  );
6640
- clack9.log.info(
6641
- pc10.dim(
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
- clack9.log.warn(
6647
- `No Route53 hosted zone found for ${pc10.cyan(trackingDomain || config2.domain)}`
7051
+ clack11.log.warn(
7052
+ `No Route53 hosted zone found for ${pc12.cyan(trackingDomain || config2.domain)}`
6648
7053
  );
6649
- clack9.log.info(
6650
- pc10.dim(
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
- clack9.log.info(
6655
- pc10.dim("DNS record details will be shown after deployment.")
7059
+ clack11.log.info(
7060
+ pc12.dim("DNS record details will be shown after deployment.")
6656
7061
  );
6657
7062
  }
6658
- const confirmHttps = await clack9.confirm({
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 (clack9.isCancel(confirmHttps) || !confirmHttps) {
6663
- clack9.log.info("HTTPS tracking not enabled. Using HTTP tracking.");
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
- clack9.log.info(
6686
- pc10.dim(
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 clack9.select({
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 (clack9.isCancel(retention)) {
6729
- clack9.cancel("Upgrade cancelled.");
7133
+ if (clack11.isCancel(retention)) {
7134
+ clack11.cancel("Upgrade cancelled.");
6730
7135
  process.exit(0);
6731
7136
  }
6732
- clack9.log.info(
6733
- pc10.dim(
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
- clack9.log.info(
6738
- pc10.dim(
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 clack9.multiselect({
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 (clack9.isCancel(selectedEvents)) {
6800
- clack9.cancel("Upgrade cancelled.");
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 clack9.confirm({
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 (clack9.isCancel(confirmed)) {
6820
- clack9.cancel("Upgrade cancelled.");
7224
+ if (clack11.isCancel(confirmed)) {
7225
+ clack11.cancel("Upgrade cancelled.");
6821
7226
  process.exit(0);
6822
7227
  }
6823
7228
  if (!confirmed) {
6824
- clack9.log.info("Dedicated IP not enabled.");
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
- ${pc10.bold("Cost Impact:")}`);
7250
+ ${pc12.bold("Cost Impact:")}`);
6846
7251
  console.log(
6847
- ` Current: ${pc10.cyan(`${formatCost(currentCostData.total.monthly)}/mo`)}`
7252
+ ` Current: ${pc12.cyan(`${formatCost(currentCostData.total.monthly)}/mo`)}`
6848
7253
  );
6849
7254
  console.log(
6850
- ` New: ${pc10.cyan(`${formatCost(newCostData.total.monthly)}/mo`)}`
7255
+ ` New: ${pc12.cyan(`${formatCost(newCostData.total.monthly)}/mo`)}`
6851
7256
  );
6852
7257
  if (costDiff > 0) {
6853
- console.log(` Change: ${pc10.yellow(`+${formatCost(costDiff)}/mo`)}`);
7258
+ console.log(` Change: ${pc12.yellow(`+${formatCost(costDiff)}/mo`)}`);
6854
7259
  } else if (costDiff < 0) {
6855
7260
  console.log(
6856
- ` Change: ${pc10.green(`${formatCost(Math.abs(costDiff))}/mo`)}`
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 clack9.confirm({
7266
+ const confirmed = await clack11.confirm({
6862
7267
  message: "Proceed with upgrade?",
6863
7268
  initialValue: true
6864
7269
  });
6865
- if (clack9.isCancel(confirmed) || !confirmed) {
6866
- clack9.cancel("Upgrade cancelled.");
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 pulumi9.automation.LocalWorkspace.createOrSelectStack(
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
- clack9.outro(
6939
- pc10.green("Preview complete. Run without --preview to upgrade.")
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 pulumi9.automation.LocalWorkspace.createOrSelectStack(
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: findHostedZone2, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
7039
- const hostedZone = await findHostedZone2(outputs.domain, region);
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
- ${pc10.green("\u2713")} ${pc10.bold("Upgrade complete!")}
7509
+ ${pc12.green("\u2713")} ${pc12.bold("Upgrade complete!")}
7105
7510
  `);
7106
7511
  if (upgradeAction === "preset" && newPreset) {
7107
7512
  console.log(
7108
- `Upgraded to ${pc10.cyan(newPreset)} preset (${pc10.green(`${formatCost(newCostData.total.monthly)}/mo`)})
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 (${pc10.green(`${formatCost(newCostData.total.monthly)}/mo`)})
7518
+ `Updated configuration (${pc12.green(`${formatCost(newCostData.total.monthly)}/mo`)})
7114
7519
  `
7115
7520
  );
7116
7521
  }
7117
7522
  if (needsCertificateValidation) {
7118
- console.log(pc10.bold("\u26A0\uFE0F HTTPS Tracking - Next Steps:\n"));
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 ${pc10.cyan("wraps email upgrade")} again to complete CloudFront setup
7531
+ ` 3. Run ${pc12.cyan("wraps email upgrade")} again to complete CloudFront setup
7127
7532
  `
7128
7533
  );
7129
7534
  console.log(
7130
- pc10.dim(
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
- pc10.green("\u2713") + " " + pc10.bold("HTTPS tracking is fully configured and ready to use!\n")
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 clack10 from "@clack/prompts";
7162
- import * as pulumi10 from "@pulumi/pulumi";
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 pc11 from "picocolors";
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
- clack10.intro(pc11.bold("Wraps Dashboard"));
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 pulumi10.automation.LocalWorkspace.selectStack({
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
- clack10.log.error("No Wraps infrastructure found");
8812
+ clack12.log.error("No Wraps infrastructure found");
8408
8813
  console.log(
8409
- `\\nRun ${pc11.cyan("wraps email init")} to deploy infrastructure first.\\n`
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
- clack10.log.success("Starting dashboard server...");
8823
+ clack12.log.success("Starting dashboard server...");
8419
8824
  console.log(
8420
- `${pc11.dim("Using current AWS credentials (no role assumption)")}\\n`
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${pc11.bold("Dashboard:")} ${pc11.cyan(url)}`);
8434
- console.log(`${pc11.dim("Press Ctrl+C to stop")}\\n`);
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 clack11 from "@clack/prompts";
8452
- import * as pulumi11 from "@pulumi/pulumi";
8453
- import pc12 from "picocolors";
8855
+ import * as clack13 from "@clack/prompts";
8856
+ import pc14 from "picocolors";
8454
8857
  async function destroy(options) {
8455
- const startTime = Date.now();
8456
- clack11.intro(
8457
- pc12.bold(
8458
- options.preview ? "Wraps Destruction Preview" : "Wraps Email Infrastructure Teardown"
8459
- )
8460
- );
8461
- const progress = new DeploymentProgress();
8462
- const identity = await progress.execute(
8463
- "Validating AWS credentials",
8464
- async () => validateAWSCredentials()
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
- if (!(options.force || options.preview)) {
8468
- const confirmed = await clack11.confirm({
8469
- message: pc12.red(
8470
- "Are you sure you want to destroy all Wraps infrastructure?"
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 (options.preview) {
8480
- try {
8481
- const previewResult = await progress.execute(
8482
- "Generating destruction preview",
8483
- async () => {
8484
- await ensurePulumiWorkDir();
8485
- const stackName = `wraps-${identity.accountId}-${region}`;
8486
- let stack;
8487
- try {
8488
- stack = await pulumi11.automation.LocalWorkspace.selectStack({
8489
- stackName,
8490
- workDir: getPulumiWorkDir()
8491
- });
8492
- } catch (_error) {
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
- try {
8523
- await progress.execute(
8524
- "Destroying infrastructure (this may take 2-3 minutes)",
8525
- async () => {
8526
- await ensurePulumiWorkDir();
8527
- const stackName = `wraps-${identity.accountId}-${region}`;
8528
- let stack;
8529
- try {
8530
- stack = await pulumi11.automation.LocalWorkspace.selectStack({
8531
- stackName,
8532
- workDir: getPulumiWorkDir()
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
- } catch (error) {
8543
- progress.stop();
8544
- if (error.message.includes("No Wraps infrastructure found")) {
8545
- clack11.log.warn("No Wraps infrastructure found");
8546
- await deleteConnectionMetadata(identity.accountId, region);
8547
- process.exit(0);
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
- await deleteConnectionMetadata(identity.accountId, region);
8554
- progress.stop();
8555
- clack11.outro(pc12.green("All Wraps infrastructure has been removed"));
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 clack12 from "@clack/prompts";
8572
- import * as pulumi12 from "@pulumi/pulumi";
8573
- import pc13 from "picocolors";
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
- let stackOutputs = {};
8938
+ progress.info(`Region: ${pc15.cyan(region)}`);
8939
+ const services = [];
8583
8940
  try {
8584
8941
  await ensurePulumiWorkDir();
8585
- const stack = await pulumi12.automation.LocalWorkspace.selectStack({
8942
+ const stack = await pulumi13.automation.LocalWorkspace.selectStack({
8586
8943
  stackName: `wraps-${identity.accountId}-${region}`,
8587
8944
  workDir: getPulumiWorkDir()
8588
8945
  });
8589
- stackOutputs = await stack.outputs();
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
- progress.stop();
8592
- clack12.log.error("No Wraps infrastructure found");
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
- const domains = await listSESDomains(region);
8601
- const { SESv2Client: SESv2Client5, GetEmailIdentityCommand: GetEmailIdentityCommand4 } = await import("@aws-sdk/client-sesv2");
8602
- const sesv2Client = new SESv2Client5({ region });
8603
- const domainsWithTokens = await Promise.all(
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
- domain_count: domainsWithTokens.length,
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 clack13 from "@clack/prompts";
8661
- import pc14 from "picocolors";
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
- clack13.log.success(pc14.green("Telemetry enabled"));
8666
- console.log(` Config: ${pc14.dim(client.getConfigPath())}`);
9007
+ clack15.log.success(pc16.green("Telemetry enabled"));
9008
+ console.log(` Config: ${pc16.dim(client.getConfigPath())}`);
8667
9009
  console.log(`
8668
- ${pc14.dim("Thank you for helping improve Wraps!")}
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
- clack13.log.success(pc14.green("Telemetry disabled"));
8675
- console.log(` Config: ${pc14.dim(client.getConfigPath())}`);
9016
+ clack15.log.success(pc16.green("Telemetry disabled"));
9017
+ console.log(` Config: ${pc16.dim(client.getConfigPath())}`);
8676
9018
  console.log(
8677
9019
  `
8678
- ${pc14.dim("You can re-enable with:")} wraps telemetry enable
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
- clack13.intro(pc14.bold("Telemetry Status"));
8685
- const status2 = client.isEnabled() ? pc14.green("Enabled") : pc14.red("Disabled");
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(` ${pc14.bold("Status:")} ${status2}`);
8688
- console.log(` ${pc14.bold("Config file:")} ${pc14.dim(client.getConfigPath())}`);
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(pc14.bold(" How to opt-out:"));
8692
- console.log(` ${pc14.cyan("wraps telemetry disable")}`);
9033
+ console.log(pc16.bold(" How to opt-out:"));
9034
+ console.log(` ${pc16.cyan("wraps telemetry disable")}`);
8693
9035
  console.log(
8694
- ` ${pc14.dim("Or set:")} ${pc14.cyan("WRAPS_TELEMETRY_DISABLED=1")}`
9036
+ ` ${pc16.dim("Or set:")} ${pc16.cyan("WRAPS_TELEMETRY_DISABLED=1")}`
8695
9037
  );
8696
- console.log(` ${pc14.dim("Or set:")} ${pc14.cyan("DO_NOT_TRACK=1")}`);
9038
+ console.log(` ${pc16.dim("Or set:")} ${pc16.cyan("DO_NOT_TRACK=1")}`);
8697
9039
  } else {
8698
9040
  console.log();
8699
- console.log(pc14.bold(" How to opt-in:"));
8700
- console.log(` ${pc14.cyan("wraps telemetry enable")}`);
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(pc14.bold(" Debug mode:"));
9045
+ console.log(pc16.bold(" Debug mode:"));
8704
9046
  console.log(
8705
- ` ${pc14.dim("See what would be sent:")} ${pc14.cyan("WRAPS_TELEMETRY_DEBUG=1 wraps <command>")}`
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
- ` ${pc14.dim("Learn more:")} ${pc14.cyan("https://wraps.dev/docs/telemetry")}`
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 status [--account <account-id>]");
8732
- console.log("# wraps completion\n");
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 : AWS account ID or alias\n");
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
- clack14.intro(pc15.bold(`WRAPS CLI v${VERSION}`));
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(` ${pc15.cyan("email")} Email infrastructure (AWS SES)`);
9124
+ console.log(` ${pc17.cyan("email")} Email infrastructure (AWS SES)
9125
+ `);
9126
+ console.log("Email Commands:");
8761
9127
  console.log(
8762
- ` ${pc15.cyan("sms")} SMS infrastructure (AWS End User Messaging) ${pc15.dim("[coming soon]")}
8763
- `
9128
+ ` ${pc17.cyan("email init")} Deploy new email infrastructure`
8764
9129
  );
8765
- console.log("Email Commands:");
8766
9130
  console.log(
8767
- ` ${pc15.cyan("email init")} Deploy new email infrastructure`
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
- ` ${pc15.cyan("email connect")} Connect to existing AWS SES`
9136
+ ` ${pc17.cyan("email sync")} Apply CLI updates to infrastructure`
8771
9137
  );
8772
- console.log(` ${pc15.cyan("email domains verify")} Verify domain DNS records`);
9138
+ console.log(` ${pc17.cyan("email upgrade")} Add features`);
8773
9139
  console.log(
8774
- ` ${pc15.cyan("email sync")} Apply CLI updates to infrastructure`
9140
+ ` ${pc17.cyan("email restore")} Restore original configuration`
8775
9141
  );
8776
- console.log(` ${pc15.cyan("email upgrade")} Add features`);
8777
9142
  console.log(
8778
- ` ${pc15.cyan("email restore")} Restore original configuration
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(` ${pc15.cyan("console")} Start local web console`);
9150
+ console.log(` ${pc17.cyan("console")} Start local web console`);
8783
9151
  console.log(
8784
- ` ${pc15.cyan("dashboard update-role")} Update hosted dashboard IAM permissions
9152
+ ` ${pc17.cyan("dashboard update-role")} Update hosted dashboard IAM permissions
8785
9153
  `
8786
9154
  );
8787
9155
  console.log("Global Commands:");
8788
- console.log(` ${pc15.cyan("status")} Show all infrastructure status`);
8789
- console.log(` ${pc15.cyan("destroy")} Remove deployed infrastructure`);
8790
- console.log(` ${pc15.cyan("completion")} Generate shell completion script`);
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
- ` ${pc15.cyan("telemetry")} Manage anonymous telemetry settings
9160
+ ` ${pc17.cyan("telemetry")} Manage anonymous telemetry settings
8793
9161
  `
8794
9162
  );
8795
9163
  console.log("Options:");
8796
9164
  console.log(
8797
- ` ${pc15.dim("-p, --provider")} Hosting provider (vercel, aws, railway, other)`
9165
+ ` ${pc17.dim("-p, --provider")} Hosting provider (vercel, aws, railway, other)`
8798
9166
  );
8799
- console.log(` ${pc15.dim("-r, --region")} AWS region`);
8800
- console.log(` ${pc15.dim("-d, --domain")} Domain name`);
8801
- console.log(` ${pc15.dim("--account")} AWS account ID or alias`);
8802
- console.log(` ${pc15.dim("--preset")} Configuration preset`);
8803
- console.log(` ${pc15.dim("-y, --yes")} Skip confirmation prompts`);
8804
- console.log(` ${pc15.dim("-f, --force")} Force destructive operations`);
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
- ` ${pc15.dim("--preview")} Preview changes without deploying`
9174
+ ` ${pc17.dim("--preview")} Preview changes without deploying`
8807
9175
  );
8808
- console.log(` ${pc15.dim("-v, --version")} Show version number
9176
+ console.log(` ${pc17.dim("-v, --version")} Show version number
8809
9177
  `);
8810
9178
  console.log(
8811
- `Run ${pc15.cyan("wraps <service> <command> --help")} for more information.
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
- clack14.intro(pc15.bold(`WRAPS CLI v${VERSION}`));
8879
- console.log("Welcome! Let's get started deploying your infrastructure.\n");
8880
- const service = await clack14.select({
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 (clack14.isCancel(action)) {
8924
- clack14.cancel("Operation cancelled.");
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
- clack14.log.info(pc15.bold("Anonymous Telemetry"));
9293
+ clack16.log.info(pc17.bold("Anonymous Telemetry"));
8954
9294
  console.log(
8955
- ` Wraps collects ${pc15.cyan("anonymous usage data")} to improve the CLI.`
9295
+ ` Wraps collects ${pc17.cyan("anonymous usage data")} to improve the CLI.`
8956
9296
  );
8957
9297
  console.log(
8958
- ` We ${pc15.bold("never")} collect: domains, AWS credentials, email content, or PII.`
9298
+ ` We ${pc17.bold("never")} collect: domains, AWS credentials, email content, or PII.`
8959
9299
  );
8960
9300
  console.log(
8961
- ` We ${pc15.bold("only")} collect: command names, success/failure, CLI version, OS.`
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: ${pc15.cyan("wraps telemetry disable")}`);
8965
- console.log(` Or set: ${pc15.cyan("WRAPS_TELEMETRY_DISABLED=1")}`);
8966
- console.log(` Learn more: ${pc15.cyan("https://wraps.dev/docs/telemetry")}`);
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
- clack14.log.error("--domain flag is required");
9376
+ clack16.log.error("--domain flag is required");
9019
9377
  console.log(
9020
9378
  `
9021
- Usage: ${pc15.cyan("wraps email domains add --domain yourapp.com")}
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
- clack14.log.error("--domain flag is required");
9392
+ clack16.log.error("--domain flag is required");
9035
9393
  console.log(
9036
9394
  `
9037
- Usage: ${pc15.cyan("wraps email domains verify --domain yourapp.com")}
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
- clack14.log.error("--domain flag is required");
9405
+ clack16.log.error("--domain flag is required");
9048
9406
  console.log(
9049
9407
  `
9050
- Usage: ${pc15.cyan("wraps email domains get-dkim --domain yourapp.com")}
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
- clack14.log.error("--domain flag is required");
9418
+ clack16.log.error("--domain flag is required");
9061
9419
  console.log(
9062
9420
  `
9063
- Usage: ${pc15.cyan("wraps email domains remove --domain yourapp.com --force")}
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
- clack14.log.error(
9433
+ clack16.log.error(
9076
9434
  `Unknown domains command: ${domainsSubCommand || "(none)"}`
9077
9435
  );
9078
9436
  console.log(
9079
9437
  `
9080
- Available commands: ${pc15.cyan("add")}, ${pc15.cyan("list")}, ${pc15.cyan("verify")}, ${pc15.cyan("get-dkim")}, ${pc15.cyan("remove")}
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
- clack14.log.error(`Unknown email command: ${subCommand}`);
9452
+ clack16.log.error(`Unknown email command: ${subCommand}`);
9089
9453
  console.log(
9090
9454
  `
9091
- Run ${pc15.cyan("wraps --help")} for available commands.
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
- clack14.log.error(`Unknown dashboard command: ${subCommand}`);
9478
+ clack16.log.error(`Unknown dashboard command: ${subCommand}`);
9115
9479
  console.log(`
9116
- Available commands: ${pc15.cyan("update-role")}
9480
+ Available commands: ${pc17.cyan("update-role")}
9117
9481
  `);
9118
- console.log(`Run ${pc15.cyan("wraps --help")} for more information.
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
- clack14.log.warn(
9155
- `'wraps dashboard' is deprecated. Use ${pc15.cyan("wraps console")} instead.`
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
- clack14.log.error(`Unknown telemetry command: ${subCommand}`);
9540
+ clack16.log.error(`Unknown telemetry command: ${subCommand}`);
9186
9541
  console.log(
9187
9542
  `
9188
- Available commands: ${pc15.cyan("enable")}, ${pc15.cyan("disable")}, ${pc15.cyan("status")}
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
- clack14.log.error(`Unknown command: ${primaryCommand}`);
9560
+ clack16.log.error(`Unknown command: ${primaryCommand}`);
9207
9561
  console.log(
9208
9562
  `
9209
- Run ${pc15.cyan("wraps --help")} for available commands.
9563
+ Run ${pc17.cyan("wraps --help")} for available commands.
9210
9564
  `
9211
9565
  );
9212
9566
  process.exit(1);