@wraps.dev/cli 2.11.5 → 2.11.7

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
@@ -1133,9 +1133,11 @@ var init_aws_detection = __esm({
1133
1133
  var errors_exports = {};
1134
1134
  __export(errors_exports, {
1135
1135
  WrapsError: () => WrapsError,
1136
+ classifyDNSError: () => classifyDNSError,
1136
1137
  errors: () => errors,
1137
1138
  handleCLIError: () => handleCLIError,
1138
1139
  isAWSError: () => isAWSError,
1140
+ isAWSNotFoundError: () => isAWSNotFoundError,
1139
1141
  isPulumiError: () => isPulumiError,
1140
1142
  parseAWSError: () => parseAWSError,
1141
1143
  parsePulumiError: () => parsePulumiError,
@@ -1162,6 +1164,23 @@ function isAWSError(error) {
1162
1164
  ];
1163
1165
  return awsErrorNames.includes(error.name) || "$metadata" in error;
1164
1166
  }
1167
+ function classifyDNSError(error) {
1168
+ if (!(error instanceof Error)) {
1169
+ return "unknown";
1170
+ }
1171
+ const code = error.code;
1172
+ if (code === "ENOTFOUND" || code === "ENODATA") {
1173
+ return "missing";
1174
+ }
1175
+ if (code === "ETIMEOUT" || code === "ESERVFAIL" || code === "ECONNREFUSED") {
1176
+ return "network";
1177
+ }
1178
+ return "unknown";
1179
+ }
1180
+ function isAWSNotFoundError(error) {
1181
+ if (!(error instanceof Error)) return false;
1182
+ return error.name === "NotFoundException" || error.name === "NoSuchEntityException" || error.name === "NoSuchEntity" || error.name === "ResourceNotFoundException" || error.$metadata?.httpStatusCode === 404;
1183
+ }
1165
1184
  function isPulumiError(error) {
1166
1185
  if (!(error instanceof Error)) {
1167
1186
  return false;
@@ -4444,6 +4463,100 @@ var init_metadata = __esm({
4444
4463
  }
4445
4464
  });
4446
4465
 
4466
+ // src/infrastructure/shared/resource-checks.ts
4467
+ async function roleExists(roleName) {
4468
+ try {
4469
+ const { IAMClient: IAMClient4, GetRoleCommand: GetRoleCommand3 } = await import("@aws-sdk/client-iam");
4470
+ const iam10 = new IAMClient4({
4471
+ region: process.env.AWS_REGION || "us-east-1"
4472
+ });
4473
+ await iam10.send(new GetRoleCommand3({ RoleName: roleName }));
4474
+ return true;
4475
+ } catch (error) {
4476
+ if (error instanceof Error && (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity")) {
4477
+ return false;
4478
+ }
4479
+ console.error("Error checking for existing IAM role:", error);
4480
+ return false;
4481
+ }
4482
+ }
4483
+ async function tableExists(tableName) {
4484
+ try {
4485
+ const { DynamoDBClient: DynamoDBClient6, DescribeTableCommand: DescribeTableCommand2 } = await import("@aws-sdk/client-dynamodb");
4486
+ const dynamodb3 = new DynamoDBClient6({
4487
+ region: process.env.AWS_REGION || "us-east-1"
4488
+ });
4489
+ await dynamodb3.send(new DescribeTableCommand2({ TableName: tableName }));
4490
+ return true;
4491
+ } catch (error) {
4492
+ if (error instanceof Error && error.name === "ResourceNotFoundException") {
4493
+ return false;
4494
+ }
4495
+ console.error("Error checking for existing DynamoDB table:", error);
4496
+ return false;
4497
+ }
4498
+ }
4499
+ async function sqsQueueExists(queueName) {
4500
+ try {
4501
+ const { SQSClient, GetQueueUrlCommand } = await import("@aws-sdk/client-sqs");
4502
+ const sqs5 = new SQSClient({
4503
+ region: process.env.AWS_REGION || "us-east-1"
4504
+ });
4505
+ const response = await sqs5.send(
4506
+ new GetQueueUrlCommand({ QueueName: queueName })
4507
+ );
4508
+ return response.QueueUrl || null;
4509
+ } catch {
4510
+ return null;
4511
+ }
4512
+ }
4513
+ async function snsTopicExists(topicName) {
4514
+ try {
4515
+ const { SNSClient: SNSClient2, ListTopicsCommand: ListTopicsCommand2 } = await import("@aws-sdk/client-sns");
4516
+ const sns3 = new SNSClient2({
4517
+ region: process.env.AWS_REGION || "us-east-1"
4518
+ });
4519
+ let nextToken;
4520
+ do {
4521
+ const response = await sns3.send(
4522
+ new ListTopicsCommand2({ NextToken: nextToken })
4523
+ );
4524
+ const found = response.Topics?.find(
4525
+ (t) => t.TopicArn?.endsWith(`:${topicName}`)
4526
+ );
4527
+ if (found?.TopicArn) {
4528
+ return found.TopicArn;
4529
+ }
4530
+ nextToken = response.NextToken;
4531
+ } while (nextToken);
4532
+ return null;
4533
+ } catch {
4534
+ return null;
4535
+ }
4536
+ }
4537
+ async function lambdaFunctionExists(functionName) {
4538
+ try {
4539
+ const { LambdaClient: LambdaClient2, GetFunctionCommand } = await import("@aws-sdk/client-lambda");
4540
+ const lambda4 = new LambdaClient2({
4541
+ region: process.env.AWS_REGION || "us-east-1"
4542
+ });
4543
+ await lambda4.send(new GetFunctionCommand({ FunctionName: functionName }));
4544
+ return true;
4545
+ } catch (error) {
4546
+ if (error instanceof Error && error.name === "ResourceNotFoundException") {
4547
+ return false;
4548
+ }
4549
+ console.error("Error checking for existing Lambda function:", error);
4550
+ return false;
4551
+ }
4552
+ }
4553
+ var init_resource_checks = __esm({
4554
+ "src/infrastructure/shared/resource-checks.ts"() {
4555
+ "use strict";
4556
+ init_esm_shims();
4557
+ }
4558
+ });
4559
+
4447
4560
  // ../core/dist/index.js
4448
4561
  var dist_exports = {};
4449
4562
  __export(dist_exports, {
@@ -4661,22 +4774,6 @@ function getPackageRoot() {
4661
4774
  }
4662
4775
  throw new Error("Could not find package.json");
4663
4776
  }
4664
- async function lambdaFunctionExists(functionName) {
4665
- try {
4666
- const { LambdaClient: LambdaClient2, GetFunctionCommand } = await import("@aws-sdk/client-lambda");
4667
- const lambda4 = new LambdaClient2({
4668
- region: process.env.AWS_REGION || "us-east-1"
4669
- });
4670
- await lambda4.send(new GetFunctionCommand({ FunctionName: functionName }));
4671
- return true;
4672
- } catch (error) {
4673
- if (error.name === "ResourceNotFoundException") {
4674
- return false;
4675
- }
4676
- console.error("Error checking for existing Lambda function:", error);
4677
- return false;
4678
- }
4679
- }
4680
4777
  async function findEventSourceMapping(functionName, queueArn) {
4681
4778
  try {
4682
4779
  const { LambdaClient: LambdaClient2, ListEventSourceMappingsCommand } = await import("@aws-sdk/client-lambda");
@@ -4873,6 +4970,7 @@ var init_lambda = __esm({
4873
4970
  "src/infrastructure/resources/lambda.ts"() {
4874
4971
  "use strict";
4875
4972
  init_esm_shims();
4973
+ init_resource_checks();
4876
4974
  nodeBuiltins = builtinModules.flatMap((m) => [m, `node:${m}`]);
4877
4975
  }
4878
4976
  });
@@ -9146,12 +9244,13 @@ async function cdnDestroy(options) {
9146
9244
  return;
9147
9245
  } catch (error) {
9148
9246
  progress.stop();
9149
- if (error.message.includes("No CDN infrastructure found")) {
9247
+ const msg = error instanceof Error ? error.message : String(error);
9248
+ if (msg.includes("No CDN infrastructure found")) {
9150
9249
  clack9.log.warn("No CDN infrastructure found to preview");
9151
9250
  process.exit(0);
9152
9251
  }
9153
9252
  trackError("PREVIEW_FAILED", "storage destroy", { step: "preview" });
9154
- throw new Error(`Preview failed: ${error.message}`);
9253
+ throw new Error(`Preview failed: ${msg}`);
9155
9254
  }
9156
9255
  }
9157
9256
  if (shouldCleanDNS && hostedZone && customDomain) {
@@ -9163,7 +9262,8 @@ async function cdnDestroy(options) {
9163
9262
  }
9164
9263
  );
9165
9264
  } catch (error) {
9166
- clack9.log.warn(`Could not delete DNS records: ${error.message}`);
9265
+ const msg = error instanceof Error ? error.message : String(error);
9266
+ clack9.log.warn(`Could not delete DNS records: ${msg}`);
9167
9267
  clack9.log.info("You may need to delete them manually from Route53");
9168
9268
  }
9169
9269
  }
@@ -9176,7 +9276,8 @@ async function cdnDestroy(options) {
9176
9276
  }
9177
9277
  );
9178
9278
  } catch (error) {
9179
- clack9.log.info(`Note: ${error.message}`);
9279
+ const msg = error instanceof Error ? error.message : String(error);
9280
+ clack9.log.info(`Note: ${msg}`);
9180
9281
  }
9181
9282
  try {
9182
9283
  await progress.execute(
@@ -9200,7 +9301,8 @@ async function cdnDestroy(options) {
9200
9301
  );
9201
9302
  } catch (error) {
9202
9303
  progress.stop();
9203
- if (error.message.includes("No CDN infrastructure found")) {
9304
+ const msg = error instanceof Error ? error.message : String(error);
9305
+ if (msg.includes("No CDN infrastructure found")) {
9204
9306
  clack9.log.warn("No CDN infrastructure found");
9205
9307
  if (metadata) {
9206
9308
  removeServiceFromConnection(metadata, "cdn");
@@ -9208,7 +9310,7 @@ async function cdnDestroy(options) {
9208
9310
  }
9209
9311
  process.exit(0);
9210
9312
  }
9211
- if (error.message?.includes("stack is currently locked")) {
9313
+ if (msg.includes("stack is currently locked")) {
9212
9314
  trackError("STACK_LOCKED", "storage destroy", { step: "destroy" });
9213
9315
  throw errors.stackLocked();
9214
9316
  }
@@ -9647,6 +9749,9 @@ async function createCdnACMCertificate(config2) {
9647
9749
  };
9648
9750
  }
9649
9751
 
9752
+ // src/infrastructure/cdn-stack.ts
9753
+ init_resource_checks();
9754
+
9650
9755
  // src/infrastructure/vercel-oidc.ts
9651
9756
  init_esm_shims();
9652
9757
  import * as aws2 from "@pulumi/aws";
@@ -9708,22 +9813,6 @@ async function createVercelOIDC(config2) {
9708
9813
  }
9709
9814
 
9710
9815
  // src/infrastructure/cdn-stack.ts
9711
- async function roleExists(roleName) {
9712
- try {
9713
- const { IAMClient: IAMClient4, GetRoleCommand: GetRoleCommand3 } = await import("@aws-sdk/client-iam");
9714
- const iam10 = new IAMClient4({
9715
- region: process.env.AWS_REGION || "us-east-1"
9716
- });
9717
- await iam10.send(new GetRoleCommand3({ RoleName: roleName }));
9718
- return true;
9719
- } catch (error) {
9720
- if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity") {
9721
- return false;
9722
- }
9723
- console.error("Error checking for existing IAM role:", error);
9724
- return false;
9725
- }
9726
- }
9727
9816
  async function createCdnIAMRole(config2) {
9728
9817
  let assumeRolePolicy;
9729
9818
  if (config2.provider === "vercel" && config2.oidcProvider) {
@@ -10492,10 +10581,11 @@ ${pc11.yellow(pc11.bold("Configuration Notes:"))}`);
10492
10581
  return;
10493
10582
  } catch (error) {
10494
10583
  trackError("PREVIEW_FAILED", "storage:init", { step: "preview" });
10495
- if (error.message?.includes("stack is currently locked")) {
10584
+ const msg = error instanceof Error ? error.message : String(error);
10585
+ if (msg.includes("stack is currently locked")) {
10496
10586
  throw errors.stackLocked();
10497
10587
  }
10498
- throw new Error(`Preview failed: ${error.message}`);
10588
+ throw new Error(`Preview failed: ${msg}`);
10499
10589
  }
10500
10590
  }
10501
10591
  let outputs;
@@ -10559,18 +10649,19 @@ ${pc11.yellow(pc11.bold("Configuration Notes:"))}`);
10559
10649
  }
10560
10650
  );
10561
10651
  } catch (error) {
10652
+ const msg = error instanceof Error ? error.message : String(error);
10562
10653
  trackServiceInit("cdn", false, {
10563
10654
  preset,
10564
10655
  provider,
10565
10656
  region,
10566
10657
  duration_ms: Date.now() - startTime
10567
10658
  });
10568
- if (error.message?.includes("stack is currently locked")) {
10659
+ if (msg.includes("stack is currently locked")) {
10569
10660
  trackError("STACK_LOCKED", "storage:init", { step: "deploy" });
10570
10661
  throw errors.stackLocked();
10571
10662
  }
10572
10663
  trackError("DEPLOYMENT_FAILED", "storage:init", { step: "deploy" });
10573
- throw new Error(`Pulumi deployment failed: ${error.message}`);
10664
+ throw new Error(`Pulumi deployment failed: ${msg}`);
10574
10665
  }
10575
10666
  if (metadata.services.cdn) {
10576
10667
  metadata.services.cdn.pulumiStackName = `wraps-cdn-${identity.accountId}-${region}`;
@@ -10703,7 +10794,8 @@ ${pc11.yellow(pc11.bold("Configuration Notes:"))}`);
10703
10794
  }
10704
10795
  } catch (error) {
10705
10796
  progress.stop();
10706
- clack10.log.warn(`Could not manage DNS records: ${error.message}`);
10797
+ const msg = error instanceof Error ? error.message : String(error);
10798
+ clack10.log.warn(`Could not manage DNS records: ${msg}`);
10707
10799
  }
10708
10800
  }
10709
10801
  }
@@ -11127,13 +11219,14 @@ async function cdnSync(options) {
11127
11219
  return result.summary;
11128
11220
  });
11129
11221
  } catch (error) {
11222
+ const msg = error instanceof Error ? error.message : String(error);
11130
11223
  trackCommand("storage:sync", {
11131
11224
  success: false,
11132
- error: error.message,
11225
+ error: msg,
11133
11226
  region,
11134
11227
  duration_ms: Date.now() - startTime
11135
11228
  });
11136
- clack12.log.error(`Sync failed: ${error.message}`);
11229
+ clack12.log.error(`Sync failed: ${msg}`);
11137
11230
  process.exit(1);
11138
11231
  }
11139
11232
  clack12.log.success(pc13.green("CDN infrastructure synced!"));
@@ -11257,7 +11350,8 @@ Current configuration:
11257
11350
  certStatus = certResponse.Certificate?.Status || "UNKNOWN";
11258
11351
  } catch (error) {
11259
11352
  progress.fail("Failed to check certificate status");
11260
- clack13.log.error(`Error: ${error.message}`);
11353
+ const msg = error instanceof Error ? error.message : String(error);
11354
+ clack13.log.error(`Error: ${msg}`);
11261
11355
  process.exit(1);
11262
11356
  }
11263
11357
  progress.stop();
@@ -11381,13 +11475,14 @@ Ready to add custom domain: ${pc14.cyan(pendingDomain)}`);
11381
11475
  }
11382
11476
  );
11383
11477
  } catch (error) {
11478
+ const msg = error instanceof Error ? error.message : String(error);
11384
11479
  trackCommand("storage:upgrade", {
11385
11480
  success: false,
11386
- error: error.message,
11481
+ error: msg,
11387
11482
  region,
11388
11483
  duration_ms: Date.now() - startTime
11389
11484
  });
11390
- clack13.log.error(`Upgrade failed: ${error.message}`);
11485
+ clack13.log.error(`Upgrade failed: ${msg}`);
11391
11486
  process.exit(1);
11392
11487
  }
11393
11488
  if (metadata.services.cdn) {
@@ -14236,16 +14331,17 @@ async function check(options) {
14236
14331
  process.exit(getExitCode(result.score.grade));
14237
14332
  } catch (error) {
14238
14333
  spinner8?.stop("Check failed");
14334
+ const msg = error instanceof Error ? error.message : String(error);
14239
14335
  if (options.json) {
14240
- console.log(JSON.stringify({ error: error.message }));
14336
+ console.log(JSON.stringify({ error: msg }));
14241
14337
  } else {
14242
- clack15.log.error(error.message);
14338
+ clack15.log.error(msg);
14243
14339
  }
14244
14340
  const duration = Date.now() - startTime;
14245
14341
  trackCommand("email:check", {
14246
14342
  success: false,
14247
14343
  duration_ms: duration,
14248
- error: error.message
14344
+ error: msg
14249
14345
  });
14250
14346
  process.exit(4);
14251
14347
  }
@@ -14855,23 +14951,8 @@ async function createAlertingResources(config2) {
14855
14951
 
14856
14952
  // src/infrastructure/resources/dynamodb.ts
14857
14953
  init_esm_shims();
14954
+ init_resource_checks();
14858
14955
  import * as aws5 from "@pulumi/aws";
14859
- async function tableExists(tableName) {
14860
- try {
14861
- const { DynamoDBClient: DynamoDBClient6, DescribeTableCommand: DescribeTableCommand2 } = await import("@aws-sdk/client-dynamodb");
14862
- const dynamodb3 = new DynamoDBClient6({
14863
- region: process.env.AWS_REGION || "us-east-1"
14864
- });
14865
- await dynamodb3.send(new DescribeTableCommand2({ TableName: tableName }));
14866
- return true;
14867
- } catch (error) {
14868
- if (error.name === "ResourceNotFoundException") {
14869
- return false;
14870
- }
14871
- console.error("Error checking for existing DynamoDB table:", error);
14872
- return false;
14873
- }
14874
- }
14875
14956
  async function createDynamoDBTables(_config) {
14876
14957
  const tableName = "wraps-email-history";
14877
14958
  const exists = await tableExists(tableName);
@@ -15068,24 +15149,9 @@ async function createEventBridgeResources(config2) {
15068
15149
 
15069
15150
  // src/infrastructure/resources/iam.ts
15070
15151
  init_esm_shims();
15152
+ init_resource_checks();
15071
15153
  import * as aws7 from "@pulumi/aws";
15072
15154
  import * as pulumi10 from "@pulumi/pulumi";
15073
- async function roleExists2(roleName) {
15074
- try {
15075
- const { IAMClient: IAMClient4, GetRoleCommand: GetRoleCommand3 } = await import("@aws-sdk/client-iam");
15076
- const iam10 = new IAMClient4({
15077
- region: process.env.AWS_REGION || "us-east-1"
15078
- });
15079
- await iam10.send(new GetRoleCommand3({ RoleName: roleName }));
15080
- return true;
15081
- } catch (error) {
15082
- if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity") {
15083
- return false;
15084
- }
15085
- console.error("Error checking for existing IAM role:", error);
15086
- return false;
15087
- }
15088
- }
15089
15155
  async function createIAMRole(config2) {
15090
15156
  let assumeRolePolicy;
15091
15157
  if (config2.provider === "vercel" && config2.oidcProvider) {
@@ -15122,7 +15188,7 @@ async function createIAMRole(config2) {
15122
15188
  throw new Error("Other providers not yet implemented");
15123
15189
  }
15124
15190
  const roleName = "wraps-email-role";
15125
- const exists = await roleExists2(roleName);
15191
+ const exists = await roleExists(roleName);
15126
15192
  const role = exists ? new aws7.iam.Role(
15127
15193
  roleName,
15128
15194
  {
@@ -15967,10 +16033,11 @@ ${pc17.bold("Current Configuration:")}
15967
16033
  return;
15968
16034
  } catch (error) {
15969
16035
  trackError("PREVIEW_FAILED", "email:config", { step: "preview" });
15970
- if (error.message?.includes("stack is currently locked")) {
16036
+ const msg = error instanceof Error ? error.message : String(error);
16037
+ if (msg.includes("stack is currently locked")) {
15971
16038
  throw errors.stackLocked();
15972
16039
  }
15973
- throw new Error(`Preview failed: ${error.message}`);
16040
+ throw new Error(`Preview failed: ${msg}`);
15974
16041
  }
15975
16042
  }
15976
16043
  let outputs;
@@ -16028,16 +16095,17 @@ ${pc17.bold("Current Configuration:")}
16028
16095
  }
16029
16096
  );
16030
16097
  } catch (error) {
16098
+ const msg = error instanceof Error ? error.message : String(error);
16031
16099
  trackCommand("email:config", {
16032
16100
  success: false,
16033
16101
  duration_ms: Date.now() - startTime
16034
16102
  });
16035
- if (error.message?.includes("stack is currently locked")) {
16103
+ if (msg.includes("stack is currently locked")) {
16036
16104
  trackError("STACK_LOCKED", "email:config", { step: "update" });
16037
16105
  throw errors.stackLocked();
16038
16106
  }
16039
16107
  trackError("UPDATE_FAILED", "email:config", { step: "update" });
16040
- throw new Error(`Pulumi update failed: ${error.message}`);
16108
+ throw new Error(`Pulumi update failed: ${msg}`);
16041
16109
  }
16042
16110
  metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
16043
16111
  await saveConnectionMetadata(metadata);
@@ -16468,10 +16536,11 @@ async function connect2(options) {
16468
16536
  return;
16469
16537
  } catch (error) {
16470
16538
  trackError("PREVIEW_FAILED", "email:connect", { step: "preview" });
16471
- if (error.message?.includes("stack is currently locked")) {
16539
+ const msg = error instanceof Error ? error.message : String(error);
16540
+ if (msg.includes("stack is currently locked")) {
16472
16541
  throw errors.stackLocked();
16473
16542
  }
16474
- throw new Error(`Preview failed: ${error.message}`);
16543
+ throw new Error(`Preview failed: ${msg}`);
16475
16544
  }
16476
16545
  }
16477
16546
  let outputs;
@@ -16527,17 +16596,18 @@ async function connect2(options) {
16527
16596
  }
16528
16597
  );
16529
16598
  } catch (error) {
16599
+ const msg = error instanceof Error ? error.message : String(error);
16530
16600
  trackServiceInit("email", false, {
16531
16601
  preset,
16532
16602
  provider,
16533
16603
  duration_ms: Date.now() - startTime
16534
16604
  });
16535
- if (error.message?.includes("stack is currently locked")) {
16605
+ if (msg.includes("stack is currently locked")) {
16536
16606
  trackError("STACK_LOCKED", "email:connect", { step: "deploy" });
16537
16607
  throw errors.stackLocked();
16538
16608
  }
16539
16609
  trackError("DEPLOYMENT_FAILED", "email:connect", { step: "deploy" });
16540
- throw new Error(`Pulumi deployment failed: ${error.message}`);
16610
+ throw new Error(`Pulumi deployment failed: ${msg}`);
16541
16611
  }
16542
16612
  if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
16543
16613
  const { findHostedZone: findHostedZone2, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
@@ -16556,9 +16626,8 @@ async function connect2(options) {
16556
16626
  );
16557
16627
  progress.succeed("DNS records created in Route53");
16558
16628
  } catch (error) {
16559
- progress.fail(
16560
- `Failed to create DNS records automatically: ${error.message}`
16561
- );
16629
+ const msg = error instanceof Error ? error.message : String(error);
16630
+ progress.fail(`Failed to create DNS records automatically: ${msg}`);
16562
16631
  progress.info(
16563
16632
  "You can manually add the required DNS records shown below"
16564
16633
  );
@@ -16687,8 +16756,11 @@ async function getEmailIdentityInfo(domain, region) {
16687
16756
  dkimTokens: response.DkimAttributes?.Tokens || [],
16688
16757
  mailFromDomain: response.MailFromAttributes?.MailFromDomain
16689
16758
  };
16690
- } catch (_error) {
16691
- return { dkimTokens: [] };
16759
+ } catch (error) {
16760
+ if (isAWSError(error)) {
16761
+ return { dkimTokens: [] };
16762
+ }
16763
+ throw error;
16692
16764
  }
16693
16765
  }
16694
16766
  async function emailDestroy(options) {
@@ -16783,7 +16855,7 @@ async function emailDestroy(options) {
16783
16855
  stackName,
16784
16856
  workDir: getPulumiWorkDir()
16785
16857
  });
16786
- } catch (_error) {
16858
+ } catch {
16787
16859
  throw new Error("No email infrastructure found to preview");
16788
16860
  }
16789
16861
  const result = await previewWithResourceChanges(stack, {
@@ -16817,12 +16889,13 @@ async function emailDestroy(options) {
16817
16889
  return;
16818
16890
  } catch (error) {
16819
16891
  progress.stop();
16820
- if (error.message.includes("No email infrastructure found")) {
16892
+ const msg = error instanceof Error ? error.message : String(error);
16893
+ if (msg.includes("No email infrastructure found")) {
16821
16894
  clack18.log.warn("No email infrastructure found to preview");
16822
16895
  process.exit(0);
16823
16896
  }
16824
16897
  trackError("PREVIEW_FAILED", "email destroy", { step: "preview" });
16825
- throw new Error(`Preview failed: ${error.message}`);
16898
+ throw new Error(`Preview failed: ${msg}`);
16826
16899
  }
16827
16900
  }
16828
16901
  if (shouldCleanDNS && hostedZone && domain && dkimTokens.length > 0) {
@@ -16838,7 +16911,8 @@ async function emailDestroy(options) {
16838
16911
  );
16839
16912
  });
16840
16913
  } catch (error) {
16841
- clack18.log.warn(`Could not delete DNS records: ${error.message}`);
16914
+ const msg = error instanceof Error ? error.message : String(error);
16915
+ clack18.log.warn(`Could not delete DNS records: ${msg}`);
16842
16916
  clack18.log.info("You may need to delete them manually from Route53");
16843
16917
  }
16844
16918
  }
@@ -16854,7 +16928,7 @@ async function emailDestroy(options) {
16854
16928
  stackName,
16855
16929
  workDir: getPulumiWorkDir()
16856
16930
  });
16857
- } catch (_error) {
16931
+ } catch {
16858
16932
  throw new Error("No email infrastructure found to destroy");
16859
16933
  }
16860
16934
  await withTimeout(
@@ -16869,12 +16943,13 @@ async function emailDestroy(options) {
16869
16943
  );
16870
16944
  } catch (error) {
16871
16945
  progress.stop();
16872
- if (error.message.includes("No email infrastructure found")) {
16946
+ const msg = error instanceof Error ? error.message : String(error);
16947
+ if (msg.includes("No email infrastructure found")) {
16873
16948
  clack18.log.warn("No email infrastructure found");
16874
16949
  await deleteConnectionMetadata(identity.accountId, region);
16875
16950
  process.exit(0);
16876
16951
  }
16877
- if (error.message?.includes("stack is currently locked")) {
16952
+ if (msg.includes("stack is currently locked")) {
16878
16953
  trackError("STACK_LOCKED", "email destroy", { step: "destroy" });
16879
16954
  throw errors.stackLocked();
16880
16955
  }
@@ -16919,6 +16994,7 @@ init_client();
16919
16994
  init_events();
16920
16995
  init_dns();
16921
16996
  init_aws();
16997
+ init_errors();
16922
16998
  init_metadata();
16923
16999
  import { Resolver as Resolver2 } from "dns/promises";
16924
17000
  import { GetEmailIdentityCommand as GetEmailIdentityCommand2, SESv2Client as SESv2Client3 } from "@aws-sdk/client-sesv2";
@@ -16945,16 +17021,19 @@ async function verifyDomain(options) {
16945
17021
  );
16946
17022
  dkimTokens = identity.DkimAttributes?.Tokens || [];
16947
17023
  mailFromDomain = identity.MailFromAttributes?.MailFromDomain;
16948
- } catch (_error) {
16949
- progress.stop();
16950
- clack19.log.error(`Domain ${options.domain} not found in SES`);
16951
- console.log(
16952
- `
17024
+ } catch (error) {
17025
+ if (isAWSNotFoundError(error)) {
17026
+ progress.stop();
17027
+ clack19.log.error(`Domain ${options.domain} not found in SES`);
17028
+ console.log(
17029
+ `
16953
17030
  Run ${pc20.cyan(`wraps email init --domain ${options.domain}`)} to add this domain.
16954
17031
  `
16955
- );
16956
- process.exit(1);
16957
- return;
17032
+ );
17033
+ process.exit(1);
17034
+ return;
17035
+ }
17036
+ throw error;
16958
17037
  }
16959
17038
  const resolver = new Resolver2();
16960
17039
  resolver.setServers(["8.8.8.8", "1.1.1.1"]);
@@ -16971,12 +17050,24 @@ Run ${pc20.cyan(`wraps email init --domain ${options.domain}`)} to add this doma
16971
17050
  status: found ? "verified" : "incorrect",
16972
17051
  records
16973
17052
  });
16974
- } catch (_error) {
16975
- dnsResults.push({
16976
- name: dkimRecord,
16977
- type: "CNAME",
16978
- status: "missing"
16979
- });
17053
+ } catch (error) {
17054
+ const dnsClass = classifyDNSError(error);
17055
+ if (dnsClass === "missing") {
17056
+ dnsResults.push({
17057
+ name: dkimRecord,
17058
+ type: "CNAME",
17059
+ status: "missing"
17060
+ });
17061
+ } else if (dnsClass === "network") {
17062
+ dnsResults.push({
17063
+ name: dkimRecord,
17064
+ type: "CNAME",
17065
+ status: "missing",
17066
+ records: ["DNS lookup failed (network issue)"]
17067
+ });
17068
+ } else {
17069
+ throw error;
17070
+ }
16980
17071
  }
16981
17072
  }
16982
17073
  try {
@@ -16989,12 +17080,24 @@ Run ${pc20.cyan(`wraps email init --domain ${options.domain}`)} to add this doma
16989
17080
  status: hasAmazonSES ? "verified" : spfRecord ? "incorrect" : "missing",
16990
17081
  records: spfRecord ? [spfRecord] : void 0
16991
17082
  });
16992
- } catch (_error) {
16993
- dnsResults.push({
16994
- name: options.domain,
16995
- type: "TXT (SPF)",
16996
- status: "missing"
16997
- });
17083
+ } catch (error) {
17084
+ const dnsClass = classifyDNSError(error);
17085
+ if (dnsClass === "missing") {
17086
+ dnsResults.push({
17087
+ name: options.domain,
17088
+ type: "TXT (SPF)",
17089
+ status: "missing"
17090
+ });
17091
+ } else if (dnsClass === "network") {
17092
+ dnsResults.push({
17093
+ name: options.domain,
17094
+ type: "TXT (SPF)",
17095
+ status: "missing",
17096
+ records: ["DNS lookup failed (network issue)"]
17097
+ });
17098
+ } else {
17099
+ throw error;
17100
+ }
16998
17101
  }
16999
17102
  try {
17000
17103
  const records = await resolver.resolveTxt(`_dmarc.${options.domain}`);
@@ -17005,12 +17108,24 @@ Run ${pc20.cyan(`wraps email init --domain ${options.domain}`)} to add this doma
17005
17108
  status: dmarcRecord ? "verified" : "missing",
17006
17109
  records: dmarcRecord ? [dmarcRecord] : void 0
17007
17110
  });
17008
- } catch (_error) {
17009
- dnsResults.push({
17010
- name: `_dmarc.${options.domain}`,
17011
- type: "TXT (DMARC)",
17012
- status: "missing"
17013
- });
17111
+ } catch (error) {
17112
+ const dnsClass = classifyDNSError(error);
17113
+ if (dnsClass === "missing") {
17114
+ dnsResults.push({
17115
+ name: `_dmarc.${options.domain}`,
17116
+ type: "TXT (DMARC)",
17117
+ status: "missing"
17118
+ });
17119
+ } else if (dnsClass === "network") {
17120
+ dnsResults.push({
17121
+ name: `_dmarc.${options.domain}`,
17122
+ type: "TXT (DMARC)",
17123
+ status: "missing",
17124
+ records: ["DNS lookup failed (network issue)"]
17125
+ });
17126
+ } else {
17127
+ throw error;
17128
+ }
17014
17129
  }
17015
17130
  if (mailFromDomain) {
17016
17131
  try {
@@ -17025,12 +17140,24 @@ Run ${pc20.cyan(`wraps email init --domain ${options.domain}`)} to add this doma
17025
17140
  status: hasMx ? "verified" : mxRecords.length > 0 ? "incorrect" : "missing",
17026
17141
  records: mxRecords.map((r) => `${r.priority} ${r.exchange}`)
17027
17142
  });
17028
- } catch (_error) {
17029
- dnsResults.push({
17030
- name: mailFromDomain,
17031
- type: "MX",
17032
- status: "missing"
17033
- });
17143
+ } catch (error) {
17144
+ const dnsClass = classifyDNSError(error);
17145
+ if (dnsClass === "missing") {
17146
+ dnsResults.push({
17147
+ name: mailFromDomain,
17148
+ type: "MX",
17149
+ status: "missing"
17150
+ });
17151
+ } else if (dnsClass === "network") {
17152
+ dnsResults.push({
17153
+ name: mailFromDomain,
17154
+ type: "MX",
17155
+ status: "missing",
17156
+ records: ["DNS lookup failed (network issue)"]
17157
+ });
17158
+ } else {
17159
+ throw error;
17160
+ }
17034
17161
  }
17035
17162
  try {
17036
17163
  const records = await resolver.resolveTxt(mailFromDomain);
@@ -17042,12 +17169,24 @@ Run ${pc20.cyan(`wraps email init --domain ${options.domain}`)} to add this doma
17042
17169
  status: hasAmazonSES ? "verified" : spfRecord ? "incorrect" : "missing",
17043
17170
  records: spfRecord ? [spfRecord] : void 0
17044
17171
  });
17045
- } catch (_error) {
17046
- dnsResults.push({
17047
- name: mailFromDomain,
17048
- type: "TXT (SPF)",
17049
- status: "missing"
17050
- });
17172
+ } catch (error) {
17173
+ const dnsClass = classifyDNSError(error);
17174
+ if (dnsClass === "missing") {
17175
+ dnsResults.push({
17176
+ name: mailFromDomain,
17177
+ type: "TXT (SPF)",
17178
+ status: "missing"
17179
+ });
17180
+ } else if (dnsClass === "network") {
17181
+ dnsResults.push({
17182
+ name: mailFromDomain,
17183
+ type: "TXT (SPF)",
17184
+ status: "missing",
17185
+ records: ["DNS lookup failed (network issue)"]
17186
+ });
17187
+ } else {
17188
+ throw error;
17189
+ }
17051
17190
  }
17052
17191
  }
17053
17192
  progress.stop();
@@ -17189,7 +17328,7 @@ Run ${pc20.cyan(`wraps email domains verify --domain ${domain}`)} to check verif
17189
17328
  );
17190
17329
  return;
17191
17330
  } catch (error) {
17192
- if (error.name !== "NotFoundException") {
17331
+ if (!isAWSNotFoundError(error)) {
17193
17332
  throw error;
17194
17333
  }
17195
17334
  }
@@ -17502,7 +17641,7 @@ ${pc20.bold("DNS Records to add:")}
17502
17641
  } catch (error) {
17503
17642
  progress.stop();
17504
17643
  trackCommand("email:domains:get-dkim", { success: false });
17505
- if (error.name === "NotFoundException") {
17644
+ if (isAWSNotFoundError(error)) {
17506
17645
  clack19.log.error(`Domain ${options.domain} not found in SES`);
17507
17646
  console.log(
17508
17647
  `
@@ -17583,7 +17722,7 @@ Use ${pc20.cyan(`wraps email domains remove --domain ${options.domain} --force`)
17583
17722
  } catch (error) {
17584
17723
  progress.stop();
17585
17724
  trackCommand("email:domains:remove", { success: false });
17586
- if (error.name === "NotFoundException") {
17725
+ if (isAWSNotFoundError(error)) {
17587
17726
  clack19.log.error(`Domain ${options.domain} not found in SES`);
17588
17727
  process.exit(1);
17589
17728
  return;
@@ -18682,10 +18821,11 @@ ${pc22.yellow(pc22.bold("Configuration Warnings:"))}`);
18682
18821
  return;
18683
18822
  } catch (error) {
18684
18823
  trackError("PREVIEW_FAILED", "email:init", { step: "preview" });
18685
- if (error.message?.includes("stack is currently locked")) {
18824
+ const msg = error instanceof Error ? error.message : String(error);
18825
+ if (msg.includes("stack is currently locked")) {
18686
18826
  throw errors.stackLocked();
18687
18827
  }
18688
- throw new Error(`Preview failed: ${error.message}`);
18828
+ throw new Error(`Preview failed: ${msg}`);
18689
18829
  }
18690
18830
  }
18691
18831
  let outputs;
@@ -18756,13 +18896,14 @@ ${pc22.yellow(pc22.bold("Configuration Warnings:"))}`);
18756
18896
  }
18757
18897
  );
18758
18898
  } catch (error) {
18899
+ const msg = error instanceof Error ? error.message : String(error);
18759
18900
  trackServiceInit("email", false, {
18760
18901
  preset,
18761
18902
  provider,
18762
18903
  region,
18763
18904
  duration_ms: Date.now() - startTime
18764
18905
  });
18765
- if (error.message?.includes("stack is currently locked")) {
18906
+ if (msg.includes("stack is currently locked")) {
18766
18907
  trackError("STACK_LOCKED", "email:init", { step: "deploy" });
18767
18908
  throw errors.stackLocked();
18768
18909
  }
@@ -18793,7 +18934,7 @@ ${pc22.yellow(pc22.bold("Configuration Warnings:"))}`);
18793
18934
  }
18794
18935
  }
18795
18936
  trackError("DEPLOYMENT_FAILED", "email:init", { step: "deploy" });
18796
- throw new Error(`Pulumi deployment failed: ${error.message}`);
18937
+ throw new Error(`Pulumi deployment failed: ${msg}`);
18797
18938
  }
18798
18939
  if (metadata.services.email) {
18799
18940
  metadata.services.email.pulumiStackName = `wraps-${identity.accountId}-${region}`;
@@ -18891,7 +19032,8 @@ ${pc22.yellow(pc22.bold("Configuration Warnings:"))}`);
18891
19032
  }
18892
19033
  } catch (error) {
18893
19034
  progress.stop();
18894
- clack21.log.warn(`Could not manage DNS records: ${error.message}`);
19035
+ const msg = error instanceof Error ? error.message : String(error);
19036
+ clack21.log.warn(`Could not manage DNS records: ${msg}`);
18895
19037
  }
18896
19038
  } else {
18897
19039
  const recordData = {
@@ -19126,7 +19268,8 @@ ${pc23.bold("The following Wraps resources will be removed:")}
19126
19268
  return;
19127
19269
  } catch (error) {
19128
19270
  trackError("PREVIEW_FAILED", "email:restore", { step: "preview" });
19129
- throw new Error(`Preview failed: ${error.message}`);
19271
+ const msg = error instanceof Error ? error.message : String(error);
19272
+ throw new Error(`Preview failed: ${msg}`);
19130
19273
  }
19131
19274
  }
19132
19275
  return;
@@ -19161,7 +19304,8 @@ ${pc23.bold("The following Wraps resources will be removed:")}
19161
19304
  );
19162
19305
  } catch (error) {
19163
19306
  trackError("DESTROY_FAILED", "email:restore", { step: "destroy" });
19164
- throw new Error(`Failed to destroy Pulumi stack: ${error.message}`);
19307
+ const msg = error instanceof Error ? error.message : String(error);
19308
+ throw new Error(`Failed to destroy Pulumi stack: ${msg}`);
19165
19309
  }
19166
19310
  });
19167
19311
  }
@@ -19188,6 +19332,7 @@ init_esm_shims();
19188
19332
  init_client();
19189
19333
  init_events();
19190
19334
  init_aws();
19335
+ init_errors();
19191
19336
  init_fs();
19192
19337
  init_metadata();
19193
19338
  import * as clack23 from "@clack/prompts";
@@ -19232,15 +19377,19 @@ async function emailStatus(options) {
19232
19377
  workDir: getPulumiWorkDir()
19233
19378
  });
19234
19379
  stackOutputs = await stack.outputs();
19235
- } catch (_error) {
19236
- progress.stop();
19237
- clack23.log.error("No email infrastructure found");
19238
- console.log(
19239
- `
19380
+ } catch (error) {
19381
+ if (error instanceof Error && (error.message.includes("no stack named") || error.message.includes("not found"))) {
19382
+ progress.stop();
19383
+ clack23.log.error("No email infrastructure found");
19384
+ console.log(
19385
+ `
19240
19386
  Run ${pc24.cyan("wraps email init")} to deploy email infrastructure.
19241
19387
  `
19242
- );
19243
- process.exit(1);
19388
+ );
19389
+ process.exit(1);
19390
+ return;
19391
+ }
19392
+ throw error;
19244
19393
  }
19245
19394
  const domains = await listSESDomains(region);
19246
19395
  const { SESv2Client: SESv2Client6, GetEmailIdentityCommand: GetEmailIdentityCommand5 } = await import("@aws-sdk/client-sesv2");
@@ -19265,17 +19414,20 @@ Run ${pc24.cyan("wraps email init")} to deploy email infrastructure.
19265
19414
  isPrimary: tracked?.isPrimary,
19266
19415
  purpose: tracked?.purpose
19267
19416
  };
19268
- } catch (_error) {
19269
- return {
19270
- domain: d.domain,
19271
- status: d.verified ? "verified" : "pending",
19272
- dkimTokens: void 0,
19273
- mailFromDomain: void 0,
19274
- mailFromStatus: void 0,
19275
- managed: tracked?.managed,
19276
- isPrimary: tracked?.isPrimary,
19277
- purpose: tracked?.purpose
19278
- };
19417
+ } catch (error) {
19418
+ if (isAWSError(error)) {
19419
+ return {
19420
+ domain: d.domain,
19421
+ status: d.verified ? "verified" : "pending",
19422
+ dkimTokens: void 0,
19423
+ mailFromDomain: void 0,
19424
+ mailFromStatus: void 0,
19425
+ managed: tracked?.managed,
19426
+ isPrimary: tracked?.isPrimary,
19427
+ purpose: tracked?.purpose
19428
+ };
19429
+ }
19430
+ throw error;
19279
19431
  }
19280
19432
  })
19281
19433
  );
@@ -20069,7 +20221,7 @@ function renderViewerPage(compiled, allSlugs) {
20069
20221
  try {
20070
20222
  const doc = iframe.contentDocument || iframe.contentWindow.document;
20071
20223
  iframe.style.height = doc.documentElement.scrollHeight + 'px';
20072
- } catch {}
20224
+ } catch {} // guardrail:allow-swallowed-error \u2014 cross-origin iframe resize
20073
20225
  }
20074
20226
  iframe.addEventListener('load', resizeIframe);
20075
20227
 
@@ -20080,7 +20232,7 @@ function renderViewerPage(compiled, allSlugs) {
20080
20232
  const data = JSON.parse(e.data);
20081
20233
  // Reload iframe (and toolbar metadata by reloading the page)
20082
20234
  iframe.src = '/${compiled.slug}/render?t=' + Date.now();
20083
- } catch {
20235
+ } catch { // guardrail:allow-swallowed-error \u2014 SSE parse error is expected
20084
20236
  // "connected" message or parse error \u2014 ignore
20085
20237
  }
20086
20238
  };
@@ -21899,11 +22051,12 @@ ${pc28.bold("Cost Impact:")}`);
21899
22051
  });
21900
22052
  return;
21901
22053
  } catch (error) {
22054
+ const msg = error instanceof Error ? error.message : String(error);
21902
22055
  trackError("PREVIEW_FAILED", "email:upgrade", { step: "preview" });
21903
- if (error.message?.includes("stack is currently locked")) {
22056
+ if (msg.includes("stack is currently locked")) {
21904
22057
  throw errors.stackLocked();
21905
22058
  }
21906
- throw new Error(`Preview failed: ${error.message}`);
22059
+ throw new Error(`Preview failed: ${msg}`);
21907
22060
  }
21908
22061
  }
21909
22062
  let outputs;
@@ -21985,18 +22138,19 @@ ${pc28.bold("Cost Impact:")}`);
21985
22138
  }
21986
22139
  );
21987
22140
  } catch (error) {
22141
+ const msg = error instanceof Error ? error.message : String(error);
21988
22142
  trackServiceUpgrade("email", {
21989
22143
  from_preset: metadata.services.email?.preset,
21990
22144
  to_preset: newPreset,
21991
22145
  action: typeof upgradeAction === "string" ? upgradeAction : void 0,
21992
22146
  duration_ms: Date.now() - startTime
21993
22147
  });
21994
- if (error.message?.includes("stack is currently locked")) {
22148
+ if (msg.includes("stack is currently locked")) {
21995
22149
  trackError("STACK_LOCKED", "email:upgrade", { step: "deploy" });
21996
22150
  throw errors.stackLocked();
21997
22151
  }
21998
22152
  trackError("UPGRADE_FAILED", "email:upgrade", { step: "deploy" });
21999
- throw new Error(`Pulumi upgrade failed: ${error.message}`);
22153
+ throw new Error(`Pulumi upgrade failed: ${msg}`);
22000
22154
  }
22001
22155
  let dnsAutoCreated = false;
22002
22156
  if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
@@ -22054,9 +22208,8 @@ ${pc28.bold("Cost Impact:")}`);
22054
22208
  );
22055
22209
  }
22056
22210
  } catch (error) {
22057
- progress.fail(
22058
- `Failed to create DNS records automatically: ${error.message}`
22059
- );
22211
+ const msg = error instanceof Error ? error.message : String(error);
22212
+ progress.fail(`Failed to create DNS records automatically: ${msg}`);
22060
22213
  progress.info(
22061
22214
  "You can manually add the required DNS records shown below"
22062
22215
  );
@@ -22195,9 +22348,8 @@ ${pc28.bold("Add these DNS records to your DNS provider:")}
22195
22348
  }
22196
22349
  }
22197
22350
  } catch (error) {
22198
- progress.fail(
22199
- `Failed to create ACM validation record: ${error.message}`
22200
- );
22351
+ const msg = error instanceof Error ? error.message : String(error);
22352
+ progress.fail(`Failed to create ACM validation record: ${msg}`);
22201
22353
  }
22202
22354
  }
22203
22355
  }
@@ -24423,10 +24575,10 @@ var WRAPS_PLATFORM_ACCOUNT_ID = "905130073023";
24423
24575
  async function updatePlatformRole(metadata, progress, externalId) {
24424
24576
  const roleName = "wraps-console-access-role";
24425
24577
  const iam10 = new IAMClient2({ region: "us-east-1" });
24426
- let roleExists4 = false;
24578
+ let roleExists2 = false;
24427
24579
  try {
24428
24580
  await iam10.send(new GetRoleCommand({ RoleName: roleName }));
24429
- roleExists4 = true;
24581
+ roleExists2 = true;
24430
24582
  } catch (error) {
24431
24583
  const isNotFound = error instanceof Error && (error.name === "NoSuchEntityException" || error.name === "NoSuchEntity" || error.message.includes("NoSuchEntity"));
24432
24584
  if (!isNotFound) {
@@ -24436,7 +24588,7 @@ async function updatePlatformRole(metadata, progress, externalId) {
24436
24588
  const emailConfig = metadata.services.email?.config;
24437
24589
  const smsConfig = metadata.services.sms?.config;
24438
24590
  const policy = buildConsolePolicyDocument(emailConfig, smsConfig);
24439
- if (roleExists4) {
24591
+ if (roleExists2) {
24440
24592
  await progress.execute("Updating platform access role", async () => {
24441
24593
  await iam10.send(
24442
24594
  new PutRolePolicyCommand({
@@ -24894,17 +25046,17 @@ Run ${pc33.cyan("wraps email init")} or ${pc33.cyan("wraps sms init")} first.
24894
25046
  }
24895
25047
  const roleName = "wraps-console-access-role";
24896
25048
  const iam10 = new IAMClient2({ region: "us-east-1" });
24897
- let roleExists4 = false;
25049
+ let roleExists2 = false;
24898
25050
  try {
24899
25051
  await iam10.send(new GetRoleCommand({ RoleName: roleName }));
24900
- roleExists4 = true;
25052
+ roleExists2 = true;
24901
25053
  } catch (error) {
24902
25054
  const isNotFound = error instanceof Error && (error.name === "NoSuchEntityException" || error.name === "NoSuchEntity" || error.message.includes("NoSuchEntity"));
24903
25055
  if (!isNotFound) {
24904
25056
  throw error;
24905
25057
  }
24906
25058
  }
24907
- if (roleExists4) {
25059
+ if (roleExists2) {
24908
25060
  const emailConfig = metadata.services.email?.config;
24909
25061
  const smsConfig = metadata.services.sms?.config;
24910
25062
  const policy = buildConsolePolicyDocument(emailConfig, smsConfig);
@@ -25061,10 +25213,10 @@ Run ${pc35.cyan("wraps email init")} to deploy infrastructure first.
25061
25213
  }
25062
25214
  const roleName = "wraps-console-access-role";
25063
25215
  const iam10 = new IAMClient3({ region: "us-east-1" });
25064
- let roleExists4 = false;
25216
+ let roleExists2 = false;
25065
25217
  try {
25066
25218
  await iam10.send(new GetRoleCommand2({ RoleName: roleName }));
25067
- roleExists4 = true;
25219
+ roleExists2 = true;
25068
25220
  } catch (error) {
25069
25221
  const isNotFound = error instanceof Error && (error.name === "NoSuchEntityException" || error.name === "NoSuchEntity" || error.message.includes("NoSuchEntity"));
25070
25222
  if (!isNotFound) {
@@ -25072,7 +25224,7 @@ Run ${pc35.cyan("wraps email init")} to deploy infrastructure first.
25072
25224
  }
25073
25225
  }
25074
25226
  const externalId = metadata.platform?.externalId;
25075
- if (!(roleExists4 || externalId)) {
25227
+ if (!(roleExists2 || externalId)) {
25076
25228
  progress.stop();
25077
25229
  log31.warn(`IAM role ${pc35.cyan(roleName)} does not exist`);
25078
25230
  console.log(
@@ -25084,7 +25236,7 @@ Run ${pc35.cyan("wraps email init")} to deploy infrastructure first.
25084
25236
  );
25085
25237
  process.exit(0);
25086
25238
  }
25087
- if (roleExists4) {
25239
+ if (roleExists2) {
25088
25240
  progress.info(`Found IAM role: ${pc35.cyan(roleName)}`);
25089
25241
  } else {
25090
25242
  progress.info(
@@ -25093,7 +25245,7 @@ Run ${pc35.cyan("wraps email init")} to deploy infrastructure first.
25093
25245
  }
25094
25246
  if (!options.force) {
25095
25247
  progress.stop();
25096
- const actionLabel = roleExists4 ? "Update" : "Create";
25248
+ const actionLabel = roleExists2 ? "Update" : "Create";
25097
25249
  const shouldContinue = await confirm13({
25098
25250
  message: `${actionLabel} IAM role ${pc35.cyan(roleName)} with latest permissions?`,
25099
25251
  initialValue: true
@@ -25112,7 +25264,7 @@ Run ${pc35.cyan("wraps email init")} to deploy infrastructure first.
25112
25264
  const smsEnabled = !!smsConfig;
25113
25265
  const smsSendingEnabled = smsConfig && smsConfig.sendingEnabled !== false;
25114
25266
  const smsEventTracking = smsConfig?.eventTracking;
25115
- if (!roleExists4 && externalId) {
25267
+ if (!roleExists2 && externalId) {
25116
25268
  const WRAPS_PLATFORM_ACCOUNT_ID2 = "905130073023";
25117
25269
  await progress.execute("Creating IAM role", async () => {
25118
25270
  const trustPolicy = {
@@ -25165,7 +25317,7 @@ Run ${pc35.cyan("wraps email init")} to deploy infrastructure first.
25165
25317
  });
25166
25318
  }
25167
25319
  progress.stop();
25168
- const actionVerb = roleExists4 ? "updated" : "created";
25320
+ const actionVerb = roleExists2 ? "updated" : "created";
25169
25321
  trackCommand("platform:update-role", {
25170
25322
  success: true,
25171
25323
  duration_ms: Date.now() - startTime,
@@ -28349,38 +28501,9 @@ import pc39 from "picocolors";
28349
28501
 
28350
28502
  // src/infrastructure/sms-stack.ts
28351
28503
  init_esm_shims();
28504
+ init_resource_checks();
28352
28505
  import * as aws19 from "@pulumi/aws";
28353
28506
  import * as pulumi24 from "@pulumi/pulumi";
28354
- async function roleExists3(roleName) {
28355
- try {
28356
- const { IAMClient: IAMClient4, GetRoleCommand: GetRoleCommand3 } = await import("@aws-sdk/client-iam");
28357
- const iam10 = new IAMClient4({
28358
- region: process.env.AWS_REGION || "us-east-1"
28359
- });
28360
- await iam10.send(new GetRoleCommand3({ RoleName: roleName }));
28361
- return true;
28362
- } catch (error) {
28363
- if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity") {
28364
- return false;
28365
- }
28366
- return false;
28367
- }
28368
- }
28369
- async function tableExists2(tableName) {
28370
- try {
28371
- const { DynamoDBClient: DynamoDBClient6, DescribeTableCommand: DescribeTableCommand2 } = await import("@aws-sdk/client-dynamodb");
28372
- const dynamodb3 = new DynamoDBClient6({
28373
- region: process.env.AWS_REGION || "us-east-1"
28374
- });
28375
- await dynamodb3.send(new DescribeTableCommand2({ TableName: tableName }));
28376
- return true;
28377
- } catch (error) {
28378
- if (error instanceof Error && "name" in error && error.name === "ResourceNotFoundException") {
28379
- return false;
28380
- }
28381
- return false;
28382
- }
28383
- }
28384
28507
  async function createSMSIAMRole(config2) {
28385
28508
  let assumeRolePolicy;
28386
28509
  if (config2.provider === "vercel" && config2.oidcProvider) {
@@ -28426,7 +28549,7 @@ async function createSMSIAMRole(config2) {
28426
28549
  throw new Error("Other providers not yet implemented");
28427
28550
  }
28428
28551
  const roleName = "wraps-sms-role";
28429
- const exists = await roleExists3(roleName);
28552
+ const exists = await roleExists(roleName);
28430
28553
  const role = exists ? new aws19.iam.Role(
28431
28554
  roleName,
28432
28555
  {
@@ -28644,20 +28767,6 @@ async function createSMSPhoneNumber(phoneNumberType, optOutList) {
28644
28767
  }
28645
28768
  );
28646
28769
  }
28647
- async function sqsQueueExists(queueName) {
28648
- try {
28649
- const { SQSClient, GetQueueUrlCommand } = await import("@aws-sdk/client-sqs");
28650
- const sqs5 = new SQSClient({
28651
- region: process.env.AWS_REGION || "us-east-1"
28652
- });
28653
- const response = await sqs5.send(
28654
- new GetQueueUrlCommand({ QueueName: queueName })
28655
- );
28656
- return response.QueueUrl || null;
28657
- } catch {
28658
- return null;
28659
- }
28660
- }
28661
28770
  async function createSMSSQSResources() {
28662
28771
  const dlqName = "wraps-sms-events-dlq";
28663
28772
  const queueName = "wraps-sms-events";
@@ -28707,30 +28816,6 @@ async function createSMSSQSResources() {
28707
28816
  });
28708
28817
  return { queue, dlq };
28709
28818
  }
28710
- async function snsTopicExists(topicName) {
28711
- try {
28712
- const { SNSClient: SNSClient2, ListTopicsCommand: ListTopicsCommand2 } = await import("@aws-sdk/client-sns");
28713
- const sns3 = new SNSClient2({
28714
- region: process.env.AWS_REGION || "us-east-1"
28715
- });
28716
- let nextToken;
28717
- do {
28718
- const response = await sns3.send(
28719
- new ListTopicsCommand2({ NextToken: nextToken })
28720
- );
28721
- const found = response.Topics?.find(
28722
- (t) => t.TopicArn?.endsWith(`:${topicName}`)
28723
- );
28724
- if (found?.TopicArn) {
28725
- return found.TopicArn;
28726
- }
28727
- nextToken = response.NextToken;
28728
- } while (nextToken);
28729
- return null;
28730
- } catch {
28731
- return null;
28732
- }
28733
- }
28734
28819
  async function createSMSSNSResources(config2) {
28735
28820
  const topicName = "wraps-sms-events";
28736
28821
  const topicArn = await snsTopicExists(topicName);
@@ -28798,7 +28883,7 @@ async function createSMSSNSResources(config2) {
28798
28883
  }
28799
28884
  async function createSMSDynamoDBTable() {
28800
28885
  const tableName = "wraps-sms-history";
28801
- const exists = await tableExists2(tableName);
28886
+ const exists = await tableExists(tableName);
28802
28887
  const tableConfig = {
28803
28888
  name: tableName,
28804
28889
  billingMode: "PAY_PER_REQUEST",
@@ -31734,18 +31819,19 @@ ${pc45.bold("Cost Impact:")}`);
31734
31819
  });
31735
31820
  }
31736
31821
  } catch (error) {
31822
+ const msg = error instanceof Error ? error.message : String(error);
31737
31823
  trackServiceUpgrade("sms", {
31738
31824
  from_preset: metadata.services.sms?.preset,
31739
31825
  to_preset: newPreset,
31740
31826
  action: typeof upgradeAction === "string" ? upgradeAction : void 0,
31741
31827
  duration_ms: Date.now() - startTime
31742
31828
  });
31743
- if (error.message?.includes("stack is currently locked")) {
31829
+ if (msg.includes("stack is currently locked")) {
31744
31830
  trackError("STACK_LOCKED", "sms:upgrade", { step: "deploy" });
31745
31831
  throw errors.stackLocked();
31746
31832
  }
31747
31833
  trackError("UPGRADE_FAILED", "sms:upgrade", { step: "deploy" });
31748
- throw new Error(`SMS upgrade failed: ${error.message}`);
31834
+ throw new Error(`SMS upgrade failed: ${msg}`);
31749
31835
  }
31750
31836
  updateServiceConfig(metadata, "sms", updatedConfig);
31751
31837
  if (metadata.services.sms) {