@wraps.dev/cli 1.4.1 → 1.5.1

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.4.1",
149
+ version: "1.5.1",
150
150
  description: "CLI for deploying Wraps email infrastructure to your AWS account",
151
151
  type: "module",
152
152
  main: "./dist/cli.js",
@@ -448,6 +448,21 @@ function trackCommand(command, metadata) {
448
448
  sanitized.email = void 0;
449
449
  client.track(`command:${command}`, sanitized);
450
450
  }
451
+ function trackServiceInit(service, success, metadata) {
452
+ const client = getTelemetryClient();
453
+ client.track("service:init", {
454
+ service,
455
+ success,
456
+ ...metadata
457
+ });
458
+ }
459
+ function trackServiceDeployed(service, metadata) {
460
+ const client = getTelemetryClient();
461
+ client.track("service:deployed", {
462
+ service,
463
+ ...metadata
464
+ });
465
+ }
451
466
  function trackError(errorCode, command, metadata) {
452
467
  const client = getTelemetryClient();
453
468
  client.track("error:occurred", {
@@ -456,6 +471,24 @@ function trackError(errorCode, command, metadata) {
456
471
  ...metadata
457
472
  });
458
473
  }
474
+ function trackFeature(feature, metadata) {
475
+ const client = getTelemetryClient();
476
+ client.track(`feature:${feature}`, metadata || {});
477
+ }
478
+ function trackServiceUpgrade(service, metadata) {
479
+ const client = getTelemetryClient();
480
+ client.track("service:upgraded", {
481
+ service,
482
+ ...metadata
483
+ });
484
+ }
485
+ function trackServiceRemoved(service, metadata) {
486
+ const client = getTelemetryClient();
487
+ client.track("service:removed", {
488
+ service,
489
+ ...metadata
490
+ });
491
+ }
459
492
  var init_events = __esm({
460
493
  "src/telemetry/events.ts"() {
461
494
  "use strict";
@@ -3198,6 +3231,35 @@ ${pc2.bold("Dashboard:")} ${pc2.blue("https://app.wraps.dev")}`);
3198
3231
  console.log(`${pc2.bold("Docs:")} ${pc2.blue("https://wraps.dev/docs")}
3199
3232
  `);
3200
3233
  }
3234
+ function displayPreview(outputs) {
3235
+ console.log(pc2.yellow("\n--- PREVIEW MODE (no changes will be made) ---\n"));
3236
+ const changes = outputs.changeSummary;
3237
+ const summaryLines = [];
3238
+ if (changes.create && changes.create > 0) {
3239
+ summaryLines.push(` ${pc2.green("+")} ${changes.create} to create`);
3240
+ }
3241
+ if (changes.update && changes.update > 0) {
3242
+ summaryLines.push(` ${pc2.yellow("~")} ${changes.update} to update`);
3243
+ }
3244
+ if (changes.delete && changes.delete > 0) {
3245
+ summaryLines.push(` ${pc2.red("-")} ${changes.delete} to destroy`);
3246
+ }
3247
+ if (changes.same && changes.same > 0) {
3248
+ summaryLines.push(` ${pc2.dim("=")} ${changes.same} unchanged`);
3249
+ }
3250
+ if (changes.replace && changes.replace > 0) {
3251
+ summaryLines.push(` ${pc2.magenta("+-")} ${changes.replace} to replace`);
3252
+ }
3253
+ if (summaryLines.length > 0) {
3254
+ clack2.note(summaryLines.join("\n"), "Resource Changes");
3255
+ } else {
3256
+ clack2.note("No changes detected", "Resource Changes");
3257
+ }
3258
+ if (outputs.costEstimate) {
3259
+ clack2.note(outputs.costEstimate, "Estimated Monthly Cost");
3260
+ }
3261
+ console.log(pc2.yellow("\n--- END PREVIEW (no changes were made) ---\n"));
3262
+ }
3201
3263
 
3202
3264
  // src/commands/dashboard/update-role.ts
3203
3265
  async function updateRole(options) {
@@ -3965,19 +4027,14 @@ async function configurationSetExists(configSetName, region) {
3965
4027
  }
3966
4028
  async function eventDestinationExists(configSetName, eventDestName, region) {
3967
4029
  try {
3968
- const {
3969
- SESv2Client: SESv2Client5,
3970
- GetConfigurationSetEventDestinationsCommand
3971
- } = await import("@aws-sdk/client-sesv2");
4030
+ const { SESv2Client: SESv2Client5, GetConfigurationSetEventDestinationsCommand } = await import("@aws-sdk/client-sesv2");
3972
4031
  const ses = new SESv2Client5({ region });
3973
4032
  const response = await ses.send(
3974
4033
  new GetConfigurationSetEventDestinationsCommand({
3975
4034
  ConfigurationSetName: configSetName
3976
4035
  })
3977
4036
  );
3978
- return response.EventDestinations?.some(
3979
- (dest) => dest.Name === eventDestName
3980
- ) ?? false;
4037
+ return response.EventDestinations?.some((dest) => dest.Name === eventDestName) ?? false;
3981
4038
  } catch (error) {
3982
4039
  if (error.name === "NotFoundException") {
3983
4040
  return false;
@@ -4360,6 +4417,7 @@ async function deployEmailStack(config2) {
4360
4417
  }
4361
4418
 
4362
4419
  // src/commands/email/config.ts
4420
+ init_events();
4363
4421
  init_aws();
4364
4422
  init_errors();
4365
4423
 
@@ -4394,7 +4452,12 @@ async function ensurePulumiInstalled() {
4394
4452
 
4395
4453
  // src/commands/email/config.ts
4396
4454
  async function config(options) {
4397
- clack3.intro(pc4.bold("Wraps Config - Apply CLI Updates to Infrastructure"));
4455
+ const startTime = Date.now();
4456
+ clack3.intro(
4457
+ pc4.bold(
4458
+ options.preview ? "Wraps Config Preview" : "Wraps Config - Apply CLI Updates to Infrastructure"
4459
+ )
4460
+ );
4398
4461
  const progress = new DeploymentProgress();
4399
4462
  const wasAutoInstalled = await progress.execute(
4400
4463
  "Checking Pulumi CLI installation",
@@ -4471,7 +4534,7 @@ ${pc4.bold("Current Configuration:")}
4471
4534
  "Your current configuration will be preserved - no features will be added or removed"
4472
4535
  );
4473
4536
  console.log("");
4474
- if (!options.yes) {
4537
+ if (!(options.yes || options.preview)) {
4475
4538
  const confirmed = await clack3.confirm({
4476
4539
  message: "Proceed with update?",
4477
4540
  initialValue: true
@@ -4491,6 +4554,67 @@ ${pc4.bold("Current Configuration:")}
4491
4554
  vercel: vercelConfig,
4492
4555
  emailConfig: config2
4493
4556
  };
4557
+ if (options.preview) {
4558
+ try {
4559
+ const previewResult = await progress.execute(
4560
+ "Generating update preview",
4561
+ async () => {
4562
+ await ensurePulumiWorkDir();
4563
+ const stack = await pulumi5.automation.LocalWorkspace.createOrSelectStack(
4564
+ {
4565
+ stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
4566
+ projectName: "wraps-email",
4567
+ program: async () => {
4568
+ const result2 = await deployEmailStack(stackConfig);
4569
+ return {
4570
+ roleArn: result2.roleArn,
4571
+ configSetName: result2.configSetName,
4572
+ tableName: result2.tableName,
4573
+ region: result2.region,
4574
+ lambdaFunctions: result2.lambdaFunctions,
4575
+ domain: result2.domain,
4576
+ dkimTokens: result2.dkimTokens,
4577
+ customTrackingDomain: result2.customTrackingDomain
4578
+ };
4579
+ }
4580
+ },
4581
+ {
4582
+ workDir: getPulumiWorkDir(),
4583
+ envVars: {
4584
+ PULUMI_CONFIG_PASSPHRASE: "",
4585
+ AWS_REGION: region
4586
+ },
4587
+ secretsProvider: "passphrase"
4588
+ }
4589
+ );
4590
+ await stack.setConfig("aws:region", { value: region });
4591
+ await stack.refresh({ onOutput: () => {
4592
+ } });
4593
+ const result = await stack.preview({ diff: true });
4594
+ return result;
4595
+ }
4596
+ );
4597
+ displayPreview({
4598
+ changeSummary: previewResult.changeSummary,
4599
+ commandName: "wraps email config"
4600
+ });
4601
+ clack3.outro(
4602
+ pc4.green("Preview complete. Run without --preview to update.")
4603
+ );
4604
+ trackCommand("email:config", {
4605
+ success: true,
4606
+ preview: true,
4607
+ duration_ms: Date.now() - startTime
4608
+ });
4609
+ return;
4610
+ } catch (error) {
4611
+ trackError("PREVIEW_FAILED", "email:config", { step: "preview" });
4612
+ if (error.message?.includes("stack is currently locked")) {
4613
+ throw errors.stackLocked();
4614
+ }
4615
+ throw new Error(`Preview failed: ${error.message}`);
4616
+ }
4617
+ }
4494
4618
  let outputs;
4495
4619
  try {
4496
4620
  outputs = await progress.execute(
@@ -4546,9 +4670,15 @@ ${pc4.bold("Current Configuration:")}
4546
4670
  }
4547
4671
  );
4548
4672
  } catch (error) {
4673
+ trackCommand("email:config", {
4674
+ success: false,
4675
+ duration_ms: Date.now() - startTime
4676
+ });
4549
4677
  if (error.message?.includes("stack is currently locked")) {
4678
+ trackError("STACK_LOCKED", "email:config", { step: "update" });
4550
4679
  throw errors.stackLocked();
4551
4680
  }
4681
+ trackError("UPDATE_FAILED", "email:config", { step: "update" });
4552
4682
  throw new Error(`Pulumi update failed: ${error.message}`);
4553
4683
  }
4554
4684
  metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -4579,6 +4709,10 @@ ${pc4.green("\u2713")} ${pc4.bold("Update complete!")}
4579
4709
  ` ${pc4.cyan("3.")} View analytics at ${pc4.cyan("wraps console")}
4580
4710
  `
4581
4711
  );
4712
+ trackCommand("email:config", {
4713
+ success: true,
4714
+ duration_ms: Date.now() - startTime
4715
+ });
4582
4716
  }
4583
4717
 
4584
4718
  // src/commands/email/connect.ts
@@ -4586,6 +4720,7 @@ init_esm_shims();
4586
4720
  import * as clack5 from "@clack/prompts";
4587
4721
  import * as pulumi6 from "@pulumi/pulumi";
4588
4722
  import pc6 from "picocolors";
4723
+ init_events();
4589
4724
  init_presets();
4590
4725
  init_aws();
4591
4726
  init_errors();
@@ -4819,7 +4954,12 @@ async function scanAWSResources(region) {
4819
4954
 
4820
4955
  // src/commands/email/connect.ts
4821
4956
  async function connect(options) {
4822
- clack5.intro(pc6.bold("Wraps Connect - Link Existing Infrastructure"));
4957
+ const startTime = Date.now();
4958
+ clack5.intro(
4959
+ pc6.bold(
4960
+ options.preview ? "Wraps Connect Preview" : "Wraps Connect - Link Existing Infrastructure"
4961
+ )
4962
+ );
4823
4963
  const progress = new DeploymentProgress();
4824
4964
  const wasAutoInstalled = await progress.execute(
4825
4965
  "Checking Pulumi CLI installation",
@@ -4897,7 +5037,7 @@ async function connect(options) {
4897
5037
  if (domainIdentities.length > 0) {
4898
5038
  emailConfig.domain = domainIdentities[0];
4899
5039
  }
4900
- if (!options.yes) {
5040
+ if (!(options.yes || options.preview)) {
4901
5041
  const confirmed = await confirmConnect();
4902
5042
  if (!confirmed) {
4903
5043
  clack5.cancel("Connection cancelled.");
@@ -4910,6 +5050,67 @@ async function connect(options) {
4910
5050
  vercel: vercelConfig,
4911
5051
  emailConfig
4912
5052
  };
5053
+ if (options.preview) {
5054
+ try {
5055
+ const previewResult = await progress.execute(
5056
+ "Generating infrastructure preview",
5057
+ async () => {
5058
+ await ensurePulumiWorkDir();
5059
+ const stack = await pulumi6.automation.LocalWorkspace.createOrSelectStack(
5060
+ {
5061
+ stackName: `wraps-${identity.accountId}-${region}`,
5062
+ projectName: "wraps-email",
5063
+ program: async () => {
5064
+ const result2 = await deployEmailStack(stackConfig);
5065
+ return {
5066
+ roleArn: result2.roleArn,
5067
+ configSetName: result2.configSetName,
5068
+ tableName: result2.tableName,
5069
+ region: result2.region,
5070
+ lambdaFunctions: result2.lambdaFunctions,
5071
+ domain: result2.domain,
5072
+ dkimTokens: result2.dkimTokens,
5073
+ customTrackingDomain: result2.customTrackingDomain
5074
+ };
5075
+ }
5076
+ },
5077
+ {
5078
+ workDir: getPulumiWorkDir(),
5079
+ envVars: {
5080
+ PULUMI_CONFIG_PASSPHRASE: "",
5081
+ AWS_REGION: region
5082
+ },
5083
+ secretsProvider: "passphrase"
5084
+ }
5085
+ );
5086
+ await stack.setConfig("aws:region", { value: region });
5087
+ const result = await stack.preview({ diff: true });
5088
+ return result;
5089
+ }
5090
+ );
5091
+ displayPreview({
5092
+ changeSummary: previewResult.changeSummary,
5093
+ commandName: "wraps email connect"
5094
+ });
5095
+ clack5.outro(
5096
+ pc6.green("Preview complete. Run without --preview to connect.")
5097
+ );
5098
+ trackServiceInit("email", true, {
5099
+ preset,
5100
+ provider,
5101
+ preview: true,
5102
+ duration_ms: Date.now() - startTime,
5103
+ existing_identities: selectedIdentities.length
5104
+ });
5105
+ return;
5106
+ } catch (error) {
5107
+ trackError("PREVIEW_FAILED", "email:connect", { step: "preview" });
5108
+ if (error.message?.includes("stack is currently locked")) {
5109
+ throw errors.stackLocked();
5110
+ }
5111
+ throw new Error(`Preview failed: ${error.message}`);
5112
+ }
5113
+ }
4913
5114
  let outputs;
4914
5115
  try {
4915
5116
  outputs = await progress.execute(
@@ -4963,9 +5164,16 @@ async function connect(options) {
4963
5164
  }
4964
5165
  );
4965
5166
  } catch (error) {
5167
+ trackServiceInit("email", false, {
5168
+ preset,
5169
+ provider,
5170
+ duration_ms: Date.now() - startTime
5171
+ });
4966
5172
  if (error.message?.includes("stack is currently locked")) {
5173
+ trackError("STACK_LOCKED", "email:connect", { step: "deploy" });
4967
5174
  throw errors.stackLocked();
4968
5175
  }
5176
+ trackError("DEPLOYMENT_FAILED", "email:connect", { step: "deploy" });
4969
5177
  throw new Error(`Pulumi deployment failed: ${error.message}`);
4970
5178
  }
4971
5179
  if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
@@ -5032,10 +5240,32 @@ ${pc6.dim("Example:")}`);
5032
5240
  );
5033
5241
  console.log("");
5034
5242
  }
5243
+ const duration = Date.now() - startTime;
5244
+ const enabledFeatures = [];
5245
+ if (emailConfig.tracking?.enabled) enabledFeatures.push("tracking");
5246
+ if (emailConfig.suppressionList?.enabled)
5247
+ enabledFeatures.push("suppression_list");
5248
+ if (emailConfig.eventTracking?.enabled)
5249
+ enabledFeatures.push("event_tracking");
5250
+ if (emailConfig.eventTracking?.dynamoDBHistory)
5251
+ enabledFeatures.push("dynamodb_history");
5252
+ trackServiceInit("email", true, {
5253
+ preset,
5254
+ provider,
5255
+ features: enabledFeatures,
5256
+ duration_ms: duration,
5257
+ existing_identities: selectedIdentities.length
5258
+ });
5259
+ trackServiceDeployed("email", {
5260
+ duration_ms: duration,
5261
+ features: enabledFeatures,
5262
+ preset
5263
+ });
5035
5264
  }
5036
5265
 
5037
5266
  // src/commands/email/domains.ts
5038
5267
  init_esm_shims();
5268
+ init_events();
5039
5269
  init_aws();
5040
5270
  import { Resolver } from "dns/promises";
5041
5271
  import { GetEmailIdentityCommand, SESv2Client as SESv2Client2 } from "@aws-sdk/client-sesv2";
@@ -5207,6 +5437,7 @@ Run ${pc7.cyan(`wraps email init --domain ${options.domain}`)} to add this domai
5207
5437
  clack6.outro(
5208
5438
  pc7.green("\u2713 Domain is fully verified and ready to send emails!")
5209
5439
  );
5440
+ trackFeature("domain_verified", { dns_auto_detected: true });
5210
5441
  } else if (someIncorrect) {
5211
5442
  clack6.outro(
5212
5443
  pc7.red("\u2717 Some DNS records are incorrect. Please update them.")
@@ -5225,6 +5456,11 @@ Run ${pc7.cyan("wraps email status")} to see the correct DNS records.
5225
5456
  "SES verification usually completes within 72 hours after DNS propagation.\n"
5226
5457
  );
5227
5458
  }
5459
+ trackCommand("email:domains:verify", {
5460
+ success: true,
5461
+ verified: verificationStatus === "verified" && allVerified,
5462
+ dkim_status: dkimStatus
5463
+ });
5228
5464
  }
5229
5465
  async function addDomain(options) {
5230
5466
  clack6.intro(pc7.bold(`Adding domain ${options.domain} to SES`));
@@ -5282,8 +5518,15 @@ ${pc7.bold("Next steps:")}
5282
5518
  );
5283
5519
  console.log(`3. Check status: ${pc7.cyan("wraps email status")}
5284
5520
  `);
5521
+ trackCommand("email:domains:add", {
5522
+ success: true
5523
+ });
5524
+ trackFeature("domain_added", {});
5285
5525
  } catch (error) {
5286
5526
  progress.stop();
5527
+ trackCommand("email:domains:add", {
5528
+ success: false
5529
+ });
5287
5530
  throw error;
5288
5531
  }
5289
5532
  }
@@ -5352,8 +5595,13 @@ Run ${pc7.cyan("wraps email domains add <domain>")} to add a domain.
5352
5595
  `Run ${pc7.cyan("wraps email domains verify --domain <domain>")} for details`
5353
5596
  )
5354
5597
  );
5598
+ trackCommand("email:domains:list", {
5599
+ success: true,
5600
+ domain_count: domains.length
5601
+ });
5355
5602
  } catch (error) {
5356
5603
  progress.stop();
5604
+ trackCommand("email:domains:list", { success: false });
5357
5605
  throw error;
5358
5606
  }
5359
5607
  }
@@ -5396,8 +5644,13 @@ ${pc7.bold("DNS Records to add:")}
5396
5644
  `
5397
5645
  );
5398
5646
  }
5647
+ trackCommand("email:domains:get-dkim", {
5648
+ success: true,
5649
+ dkim_status: dkimStatus
5650
+ });
5399
5651
  } catch (error) {
5400
5652
  progress.stop();
5653
+ trackCommand("email:domains:get-dkim", { success: false });
5401
5654
  if (error.name === "NotFoundException") {
5402
5655
  clack6.log.error(`Domain ${options.domain} not found in SES`);
5403
5656
  console.log(
@@ -5443,8 +5696,13 @@ async function removeDomain(options) {
5443
5696
  });
5444
5697
  progress.stop();
5445
5698
  clack6.outro(pc7.green(`\u2713 Domain ${options.domain} removed successfully`));
5699
+ trackCommand("email:domains:remove", {
5700
+ success: true
5701
+ });
5702
+ trackFeature("domain_removed", {});
5446
5703
  } catch (error) {
5447
5704
  progress.stop();
5705
+ trackCommand("email:domains:remove", { success: false });
5448
5706
  if (error.name === "NotFoundException") {
5449
5707
  clack6.log.error(`Domain ${options.domain} not found in SES`);
5450
5708
  process.exit(1);
@@ -5459,13 +5717,19 @@ init_esm_shims();
5459
5717
  import * as clack7 from "@clack/prompts";
5460
5718
  import * as pulumi7 from "@pulumi/pulumi";
5461
5719
  import pc8 from "picocolors";
5720
+ init_events();
5462
5721
  init_costs();
5463
5722
  init_presets();
5464
5723
  init_aws();
5465
5724
  init_errors();
5466
5725
  init_prompts();
5467
5726
  async function init(options) {
5468
- clack7.intro(pc8.bold("Wraps Email Infrastructure Setup"));
5727
+ const startTime = Date.now();
5728
+ clack7.intro(
5729
+ pc8.bold(
5730
+ options.preview ? "Wraps Email Infrastructure Preview" : "Wraps Email Infrastructure Setup"
5731
+ )
5732
+ );
5469
5733
  const progress = new DeploymentProgress();
5470
5734
  const wasAutoInstalled = await progress.execute(
5471
5735
  "Checking Pulumi CLI installation",
@@ -5548,7 +5812,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
5548
5812
  if (vercelConfig) {
5549
5813
  metadata.vercel = vercelConfig;
5550
5814
  }
5551
- if (!options.yes) {
5815
+ if (!(options.yes || options.preview)) {
5552
5816
  const confirmed = await confirmDeploy();
5553
5817
  if (!confirmed) {
5554
5818
  clack7.cancel("Deployment cancelled.");
@@ -5561,6 +5825,71 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
5561
5825
  vercel: vercelConfig,
5562
5826
  emailConfig
5563
5827
  };
5828
+ if (options.preview) {
5829
+ try {
5830
+ const previewResult = await progress.execute(
5831
+ "Generating infrastructure preview",
5832
+ async () => {
5833
+ await ensurePulumiWorkDir();
5834
+ const stack = await pulumi7.automation.LocalWorkspace.createOrSelectStack(
5835
+ {
5836
+ stackName: `wraps-${identity.accountId}-${region}`,
5837
+ projectName: "wraps-email",
5838
+ program: async () => {
5839
+ const result2 = await deployEmailStack(stackConfig);
5840
+ return {
5841
+ roleArn: result2.roleArn,
5842
+ configSetName: result2.configSetName,
5843
+ tableName: result2.tableName,
5844
+ region: result2.region,
5845
+ lambdaFunctions: result2.lambdaFunctions,
5846
+ domain: result2.domain,
5847
+ dkimTokens: result2.dkimTokens,
5848
+ customTrackingDomain: result2.customTrackingDomain,
5849
+ mailFromDomain: result2.mailFromDomain,
5850
+ archiveArn: result2.archiveArn,
5851
+ archivingEnabled: result2.archivingEnabled,
5852
+ archiveRetention: result2.archiveRetention
5853
+ };
5854
+ }
5855
+ },
5856
+ {
5857
+ workDir: getPulumiWorkDir(),
5858
+ envVars: {
5859
+ PULUMI_CONFIG_PASSPHRASE: "",
5860
+ AWS_REGION: region
5861
+ },
5862
+ secretsProvider: "passphrase"
5863
+ }
5864
+ );
5865
+ await stack.setConfig("aws:region", { value: region });
5866
+ const result = await stack.preview({ diff: true });
5867
+ return result;
5868
+ }
5869
+ );
5870
+ displayPreview({
5871
+ changeSummary: previewResult.changeSummary,
5872
+ costEstimate: costSummary,
5873
+ commandName: "wraps email init"
5874
+ });
5875
+ clack7.outro(
5876
+ pc8.green("Preview complete. Run without --preview to deploy.")
5877
+ );
5878
+ trackServiceInit("email", true, {
5879
+ preset,
5880
+ provider,
5881
+ preview: true,
5882
+ duration_ms: Date.now() - startTime
5883
+ });
5884
+ return;
5885
+ } catch (error) {
5886
+ trackError("PREVIEW_FAILED", "email:init", { step: "preview" });
5887
+ if (error.message?.includes("stack is currently locked")) {
5888
+ throw errors.stackLocked();
5889
+ }
5890
+ throw new Error(`Preview failed: ${error.message}`);
5891
+ }
5892
+ }
5564
5893
  let outputs;
5565
5894
  try {
5566
5895
  outputs = await progress.execute(
@@ -5624,9 +5953,16 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
5624
5953
  }
5625
5954
  );
5626
5955
  } catch (error) {
5956
+ trackServiceInit("email", false, {
5957
+ preset,
5958
+ provider,
5959
+ duration_ms: Date.now() - startTime
5960
+ });
5627
5961
  if (error.message?.includes("stack is currently locked")) {
5962
+ trackError("STACK_LOCKED", "email:init", { step: "deploy" });
5628
5963
  throw errors.stackLocked();
5629
5964
  }
5965
+ trackError("DEPLOYMENT_FAILED", "email:init", { step: "deploy" });
5630
5966
  throw new Error(`Pulumi deployment failed: ${error.message}`);
5631
5967
  }
5632
5968
  if (metadata.services.email) {
@@ -5677,16 +6013,45 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
5677
6013
  domain: outputs.domain,
5678
6014
  mailFromDomain: outputs.mailFromDomain
5679
6015
  });
6016
+ const duration = Date.now() - startTime;
6017
+ const enabledFeatures = [];
6018
+ if (emailConfig.tracking?.enabled) enabledFeatures.push("tracking");
6019
+ if (emailConfig.suppressionList?.enabled)
6020
+ enabledFeatures.push("suppression_list");
6021
+ if (emailConfig.eventTracking?.enabled)
6022
+ enabledFeatures.push("event_tracking");
6023
+ if (emailConfig.eventTracking?.dynamoDBHistory)
6024
+ enabledFeatures.push("dynamodb_history");
6025
+ if (emailConfig.dedicatedIp) enabledFeatures.push("dedicated_ip");
6026
+ if (emailConfig.emailArchiving?.enabled)
6027
+ enabledFeatures.push("email_archiving");
6028
+ trackServiceInit("email", true, {
6029
+ preset,
6030
+ provider,
6031
+ features: enabledFeatures,
6032
+ duration_ms: duration
6033
+ });
6034
+ trackServiceDeployed("email", {
6035
+ duration_ms: duration,
6036
+ features: enabledFeatures,
6037
+ preset
6038
+ });
5680
6039
  }
5681
6040
 
5682
6041
  // src/commands/email/restore.ts
5683
6042
  init_esm_shims();
6043
+ init_events();
5684
6044
  init_aws();
5685
6045
  import * as clack8 from "@clack/prompts";
5686
6046
  import * as pulumi8 from "@pulumi/pulumi";
5687
6047
  import pc9 from "picocolors";
5688
6048
  async function restore(options) {
5689
- clack8.intro(pc9.bold("Wraps Restore - Remove Wraps Infrastructure"));
6049
+ const startTime = Date.now();
6050
+ clack8.intro(
6051
+ pc9.bold(
6052
+ options.preview ? "Wraps Restore Preview" : "Wraps Restore - Remove Wraps Infrastructure"
6053
+ )
6054
+ );
5690
6055
  clack8.log.info(
5691
6056
  `${pc9.yellow("Note:")} This will remove all Wraps-managed infrastructure.`
5692
6057
  );
@@ -5733,7 +6098,7 @@ ${pc9.bold("The following Wraps resources will be removed:")}
5733
6098
  }
5734
6099
  console.log(` ${pc9.cyan("\u2713")} IAM Role (wraps-email-role)`);
5735
6100
  console.log("");
5736
- if (!options.force) {
6101
+ if (!(options.force || options.preview)) {
5737
6102
  const confirmed = await clack8.confirm({
5738
6103
  message: "Proceed with removal? This cannot be undone.",
5739
6104
  initialValue: false
@@ -5743,6 +6108,55 @@ ${pc9.bold("The following Wraps resources will be removed:")}
5743
6108
  process.exit(0);
5744
6109
  }
5745
6110
  }
6111
+ if (options.preview) {
6112
+ if (metadata.services.email?.pulumiStackName) {
6113
+ try {
6114
+ const previewResult = await progress.execute(
6115
+ "Generating removal preview",
6116
+ async () => {
6117
+ const stack = await pulumi8.automation.LocalWorkspace.selectStack(
6118
+ {
6119
+ stackName: metadata.services.email.pulumiStackName,
6120
+ projectName: "wraps-email",
6121
+ program: async () => {
6122
+ }
6123
+ // Empty program for destroy
6124
+ },
6125
+ {
6126
+ workDir: getPulumiWorkDir(),
6127
+ envVars: {
6128
+ PULUMI_CONFIG_PASSPHRASE: "",
6129
+ AWS_REGION: region
6130
+ },
6131
+ secretsProvider: "passphrase"
6132
+ }
6133
+ );
6134
+ const result = await stack.preview({ diff: true });
6135
+ return result;
6136
+ }
6137
+ );
6138
+ displayPreview({
6139
+ changeSummary: previewResult.changeSummary,
6140
+ costEstimate: "Monthly cost after removal: $0.00",
6141
+ commandName: "wraps email restore"
6142
+ });
6143
+ clack8.outro(
6144
+ pc9.green(
6145
+ "Preview complete. Run without --preview to remove infrastructure."
6146
+ )
6147
+ );
6148
+ trackServiceRemoved("email", {
6149
+ preview: true,
6150
+ duration_ms: Date.now() - startTime
6151
+ });
6152
+ return;
6153
+ } catch (error) {
6154
+ trackError("PREVIEW_FAILED", "email:restore", { step: "preview" });
6155
+ throw new Error(`Preview failed: ${error.message}`);
6156
+ }
6157
+ }
6158
+ return;
6159
+ }
5746
6160
  if (metadata.services.email?.pulumiStackName) {
5747
6161
  await progress.execute("Removing Wraps infrastructure", async () => {
5748
6162
  try {
@@ -5772,6 +6186,7 @@ ${pc9.bold("The following Wraps resources will be removed:")}
5772
6186
  metadata.services.email.pulumiStackName
5773
6187
  );
5774
6188
  } catch (error) {
6189
+ trackError("DESTROY_FAILED", "email:restore", { step: "destroy" });
5775
6190
  throw new Error(`Failed to destroy Pulumi stack: ${error.message}`);
5776
6191
  }
5777
6192
  });
@@ -5788,6 +6203,10 @@ ${pc9.green("\u2713")} ${pc9.bold("Infrastructure removed successfully!")}
5788
6203
  );
5789
6204
  console.log(`${pc9.dim("Your original AWS resources remain unchanged.")}
5790
6205
  `);
6206
+ trackServiceRemoved("email", {
6207
+ reason: "user_initiated",
6208
+ duration_ms: Date.now() - startTime
6209
+ });
5791
6210
  }
5792
6211
 
5793
6212
  // src/commands/email/upgrade.ts
@@ -5795,13 +6214,20 @@ init_esm_shims();
5795
6214
  import * as clack9 from "@clack/prompts";
5796
6215
  import * as pulumi9 from "@pulumi/pulumi";
5797
6216
  import pc10 from "picocolors";
6217
+ init_events();
5798
6218
  init_costs();
5799
6219
  init_presets();
5800
6220
  init_aws();
5801
6221
  init_errors();
5802
6222
  init_prompts();
5803
6223
  async function upgrade(options) {
5804
- clack9.intro(pc10.bold("Wraps Upgrade - Enhance Your Email Infrastructure"));
6224
+ const startTime = Date.now();
6225
+ let upgradeAction = "";
6226
+ clack9.intro(
6227
+ pc10.bold(
6228
+ options.preview ? "Wraps Upgrade Preview" : "Wraps Upgrade - Enhance Your Email Infrastructure"
6229
+ )
6230
+ );
5805
6231
  const progress = new DeploymentProgress();
5806
6232
  const wasAutoInstalled = await progress.execute(
5807
6233
  "Checking Pulumi CLI installation",
@@ -5903,7 +6329,7 @@ ${pc10.bold("Current Configuration:")}
5903
6329
  Estimated Cost: ${pc10.cyan(`~${formatCost(currentCostData.total.monthly)}/mo`)}`
5904
6330
  );
5905
6331
  console.log("");
5906
- const upgradeAction = await clack9.select({
6332
+ upgradeAction = await clack9.select({
5907
6333
  message: "What would you like to do?",
5908
6334
  options: [
5909
6335
  {
@@ -6431,7 +6857,7 @@ ${pc10.bold("Cost Impact:")}`);
6431
6857
  );
6432
6858
  }
6433
6859
  console.log("");
6434
- if (!options.yes) {
6860
+ if (!(options.yes || options.preview)) {
6435
6861
  const confirmed = await clack9.confirm({
6436
6862
  message: "Proceed with upgrade?",
6437
6863
  initialValue: true
@@ -6453,6 +6879,81 @@ ${pc10.bold("Cost Impact:")}`);
6453
6879
  vercel: vercelConfig,
6454
6880
  emailConfig: updatedConfig
6455
6881
  };
6882
+ if (options.preview) {
6883
+ try {
6884
+ const previewResult = await progress.execute(
6885
+ "Generating upgrade preview",
6886
+ async () => {
6887
+ await ensurePulumiWorkDir();
6888
+ const stack = await pulumi9.automation.LocalWorkspace.createOrSelectStack(
6889
+ {
6890
+ stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
6891
+ projectName: "wraps-email",
6892
+ program: async () => {
6893
+ const result2 = await deployEmailStack(stackConfig);
6894
+ return {
6895
+ roleArn: result2.roleArn,
6896
+ configSetName: result2.configSetName,
6897
+ tableName: result2.tableName,
6898
+ region: result2.region,
6899
+ lambdaFunctions: result2.lambdaFunctions,
6900
+ domain: result2.domain,
6901
+ dkimTokens: result2.dkimTokens,
6902
+ customTrackingDomain: result2.customTrackingDomain,
6903
+ httpsTrackingEnabled: result2.httpsTrackingEnabled,
6904
+ cloudFrontDomain: result2.cloudFrontDomain,
6905
+ acmCertificateValidationRecords: result2.acmCertificateValidationRecords,
6906
+ archiveArn: result2.archiveArn,
6907
+ archivingEnabled: result2.archivingEnabled,
6908
+ archiveRetention: result2.archiveRetention
6909
+ };
6910
+ }
6911
+ },
6912
+ {
6913
+ workDir: getPulumiWorkDir(),
6914
+ envVars: {
6915
+ PULUMI_CONFIG_PASSPHRASE: "",
6916
+ AWS_REGION: region
6917
+ },
6918
+ secretsProvider: "passphrase"
6919
+ }
6920
+ );
6921
+ await stack.setConfig("aws:region", { value: region });
6922
+ await stack.refresh({ onOutput: () => {
6923
+ } });
6924
+ const result = await stack.preview({ diff: true });
6925
+ return result;
6926
+ }
6927
+ );
6928
+ const costComparison = [
6929
+ `Current: ${formatCost(currentCostData.total.monthly)}/mo`,
6930
+ `After upgrade: ${formatCost(newCostData.total.monthly)}/mo`,
6931
+ costDiff > 0 ? `Change: +${formatCost(costDiff)}/mo` : costDiff < 0 ? `Change: -${formatCost(Math.abs(costDiff))}/mo` : "Change: No cost difference"
6932
+ ].join("\n");
6933
+ displayPreview({
6934
+ changeSummary: previewResult.changeSummary,
6935
+ costEstimate: costComparison,
6936
+ commandName: "wraps email upgrade"
6937
+ });
6938
+ clack9.outro(
6939
+ pc10.green("Preview complete. Run without --preview to upgrade.")
6940
+ );
6941
+ trackServiceUpgrade("email", {
6942
+ from_preset: metadata.services.email?.preset,
6943
+ to_preset: newPreset,
6944
+ preview: true,
6945
+ action: typeof upgradeAction === "string" ? upgradeAction : void 0,
6946
+ duration_ms: Date.now() - startTime
6947
+ });
6948
+ return;
6949
+ } catch (error) {
6950
+ trackError("PREVIEW_FAILED", "email:upgrade", { step: "preview" });
6951
+ if (error.message?.includes("stack is currently locked")) {
6952
+ throw errors.stackLocked();
6953
+ }
6954
+ throw new Error(`Preview failed: ${error.message}`);
6955
+ }
6956
+ }
6456
6957
  let outputs;
6457
6958
  try {
6458
6959
  outputs = await progress.execute(
@@ -6520,9 +7021,17 @@ ${pc10.bold("Cost Impact:")}`);
6520
7021
  }
6521
7022
  );
6522
7023
  } catch (error) {
7024
+ trackServiceUpgrade("email", {
7025
+ from_preset: metadata.services.email?.preset,
7026
+ to_preset: newPreset,
7027
+ action: typeof upgradeAction === "string" ? upgradeAction : void 0,
7028
+ duration_ms: Date.now() - startTime
7029
+ });
6523
7030
  if (error.message?.includes("stack is currently locked")) {
7031
+ trackError("STACK_LOCKED", "email:upgrade", { step: "deploy" });
6524
7032
  throw errors.stackLocked();
6525
7033
  }
7034
+ trackError("UPGRADE_FAILED", "email:upgrade", { step: "deploy" });
6526
7035
  throw new Error(`Pulumi upgrade failed: ${error.message}`);
6527
7036
  }
6528
7037
  if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
@@ -6627,6 +7136,24 @@ ${pc10.green("\u2713")} ${pc10.bold("Upgrade complete!")}
6627
7136
  pc10.green("\u2713") + " " + pc10.bold("HTTPS tracking is fully configured and ready to use!\n")
6628
7137
  );
6629
7138
  }
7139
+ const enabledFeatures = [];
7140
+ if (updatedConfig.tracking?.enabled) enabledFeatures.push("tracking");
7141
+ if (updatedConfig.suppressionList?.enabled)
7142
+ enabledFeatures.push("suppression_list");
7143
+ if (updatedConfig.eventTracking?.enabled)
7144
+ enabledFeatures.push("event_tracking");
7145
+ if (updatedConfig.eventTracking?.dynamoDBHistory)
7146
+ enabledFeatures.push("dynamodb_history");
7147
+ if (updatedConfig.dedicatedIp) enabledFeatures.push("dedicated_ip");
7148
+ if (updatedConfig.emailArchiving?.enabled)
7149
+ enabledFeatures.push("email_archiving");
7150
+ trackServiceUpgrade("email", {
7151
+ from_preset: metadata.services.email?.preset,
7152
+ to_preset: newPreset,
7153
+ added_features: enabledFeatures,
7154
+ action: typeof upgradeAction === "string" ? upgradeAction : void 0,
7155
+ duration_ms: Date.now() - startTime
7156
+ });
6630
7157
  }
6631
7158
 
6632
7159
  // src/commands/shared/dashboard.ts
@@ -7857,6 +8384,7 @@ async function startConsoleServer(config2) {
7857
8384
  }
7858
8385
 
7859
8386
  // src/commands/shared/dashboard.ts
8387
+ init_events();
7860
8388
  init_aws();
7861
8389
  async function dashboard(options) {
7862
8390
  clack10.intro(pc11.bold("Wraps Dashboard"));
@@ -7907,25 +8435,36 @@ async function dashboard(options) {
7907
8435
  if (!options.noOpen) {
7908
8436
  await open(url);
7909
8437
  }
8438
+ trackCommand("console", {
8439
+ success: true,
8440
+ port,
8441
+ no_open: options.noOpen ?? false
8442
+ });
7910
8443
  await new Promise(() => {
7911
8444
  });
7912
8445
  }
7913
8446
 
7914
8447
  // src/commands/shared/destroy.ts
7915
8448
  init_esm_shims();
8449
+ init_events();
7916
8450
  init_aws();
7917
8451
  import * as clack11 from "@clack/prompts";
7918
8452
  import * as pulumi11 from "@pulumi/pulumi";
7919
8453
  import pc12 from "picocolors";
7920
8454
  async function destroy(options) {
7921
- clack11.intro(pc12.bold("Wraps Email Infrastructure Teardown"));
8455
+ const startTime = Date.now();
8456
+ clack11.intro(
8457
+ pc12.bold(
8458
+ options.preview ? "Wraps Destruction Preview" : "Wraps Email Infrastructure Teardown"
8459
+ )
8460
+ );
7922
8461
  const progress = new DeploymentProgress();
7923
8462
  const identity = await progress.execute(
7924
8463
  "Validating AWS credentials",
7925
8464
  async () => validateAWSCredentials()
7926
8465
  );
7927
8466
  const region = await getAWSRegion();
7928
- if (!options.force) {
8467
+ if (!(options.force || options.preview)) {
7929
8468
  const confirmed = await clack11.confirm({
7930
8469
  message: pc12.red(
7931
8470
  "Are you sure you want to destroy all Wraps infrastructure?"
@@ -7937,6 +8476,49 @@ async function destroy(options) {
7937
8476
  process.exit(0);
7938
8477
  }
7939
8478
  }
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
+ });
8511
+ 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
+ }
8521
+ }
7940
8522
  try {
7941
8523
  await progress.execute(
7942
8524
  "Destroying infrastructure (this may take 2-3 minutes)",
@@ -7964,6 +8546,7 @@ async function destroy(options) {
7964
8546
  await deleteConnectionMetadata(identity.accountId, region);
7965
8547
  process.exit(0);
7966
8548
  }
8549
+ trackError("DESTROY_FAILED", "destroy", { step: "destroy" });
7967
8550
  clack11.log.error("Infrastructure destruction failed");
7968
8551
  throw error;
7969
8552
  }
@@ -7975,15 +8558,21 @@ async function destroy(options) {
7975
8558
  Run ${pc12.cyan("wraps email init")} to deploy infrastructure again.
7976
8559
  `
7977
8560
  );
8561
+ trackServiceRemoved("email", {
8562
+ reason: "user_initiated",
8563
+ duration_ms: Date.now() - startTime
8564
+ });
7978
8565
  }
7979
8566
 
7980
8567
  // src/commands/shared/status.ts
7981
8568
  init_esm_shims();
8569
+ init_events();
7982
8570
  init_aws();
7983
8571
  import * as clack12 from "@clack/prompts";
7984
8572
  import * as pulumi12 from "@pulumi/pulumi";
7985
8573
  import pc13 from "picocolors";
7986
8574
  async function status(_options) {
8575
+ const startTime = Date.now();
7987
8576
  const progress = new DeploymentProgress();
7988
8577
  const identity = await progress.execute(
7989
8578
  "Loading infrastructure status",
@@ -8057,6 +8646,12 @@ Run ${pc13.cyan("wraps email init")} to deploy infrastructure.
8057
8646
  cloudFrontDomain: stackOutputs.cloudFrontDomain?.value
8058
8647
  } : void 0
8059
8648
  });
8649
+ trackCommand("status", {
8650
+ success: true,
8651
+ domain_count: domainsWithTokens.length,
8652
+ integration_level: integrationLevel,
8653
+ duration_ms: Date.now() - startTime
8654
+ });
8060
8655
  }
8061
8656
 
8062
8657
  // src/commands/telemetry.ts
@@ -8207,6 +8802,9 @@ function showHelp() {
8207
8802
  console.log(` ${pc15.dim("--preset")} Configuration preset`);
8208
8803
  console.log(` ${pc15.dim("-y, --yes")} Skip confirmation prompts`);
8209
8804
  console.log(` ${pc15.dim("-f, --force")} Force destructive operations`);
8805
+ console.log(
8806
+ ` ${pc15.dim("--preview")} Preview changes without deploying`
8807
+ );
8210
8808
  console.log(` ${pc15.dim("-v, --version")} Show version number
8211
8809
  `);
8212
8810
  console.log(
@@ -8266,6 +8864,11 @@ args.options([
8266
8864
  name: "noOpen",
8267
8865
  description: "Don't open browser automatically",
8268
8866
  defaultValue: false
8867
+ },
8868
+ {
8869
+ name: "preview",
8870
+ description: "Preview changes without deploying",
8871
+ defaultValue: false
8269
8872
  }
8270
8873
  ]);
8271
8874
  var flags = args.parse(process.argv);
@@ -8327,13 +8930,15 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
8327
8930
  region: flags.region,
8328
8931
  domain: flags.domain,
8329
8932
  preset: flags.preset,
8330
- yes: flags.yes
8933
+ yes: flags.yes,
8934
+ preview: flags.preview
8331
8935
  });
8332
8936
  } else {
8333
8937
  await connect({
8334
8938
  provider: flags.provider,
8335
8939
  region: flags.region,
8336
- yes: flags.yes
8940
+ yes: flags.yes,
8941
+ preview: flags.preview
8337
8942
  });
8338
8943
  }
8339
8944
  }
@@ -8371,33 +8976,38 @@ async function run() {
8371
8976
  region: flags.region,
8372
8977
  domain: flags.domain,
8373
8978
  preset: flags.preset,
8374
- yes: flags.yes
8979
+ yes: flags.yes,
8980
+ preview: flags.preview
8375
8981
  });
8376
8982
  break;
8377
8983
  case "connect":
8378
8984
  await connect({
8379
8985
  provider: flags.provider,
8380
8986
  region: flags.region,
8381
- yes: flags.yes
8987
+ yes: flags.yes,
8988
+ preview: flags.preview
8382
8989
  });
8383
8990
  break;
8384
8991
  case "config":
8385
8992
  case "sync":
8386
8993
  await config({
8387
8994
  region: flags.region,
8388
- yes: flags.yes
8995
+ yes: flags.yes,
8996
+ preview: flags.preview
8389
8997
  });
8390
8998
  break;
8391
8999
  case "upgrade":
8392
9000
  await upgrade({
8393
9001
  region: flags.region,
8394
- yes: flags.yes
9002
+ yes: flags.yes,
9003
+ preview: flags.preview
8395
9004
  });
8396
9005
  break;
8397
9006
  case "restore":
8398
9007
  await restore({
8399
9008
  region: flags.region,
8400
- force: flags.force
9009
+ force: flags.force,
9010
+ preview: flags.preview
8401
9011
  });
8402
9012
  break;
8403
9013
  case "domains": {
@@ -8483,6 +9093,13 @@ Run ${pc15.cyan("wraps --help")} for available commands.
8483
9093
  );
8484
9094
  process.exit(1);
8485
9095
  }
9096
+ const emailDuration = Date.now() - startTime;
9097
+ const emailCommandName = `email:${subCommand}`;
9098
+ trackCommand(emailCommandName, {
9099
+ success: true,
9100
+ duration_ms: emailDuration,
9101
+ service: "email"
9102
+ });
8486
9103
  return;
8487
9104
  }
8488
9105
  if (primaryCommand === "dashboard" && subCommand) {
@@ -8502,6 +9119,12 @@ Available commands: ${pc15.cyan("update-role")}
8502
9119
  `);
8503
9120
  process.exit(1);
8504
9121
  }
9122
+ const dashboardDuration = Date.now() - startTime;
9123
+ const dashboardCommandName = `dashboard:${subCommand}`;
9124
+ trackCommand(dashboardCommandName, {
9125
+ success: true,
9126
+ duration_ms: dashboardDuration
9127
+ });
8505
9128
  return;
8506
9129
  }
8507
9130
  if (primaryCommand === "sms" && subCommand) {
@@ -8539,7 +9162,8 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
8539
9162
  break;
8540
9163
  case "destroy":
8541
9164
  await destroy({
8542
- force: flags.force
9165
+ force: flags.force,
9166
+ preview: flags.preview
8543
9167
  });
8544
9168
  break;
8545
9169
  case "completion":