@wraps.dev/cli 2.1.0 → 2.2.0

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
@@ -147,7 +147,7 @@ var require_package = __commonJS({
147
147
  "package.json"(exports, module) {
148
148
  module.exports = {
149
149
  name: "@wraps.dev/cli",
150
- version: "2.1.0",
150
+ version: "2.2.0",
151
151
  description: "CLI for deploying Wraps email infrastructure to your AWS account",
152
152
  type: "module",
153
153
  main: "./dist/cli.js",
@@ -647,6 +647,19 @@ To remove: wraps destroy --stack ${stackName}`,
647
647
  "SMS_SIMULATOR_LIMIT",
648
648
  "Upgrade to a toll-free number for production use:\n wraps sms upgrade --phone-type toll-free",
649
649
  "https://wraps.dev/docs/cli-reference"
650
+ ),
651
+ // SMTP-specific errors
652
+ smtpRequiresSending: () => new WrapsError(
653
+ "SMTP credentials require email sending to be enabled",
654
+ "SMTP_REQUIRES_SENDING",
655
+ "Enable sending first:\n wraps email upgrade\nAnd select 'Custom configuration' to enable sending.",
656
+ "https://wraps.dev/docs/cli-reference"
657
+ ),
658
+ smtpCredentialsNotFound: () => new WrapsError(
659
+ "SMTP credentials not found",
660
+ "SMTP_CREDENTIALS_NOT_FOUND",
661
+ "Enable SMTP credentials:\n wraps email upgrade\nAnd select 'Enable SMTP credentials'",
662
+ "https://wraps.dev/docs/cli-reference"
650
663
  )
651
664
  };
652
665
  }
@@ -953,6 +966,15 @@ function calculateEmailArchivingCost(config2, emailsPerMonth) {
953
966
  description: `Email archiving (${retention}, ~${storageGB.toFixed(2)} GB at steady-state)`
954
967
  };
955
968
  }
969
+ function calculateSMTPCredentialsCost(config2) {
970
+ if (!config2.smtpCredentials?.enabled) {
971
+ return;
972
+ }
973
+ return {
974
+ monthly: 0,
975
+ description: "SMTP credentials (no additional cost)"
976
+ };
977
+ }
956
978
  function calculateCosts(config2, emailsPerMonth = 1e4) {
957
979
  const tracking = calculateTrackingCost(config2);
958
980
  const reputationMetrics = calculateReputationMetricsCost(config2);
@@ -961,8 +983,9 @@ function calculateCosts(config2, emailsPerMonth = 1e4) {
961
983
  const emailArchiving = calculateEmailArchivingCost(config2, emailsPerMonth);
962
984
  const dedicatedIp = calculateDedicatedIpCost(config2);
963
985
  const waf = calculateWafCost(config2, emailsPerMonth);
986
+ const smtpCredentials = calculateSMTPCredentialsCost(config2);
964
987
  const sesEmailCost = Math.max(0, emailsPerMonth - FREE_TIER.SES_EMAILS) * AWS_PRICING.SES_PER_EMAIL;
965
- const totalMonthlyCost = sesEmailCost + (tracking?.monthly || 0) + (reputationMetrics?.monthly || 0) + (eventTracking?.monthly || 0) + (dynamoDBHistory?.monthly || 0) + (emailArchiving?.monthly || 0) + (dedicatedIp?.monthly || 0) + (waf?.monthly || 0);
988
+ const totalMonthlyCost = sesEmailCost + (tracking?.monthly || 0) + (reputationMetrics?.monthly || 0) + (eventTracking?.monthly || 0) + (dynamoDBHistory?.monthly || 0) + (emailArchiving?.monthly || 0) + (dedicatedIp?.monthly || 0) + (waf?.monthly || 0) + (smtpCredentials?.monthly || 0);
966
989
  return {
967
990
  tracking,
968
991
  reputationMetrics,
@@ -971,6 +994,7 @@ function calculateCosts(config2, emailsPerMonth = 1e4) {
971
994
  emailArchiving,
972
995
  dedicatedIp,
973
996
  waf,
997
+ smtpCredentials,
974
998
  total: {
975
999
  monthly: totalMonthlyCost,
976
1000
  perEmail: AWS_PRICING.SES_PER_EMAIL,
@@ -1031,6 +1055,11 @@ function getCostSummary(config2, emailsPerMonth = 1e4) {
1031
1055
  ` - ${costs.waf.description}: ${formatCost(costs.waf.monthly)}`
1032
1056
  );
1033
1057
  }
1058
+ if (costs.smtpCredentials) {
1059
+ lines.push(
1060
+ ` - ${costs.smtpCredentials.description}: ${formatCost(costs.smtpCredentials.monthly)}`
1061
+ );
1062
+ }
1034
1063
  return lines.join("\n");
1035
1064
  }
1036
1065
  var AWS_PRICING, FREE_TIER;
@@ -2317,6 +2346,11 @@ function applyConfigUpdates(existingConfig, updates) {
2317
2346
  ...result.emailArchiving,
2318
2347
  ...value
2319
2348
  };
2349
+ } else if (key === "smtpCredentials" && typeof value === "object") {
2350
+ result.smtpCredentials = {
2351
+ ...result.smtpCredentials,
2352
+ ...value
2353
+ };
2320
2354
  } else {
2321
2355
  result[key] = value;
2322
2356
  }
@@ -3230,12 +3264,12 @@ var acm_exports = {};
3230
3264
  __export(acm_exports, {
3231
3265
  createACMCertificate: () => createACMCertificate
3232
3266
  });
3233
- import * as aws8 from "@pulumi/aws";
3267
+ import * as aws9 from "@pulumi/aws";
3234
3268
  async function createACMCertificate(config2) {
3235
- const usEast1Provider = new aws8.Provider("acm-us-east-1", {
3269
+ const usEast1Provider = new aws9.Provider("acm-us-east-1", {
3236
3270
  region: "us-east-1"
3237
3271
  });
3238
- const certificate = new aws8.acm.Certificate(
3272
+ const certificate = new aws9.acm.Certificate(
3239
3273
  "wraps-email-tracking-cert",
3240
3274
  {
3241
3275
  domainName: config2.domain,
@@ -3258,7 +3292,7 @@ async function createACMCertificate(config2) {
3258
3292
  );
3259
3293
  let certificateValidation;
3260
3294
  if (config2.hostedZoneId) {
3261
- const validationRecord = new aws8.route53.Record(
3295
+ const validationRecord = new aws9.route53.Record(
3262
3296
  "wraps-email-tracking-cert-validation",
3263
3297
  {
3264
3298
  zoneId: config2.hostedZoneId,
@@ -3268,7 +3302,7 @@ async function createACMCertificate(config2) {
3268
3302
  ttl: 60
3269
3303
  }
3270
3304
  );
3271
- certificateValidation = new aws8.acm.CertificateValidation(
3305
+ certificateValidation = new aws9.acm.CertificateValidation(
3272
3306
  "wraps-email-tracking-cert-validation-waiter",
3273
3307
  {
3274
3308
  certificateArn: certificate.arn,
@@ -3297,7 +3331,7 @@ var cloudfront_exports = {};
3297
3331
  __export(cloudfront_exports, {
3298
3332
  createCloudFrontTracking: () => createCloudFrontTracking
3299
3333
  });
3300
- import * as aws9 from "@pulumi/aws";
3334
+ import * as aws10 from "@pulumi/aws";
3301
3335
  async function findDistributionByAlias(alias) {
3302
3336
  try {
3303
3337
  const { CloudFrontClient, ListDistributionsCommand } = await import("@aws-sdk/client-cloudfront");
@@ -3313,10 +3347,10 @@ async function findDistributionByAlias(alias) {
3313
3347
  }
3314
3348
  }
3315
3349
  async function createWAFWebACL() {
3316
- const usEast1Provider = new aws9.Provider("waf-us-east-1", {
3350
+ const usEast1Provider = new aws10.Provider("waf-us-east-1", {
3317
3351
  region: "us-east-1"
3318
3352
  });
3319
- const webAcl = new aws9.wafv2.WebAcl(
3353
+ const webAcl = new aws10.wafv2.WebAcl(
3320
3354
  "wraps-email-tracking-waf",
3321
3355
  {
3322
3356
  scope: "CLOUDFRONT",
@@ -3431,14 +3465,14 @@ async function createCloudFrontTracking(config2) {
3431
3465
  Description: "Wraps email tracking CloudFront distribution"
3432
3466
  }
3433
3467
  };
3434
- const distribution = existingDistributionId ? new aws9.cloudfront.Distribution(
3468
+ const distribution = existingDistributionId ? new aws10.cloudfront.Distribution(
3435
3469
  "wraps-email-tracking-cdn",
3436
3470
  distributionConfig,
3437
3471
  {
3438
3472
  import: existingDistributionId
3439
3473
  // Import existing distribution
3440
3474
  }
3441
- ) : new aws9.cloudfront.Distribution(
3475
+ ) : new aws10.cloudfront.Distribution(
3442
3476
  "wraps-email-tracking-cdn",
3443
3477
  distributionConfig
3444
3478
  );
@@ -8143,7 +8177,7 @@ import pc8 from "picocolors";
8143
8177
 
8144
8178
  // src/infrastructure/email-stack.ts
8145
8179
  init_esm_shims();
8146
- import * as aws10 from "@pulumi/aws";
8180
+ import * as aws11 from "@pulumi/aws";
8147
8181
  import * as pulumi4 from "@pulumi/pulumi";
8148
8182
 
8149
8183
  // src/infrastructure/resources/dynamodb.ts
@@ -8366,10 +8400,10 @@ import * as pulumi2 from "@pulumi/pulumi";
8366
8400
  async function roleExists(roleName) {
8367
8401
  try {
8368
8402
  const { IAMClient: IAMClient3, GetRoleCommand: GetRoleCommand2 } = await import("@aws-sdk/client-iam");
8369
- const iam7 = new IAMClient3({
8403
+ const iam8 = new IAMClient3({
8370
8404
  region: process.env.AWS_REGION || "us-east-1"
8371
8405
  });
8372
- await iam7.send(new GetRoleCommand2({ RoleName: roleName }));
8406
+ await iam8.send(new GetRoleCommand2({ RoleName: roleName }));
8373
8407
  return true;
8374
8408
  } catch (error) {
8375
8409
  if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity") {
@@ -8728,11 +8762,93 @@ async function createSESResources(config2) {
8728
8762
  };
8729
8763
  }
8730
8764
 
8731
- // src/infrastructure/resources/sqs.ts
8765
+ // src/infrastructure/resources/smtp-credentials.ts
8732
8766
  init_esm_shims();
8733
8767
  import * as aws6 from "@pulumi/aws";
8768
+ import { createHmac } from "crypto";
8769
+ function convertToSMTPPassword(secretAccessKey, region) {
8770
+ const DATE = "11111111";
8771
+ const SERVICE = "ses";
8772
+ const MESSAGE = "SendRawEmail";
8773
+ const TERMINAL = "aws4_request";
8774
+ const VERSION2 = 4;
8775
+ const kDate = createHmac("sha256", `AWS4${secretAccessKey}`).update(DATE).digest();
8776
+ const kRegion = createHmac("sha256", kDate).update(region).digest();
8777
+ const kService = createHmac("sha256", kRegion).update(SERVICE).digest();
8778
+ const kTerminal = createHmac("sha256", kService).update(TERMINAL).digest();
8779
+ const kMessage = createHmac("sha256", kTerminal).update(MESSAGE).digest();
8780
+ const signatureWithVersion = Buffer.concat([Buffer.from([VERSION2]), kMessage]);
8781
+ return signatureWithVersion.toString("base64");
8782
+ }
8783
+ async function userExists(userName) {
8784
+ try {
8785
+ const { IAMClient: IAMClient3, GetUserCommand } = await import("@aws-sdk/client-iam");
8786
+ const iam8 = new IAMClient3({ region: process.env.AWS_REGION || "us-east-1" });
8787
+ await iam8.send(new GetUserCommand({ UserName: userName }));
8788
+ return true;
8789
+ } catch (error) {
8790
+ if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity") {
8791
+ return false;
8792
+ }
8793
+ return false;
8794
+ }
8795
+ }
8796
+ async function createSMTPCredentials(config2) {
8797
+ const userName = "wraps-email-smtp-user";
8798
+ const userAlreadyExists = await userExists(userName);
8799
+ const iamUser = userAlreadyExists ? new aws6.iam.User(
8800
+ userName,
8801
+ {
8802
+ name: userName,
8803
+ tags: {
8804
+ ManagedBy: "wraps-cli",
8805
+ Purpose: "SES SMTP Authentication"
8806
+ }
8807
+ },
8808
+ { import: userName }
8809
+ ) : new aws6.iam.User(userName, {
8810
+ name: userName,
8811
+ tags: {
8812
+ ManagedBy: "wraps-cli",
8813
+ Purpose: "SES SMTP Authentication"
8814
+ }
8815
+ });
8816
+ new aws6.iam.UserPolicy("wraps-email-smtp-policy", {
8817
+ user: iamUser.name,
8818
+ policy: JSON.stringify({
8819
+ Version: "2012-10-17",
8820
+ Statement: [
8821
+ {
8822
+ Effect: "Allow",
8823
+ Action: "ses:SendRawEmail",
8824
+ Resource: "*",
8825
+ Condition: {
8826
+ StringEquals: {
8827
+ "ses:ConfigurationSetName": config2.configSetName
8828
+ }
8829
+ }
8830
+ }
8831
+ ]
8832
+ })
8833
+ });
8834
+ const accessKey = new aws6.iam.AccessKey("wraps-email-smtp-key", {
8835
+ user: iamUser.name
8836
+ });
8837
+ const smtpPassword = accessKey.secret.apply(
8838
+ (secret) => convertToSMTPPassword(secret, config2.region)
8839
+ );
8840
+ return {
8841
+ iamUser,
8842
+ accessKey,
8843
+ smtpPassword
8844
+ };
8845
+ }
8846
+
8847
+ // src/infrastructure/resources/sqs.ts
8848
+ init_esm_shims();
8849
+ import * as aws7 from "@pulumi/aws";
8734
8850
  async function createSQSResources() {
8735
- const dlq = new aws6.sqs.Queue("wraps-email-events-dlq", {
8851
+ const dlq = new aws7.sqs.Queue("wraps-email-events-dlq", {
8736
8852
  name: "wraps-email-events-dlq",
8737
8853
  messageRetentionSeconds: 1209600,
8738
8854
  // 14 days
@@ -8741,7 +8857,7 @@ async function createSQSResources() {
8741
8857
  Description: "Dead letter queue for failed SES event processing"
8742
8858
  }
8743
8859
  });
8744
- const queue = new aws6.sqs.Queue("wraps-email-events", {
8860
+ const queue = new aws7.sqs.Queue("wraps-email-events", {
8745
8861
  name: "wraps-email-events",
8746
8862
  visibilityTimeoutSeconds: 300,
8747
8863
  // 5 minutes (Lambda timeout)
@@ -8769,14 +8885,14 @@ async function createSQSResources() {
8769
8885
 
8770
8886
  // src/infrastructure/vercel-oidc.ts
8771
8887
  init_esm_shims();
8772
- import * as aws7 from "@pulumi/aws";
8888
+ import * as aws8 from "@pulumi/aws";
8773
8889
  async function getExistingOIDCProviderArn(url) {
8774
8890
  try {
8775
8891
  const { IAMClient: IAMClient3, ListOpenIDConnectProvidersCommand } = await import("@aws-sdk/client-iam");
8776
- const iam7 = new IAMClient3({
8892
+ const iam8 = new IAMClient3({
8777
8893
  region: process.env.AWS_REGION || "us-east-1"
8778
8894
  });
8779
- const response = await iam7.send(new ListOpenIDConnectProvidersCommand({}));
8895
+ const response = await iam8.send(new ListOpenIDConnectProvidersCommand({}));
8780
8896
  const expectedArnSuffix = url.replace("https://", "");
8781
8897
  const provider = response.OpenIDConnectProviderList?.find(
8782
8898
  (p) => p.Arn?.endsWith(expectedArnSuffix)
@@ -8791,7 +8907,7 @@ async function createVercelOIDC(config2) {
8791
8907
  const url = `https://oidc.vercel.com/${config2.teamSlug}`;
8792
8908
  const existingArn = await getExistingOIDCProviderArn(url);
8793
8909
  if (existingArn) {
8794
- return new aws7.iam.OpenIdConnectProvider(
8910
+ return new aws8.iam.OpenIdConnectProvider(
8795
8911
  "wraps-vercel-oidc",
8796
8912
  {
8797
8913
  url,
@@ -8812,7 +8928,7 @@ async function createVercelOIDC(config2) {
8812
8928
  }
8813
8929
  );
8814
8930
  }
8815
- return new aws7.iam.OpenIdConnectProvider("wraps-vercel-oidc", {
8931
+ return new aws8.iam.OpenIdConnectProvider("wraps-vercel-oidc", {
8816
8932
  url,
8817
8933
  clientIdLists: [`https://vercel.com/${config2.teamSlug}`],
8818
8934
  thumbprintLists: [
@@ -8829,7 +8945,7 @@ async function createVercelOIDC(config2) {
8829
8945
 
8830
8946
  // src/infrastructure/email-stack.ts
8831
8947
  async function deployEmailStack(config2) {
8832
- const identity = await aws10.getCallerIdentity();
8948
+ const identity = await aws11.getCallerIdentity();
8833
8949
  const accountId = identity.accountId;
8834
8950
  let oidcProvider;
8835
8951
  if (config2.provider === "vercel" && config2.vercel) {
@@ -8934,6 +9050,13 @@ async function deployEmailStack(config2) {
8934
9050
  region: config2.region
8935
9051
  });
8936
9052
  }
9053
+ let smtpResources;
9054
+ if (emailConfig.smtpCredentials?.enabled && sesResources) {
9055
+ smtpResources = await createSMTPCredentials({
9056
+ configSetName: "wraps-email-tracking",
9057
+ region: config2.region
9058
+ });
9059
+ }
8937
9060
  return {
8938
9061
  roleArn: role.arn,
8939
9062
  configSetName: sesResources?.configSet.configurationSetName,
@@ -8953,7 +9076,12 @@ async function deployEmailStack(config2) {
8953
9076
  mailFromDomain: sesResources?.mailFromDomain,
8954
9077
  archiveArn: archiveResources?.archiveArn,
8955
9078
  archivingEnabled: emailConfig.emailArchiving?.enabled,
8956
- archiveRetention: emailConfig.emailArchiving?.enabled ? emailConfig.emailArchiving.retention : void 0
9079
+ archiveRetention: emailConfig.emailArchiving?.enabled ? emailConfig.emailArchiving.retention : void 0,
9080
+ // SMTP credentials (shown once, not stored)
9081
+ smtpUserArn: smtpResources?.iamUser.arn,
9082
+ smtpUsername: smtpResources?.accessKey.id,
9083
+ smtpPassword: smtpResources?.smtpPassword,
9084
+ smtpEndpoint: smtpResources ? `email-smtp.${config2.region}.amazonaws.com` : void 0
8957
9085
  };
8958
9086
  }
8959
9087
 
@@ -10000,13 +10128,13 @@ async function scanLambdaFunctions(region) {
10000
10128
  }
10001
10129
  }
10002
10130
  async function scanIAMRoles(region) {
10003
- const iam7 = new IAMClient({ region });
10131
+ const iam8 = new IAMClient({ region });
10004
10132
  const roles = [];
10005
10133
  try {
10006
10134
  let marker;
10007
10135
  let hasMore = true;
10008
10136
  while (hasMore) {
10009
- const listResponse = await iam7.send(
10137
+ const listResponse = await iam8.send(
10010
10138
  new ListRolesCommand({
10011
10139
  Marker: marker,
10012
10140
  MaxItems: 100
@@ -11901,6 +12029,11 @@ ${pc15.bold("Current Configuration:")}
11901
12029
  value: "wraps-dashboard",
11902
12030
  label: metadata.services.email?.webhookSecret ? "Manage Wraps Dashboard connection" : "Connect to Wraps Dashboard",
11903
12031
  hint: metadata.services.email?.webhookSecret ? "Regenerate secret or disconnect" : "Send events to dashboard for analytics"
12032
+ },
12033
+ {
12034
+ value: "smtp-credentials",
12035
+ label: metadata.services.email?.smtpCredentials?.enabled ? "Manage SMTP credentials" : "Enable SMTP credentials",
12036
+ hint: metadata.services.email?.smtpCredentials?.enabled ? "Rotate or disable credentials" : "Generate credentials for PHP, WordPress, etc."
11904
12037
  }
11905
12038
  ]
11906
12039
  });
@@ -12492,6 +12625,98 @@ ${pc15.bold("Webhook Configuration:")}`);
12492
12625
  newPreset = void 0;
12493
12626
  break;
12494
12627
  }
12628
+ case "smtp-credentials": {
12629
+ if (metadata.services.email?.smtpCredentials?.enabled) {
12630
+ clack14.log.info(
12631
+ `SMTP credentials are currently ${pc15.green("enabled")} (created ${metadata.services.email.smtpCredentials.createdAt})`
12632
+ );
12633
+ const smtpAction = await clack14.select({
12634
+ message: "What would you like to do?",
12635
+ options: [
12636
+ {
12637
+ value: "rotate",
12638
+ label: "Rotate credentials",
12639
+ hint: "Generate new credentials (invalidates old ones)"
12640
+ },
12641
+ {
12642
+ value: "disable",
12643
+ label: "Disable SMTP credentials",
12644
+ hint: "Delete IAM user and credentials"
12645
+ },
12646
+ {
12647
+ value: "cancel",
12648
+ label: "Cancel",
12649
+ hint: "Keep current credentials"
12650
+ }
12651
+ ]
12652
+ });
12653
+ if (clack14.isCancel(smtpAction) || smtpAction === "cancel") {
12654
+ clack14.log.info("No changes made.");
12655
+ process.exit(0);
12656
+ }
12657
+ if (smtpAction === "disable") {
12658
+ const confirmDisable = await clack14.confirm({
12659
+ message: "Are you sure? Any systems using these credentials will stop working immediately.",
12660
+ initialValue: false
12661
+ });
12662
+ if (clack14.isCancel(confirmDisable) || !confirmDisable) {
12663
+ clack14.log.info("SMTP credentials not disabled.");
12664
+ process.exit(0);
12665
+ }
12666
+ updatedConfig = {
12667
+ ...config2,
12668
+ smtpCredentials: { enabled: false }
12669
+ };
12670
+ if (metadata.services.email) {
12671
+ metadata.services.email.smtpCredentials = void 0;
12672
+ }
12673
+ newPreset = void 0;
12674
+ break;
12675
+ }
12676
+ clack14.log.info(
12677
+ "\nRotating credentials will invalidate your current SMTP password."
12678
+ );
12679
+ clack14.log.warn("You will need to update all systems using the old credentials.");
12680
+ const confirmRotate = await clack14.confirm({
12681
+ message: "Generate new SMTP credentials?",
12682
+ initialValue: false
12683
+ });
12684
+ if (clack14.isCancel(confirmRotate) || !confirmRotate) {
12685
+ clack14.log.info("Credential rotation cancelled.");
12686
+ process.exit(0);
12687
+ }
12688
+ }
12689
+ clack14.log.info(`
12690
+ ${pc15.bold("SMTP Credentials for Legacy Systems")}
12691
+ `);
12692
+ clack14.log.info(pc15.dim("Generate SMTP username/password that works with:"));
12693
+ clack14.log.info(pc15.dim(" - PHP mail() and PHPMailer"));
12694
+ clack14.log.info(pc15.dim(" - WordPress (WP Mail SMTP plugin)"));
12695
+ clack14.log.info(pc15.dim(" - Nodemailer and other SMTP libraries"));
12696
+ clack14.log.info(pc15.dim(" - Any SMTP-compatible email client"));
12697
+ console.log("");
12698
+ clack14.log.warn(
12699
+ "Credentials will be shown ONCE after deployment - save them immediately!"
12700
+ );
12701
+ console.log("");
12702
+ const confirmCreate = await clack14.confirm({
12703
+ message: "Create SMTP credentials?",
12704
+ initialValue: true
12705
+ });
12706
+ if (clack14.isCancel(confirmCreate) || !confirmCreate) {
12707
+ clack14.log.info("SMTP credentials not created.");
12708
+ process.exit(0);
12709
+ }
12710
+ updatedConfig = {
12711
+ ...config2,
12712
+ smtpCredentials: {
12713
+ enabled: true,
12714
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
12715
+ }
12716
+ };
12717
+ newPreset = void 0;
12718
+ break;
12719
+ }
12495
12720
  }
12496
12721
  const newCostData = calculateCosts(updatedConfig, 5e4);
12497
12722
  const costDiff = newCostData.total.monthly - currentCostData.total.monthly;
@@ -12643,7 +12868,12 @@ ${pc15.bold("Cost Impact:")}`);
12643
12868
  acmCertificateValidationRecords: result.acmCertificateValidationRecords,
12644
12869
  archiveArn: result.archiveArn,
12645
12870
  archivingEnabled: result.archivingEnabled,
12646
- archiveRetention: result.archiveRetention
12871
+ archiveRetention: result.archiveRetention,
12872
+ // SMTP credentials (shown once)
12873
+ smtpUserArn: result.smtpUserArn,
12874
+ smtpUsername: result.smtpUsername,
12875
+ smtpPassword: result.smtpPassword,
12876
+ smtpEndpoint: result.smtpEndpoint
12647
12877
  };
12648
12878
  }
12649
12879
  },
@@ -12679,7 +12909,12 @@ ${pc15.bold("Cost Impact:")}`);
12679
12909
  acmCertificateValidationRecords: pulumiOutputs.acmCertificateValidationRecords?.value,
12680
12910
  archiveArn: pulumiOutputs.archiveArn?.value,
12681
12911
  archivingEnabled: pulumiOutputs.archivingEnabled?.value,
12682
- archiveRetention: pulumiOutputs.archiveRetention?.value
12912
+ archiveRetention: pulumiOutputs.archiveRetention?.value,
12913
+ // SMTP credentials (shown once)
12914
+ smtpUserArn: pulumiOutputs.smtpUserArn?.value,
12915
+ smtpUsername: pulumiOutputs.smtpUsername?.value,
12916
+ smtpPassword: pulumiOutputs.smtpPassword?.value,
12917
+ smtpEndpoint: pulumiOutputs.smtpEndpoint?.value
12683
12918
  };
12684
12919
  }
12685
12920
  );
@@ -12823,6 +13058,31 @@ ${pc15.green("\u2713")} ${pc15.bold("Upgrade complete!")}
12823
13058
  )
12824
13059
  );
12825
13060
  }
13061
+ if (upgradeAction === "smtp-credentials" && outputs.smtpUsername && outputs.smtpPassword) {
13062
+ console.log(pc15.bold("\n\u{1F4E7} SMTP Connection Details\n"));
13063
+ console.log(` ${pc15.cyan("Server:")} ${outputs.smtpEndpoint}`);
13064
+ console.log(` ${pc15.cyan("Port:")} 587 (STARTTLS) or 465 (TLS)`);
13065
+ console.log(` ${pc15.cyan("Username:")} ${outputs.smtpUsername}`);
13066
+ console.log(` ${pc15.cyan("Password:")} ${outputs.smtpPassword}`);
13067
+ console.log(` ${pc15.cyan("Encryption:")} TLS/STARTTLS required
13068
+ `);
13069
+ console.log(pc15.yellow("\u26A0\uFE0F IMPORTANT: Save these credentials NOW!"));
13070
+ console.log(pc15.yellow(" They cannot be retrieved later.\n"));
13071
+ console.log(pc15.bold(" Environment Variables:\n"));
13072
+ console.log(pc15.dim(` SMTP_HOST=${outputs.smtpEndpoint}`));
13073
+ console.log(pc15.dim(` SMTP_PORT=587`));
13074
+ console.log(pc15.dim(` SMTP_USER=${outputs.smtpUsername}`));
13075
+ console.log(pc15.dim(` SMTP_PASS=${outputs.smtpPassword}
13076
+ `));
13077
+ if (metadata.services.email && outputs.smtpUserArn) {
13078
+ metadata.services.email.smtpCredentials = {
13079
+ enabled: true,
13080
+ iamUserArn: outputs.smtpUserArn,
13081
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
13082
+ };
13083
+ await saveConnectionMetadata(metadata);
13084
+ }
13085
+ }
12826
13086
  const enabledFeatures = [];
12827
13087
  if (updatedConfig.tracking?.enabled) {
12828
13088
  enabledFeatures.push("tracking");
@@ -12842,6 +13102,9 @@ ${pc15.green("\u2713")} ${pc15.bold("Upgrade complete!")}
12842
13102
  if (updatedConfig.emailArchiving?.enabled) {
12843
13103
  enabledFeatures.push("email_archiving");
12844
13104
  }
13105
+ if (updatedConfig.smtpCredentials?.enabled) {
13106
+ enabledFeatures.push("smtp_credentials");
13107
+ }
12845
13108
  trackServiceUpgrade("email", {
12846
13109
  from_preset: metadata.services.email?.preset,
12847
13110
  to_preset: newPreset,
@@ -12945,10 +13208,10 @@ Run ${pc18.cyan("wraps email init")} to deploy infrastructure first.
12945
13208
  process.exit(1);
12946
13209
  }
12947
13210
  const roleName = "wraps-console-access-role";
12948
- const iam7 = new IAMClient2({ region: "us-east-1" });
13211
+ const iam8 = new IAMClient2({ region: "us-east-1" });
12949
13212
  let roleExists4 = false;
12950
13213
  try {
12951
- await iam7.send(new GetRoleCommand({ RoleName: roleName }));
13214
+ await iam8.send(new GetRoleCommand({ RoleName: roleName }));
12952
13215
  roleExists4 = true;
12953
13216
  } catch (error) {
12954
13217
  if (error && typeof error === "object" && "name" in error && error.name !== "NoSuchEntity") {
@@ -12989,7 +13252,7 @@ Run ${pc18.cyan("wraps email init")} to deploy infrastructure first.
12989
13252
  const smsEventTracking = smsConfig?.eventTracking;
12990
13253
  await progress.execute("Updating IAM role permissions", async () => {
12991
13254
  const { PutRolePolicyCommand } = await import("@aws-sdk/client-iam");
12992
- await iam7.send(
13255
+ await iam8.send(
12993
13256
  new PutRolePolicyCommand({
12994
13257
  RoleName: roleName,
12995
13258
  PolicyName: "wraps-console-access-policy",
@@ -16014,15 +16277,15 @@ import pc22 from "picocolors";
16014
16277
 
16015
16278
  // src/infrastructure/sms-stack.ts
16016
16279
  init_esm_shims();
16017
- import * as aws11 from "@pulumi/aws";
16280
+ import * as aws12 from "@pulumi/aws";
16018
16281
  import * as pulumi14 from "@pulumi/pulumi";
16019
16282
  async function roleExists2(roleName) {
16020
16283
  try {
16021
16284
  const { IAMClient: IAMClient3, GetRoleCommand: GetRoleCommand2 } = await import("@aws-sdk/client-iam");
16022
- const iam7 = new IAMClient3({
16285
+ const iam8 = new IAMClient3({
16023
16286
  region: process.env.AWS_REGION || "us-east-1"
16024
16287
  });
16025
- await iam7.send(new GetRoleCommand2({ RoleName: roleName }));
16288
+ await iam8.send(new GetRoleCommand2({ RoleName: roleName }));
16026
16289
  return true;
16027
16290
  } catch (error) {
16028
16291
  if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity") {
@@ -16092,7 +16355,7 @@ async function createSMSIAMRole(config2) {
16092
16355
  }
16093
16356
  const roleName = "wraps-sms-role";
16094
16357
  const exists = await roleExists2(roleName);
16095
- const role = exists ? new aws11.iam.Role(
16358
+ const role = exists ? new aws12.iam.Role(
16096
16359
  roleName,
16097
16360
  {
16098
16361
  name: roleName,
@@ -16107,7 +16370,7 @@ async function createSMSIAMRole(config2) {
16107
16370
  import: roleName,
16108
16371
  customTimeouts: { create: "2m", update: "2m", delete: "2m" }
16109
16372
  }
16110
- ) : new aws11.iam.Role(
16373
+ ) : new aws12.iam.Role(
16111
16374
  roleName,
16112
16375
  {
16113
16376
  name: roleName,
@@ -16202,7 +16465,7 @@ async function createSMSIAMRole(config2) {
16202
16465
  Resource: "arn:aws:logs:*:*:log-group:/aws/lambda/wraps-sms-*"
16203
16466
  });
16204
16467
  }
16205
- new aws11.iam.RolePolicy("wraps-sms-policy", {
16468
+ new aws12.iam.RolePolicy("wraps-sms-policy", {
16206
16469
  role: role.name,
16207
16470
  policy: JSON.stringify({
16208
16471
  Version: "2012-10-17",
@@ -16212,7 +16475,7 @@ async function createSMSIAMRole(config2) {
16212
16475
  return role;
16213
16476
  }
16214
16477
  function createSMSConfigurationSet() {
16215
- return new aws11.pinpoint.Smsvoicev2ConfigurationSet("wraps-sms-config", {
16478
+ return new aws12.pinpoint.Smsvoicev2ConfigurationSet("wraps-sms-config", {
16216
16479
  name: "wraps-sms-config",
16217
16480
  defaultMessageType: "TRANSACTIONAL",
16218
16481
  tags: {
@@ -16222,7 +16485,7 @@ function createSMSConfigurationSet() {
16222
16485
  });
16223
16486
  }
16224
16487
  function createSMSOptOutList() {
16225
- return new aws11.pinpoint.Smsvoicev2OptOutList("wraps-sms-optouts", {
16488
+ return new aws12.pinpoint.Smsvoicev2OptOutList("wraps-sms-optouts", {
16226
16489
  name: "wraps-sms-optouts",
16227
16490
  tags: {
16228
16491
  ManagedBy: "wraps-cli",
@@ -16286,7 +16549,7 @@ async function createSMSPhoneNumber(phoneNumberType, optOutList) {
16286
16549
  }
16287
16550
  };
16288
16551
  if (existingArn) {
16289
- return new aws11.pinpoint.Smsvoicev2PhoneNumber(
16552
+ return new aws12.pinpoint.Smsvoicev2PhoneNumber(
16290
16553
  "wraps-sms-number",
16291
16554
  phoneConfig,
16292
16555
  {
@@ -16295,7 +16558,7 @@ async function createSMSPhoneNumber(phoneNumberType, optOutList) {
16295
16558
  }
16296
16559
  );
16297
16560
  }
16298
- return new aws11.pinpoint.Smsvoicev2PhoneNumber(
16561
+ return new aws12.pinpoint.Smsvoicev2PhoneNumber(
16299
16562
  "wraps-sms-number",
16300
16563
  phoneConfig,
16301
16564
  {
@@ -16338,10 +16601,10 @@ async function createSMSSQSResources() {
16338
16601
  Description: "Dead letter queue for failed SMS event processing"
16339
16602
  }
16340
16603
  };
16341
- const dlq = dlqUrl ? new aws11.sqs.Queue(dlqName, dlqConfig, {
16604
+ const dlq = dlqUrl ? new aws12.sqs.Queue(dlqName, dlqConfig, {
16342
16605
  import: dlqUrl,
16343
16606
  customTimeouts: { create: "2m", update: "2m", delete: "2m" }
16344
- }) : new aws11.sqs.Queue(dlqName, dlqConfig, {
16607
+ }) : new aws12.sqs.Queue(dlqName, dlqConfig, {
16345
16608
  customTimeouts: { create: "2m", update: "2m", delete: "2m" }
16346
16609
  });
16347
16610
  const queueConfig = {
@@ -16364,10 +16627,10 @@ async function createSMSSQSResources() {
16364
16627
  Description: "Queue for SMS events from SNS"
16365
16628
  }
16366
16629
  };
16367
- const queue = queueUrl ? new aws11.sqs.Queue(queueName, queueConfig, {
16630
+ const queue = queueUrl ? new aws12.sqs.Queue(queueName, queueConfig, {
16368
16631
  import: queueUrl,
16369
16632
  customTimeouts: { create: "2m", update: "2m", delete: "2m" }
16370
- }) : new aws11.sqs.Queue(queueName, queueConfig, {
16633
+ }) : new aws12.sqs.Queue(queueName, queueConfig, {
16371
16634
  customTimeouts: { create: "2m", update: "2m", delete: "2m" }
16372
16635
  });
16373
16636
  return { queue, dlq };
@@ -16407,13 +16670,13 @@ async function createSMSSNSResources(config2) {
16407
16670
  Description: "SNS topic for SMS delivery events"
16408
16671
  }
16409
16672
  };
16410
- const topic = topicArn ? new aws11.sns.Topic("wraps-sms-events-topic", topicConfig, {
16673
+ const topic = topicArn ? new aws12.sns.Topic("wraps-sms-events-topic", topicConfig, {
16411
16674
  import: topicArn,
16412
16675
  customTimeouts: { create: "2m", update: "2m", delete: "2m" }
16413
- }) : new aws11.sns.Topic("wraps-sms-events-topic", topicConfig, {
16676
+ }) : new aws12.sns.Topic("wraps-sms-events-topic", topicConfig, {
16414
16677
  customTimeouts: { create: "2m", update: "2m", delete: "2m" }
16415
16678
  });
16416
- new aws11.sns.TopicPolicy("wraps-sms-events-topic-policy", {
16679
+ new aws12.sns.TopicPolicy("wraps-sms-events-topic-policy", {
16417
16680
  arn: topic.arn,
16418
16681
  policy: topic.arn.apply(
16419
16682
  (topicArn2) => JSON.stringify({
@@ -16430,7 +16693,7 @@ async function createSMSSNSResources(config2) {
16430
16693
  })
16431
16694
  )
16432
16695
  });
16433
- new aws11.sqs.QueuePolicy("wraps-sms-events-queue-policy", {
16696
+ new aws12.sqs.QueuePolicy("wraps-sms-events-queue-policy", {
16434
16697
  queueUrl: config2.queueUrl,
16435
16698
  policy: pulumi14.all([config2.queueArn, topic.arn]).apply(
16436
16699
  ([queueArn, topicArn2]) => JSON.stringify({
@@ -16449,7 +16712,7 @@ async function createSMSSNSResources(config2) {
16449
16712
  })
16450
16713
  )
16451
16714
  });
16452
- const subscription = new aws11.sns.TopicSubscription(
16715
+ const subscription = new aws12.sns.TopicSubscription(
16453
16716
  "wraps-sms-events-subscription",
16454
16717
  {
16455
16718
  topic: topic.arn,
@@ -16498,17 +16761,17 @@ async function createSMSDynamoDBTable() {
16498
16761
  Service: "sms"
16499
16762
  }
16500
16763
  };
16501
- return exists ? new aws11.dynamodb.Table(tableName, tableConfig, {
16764
+ return exists ? new aws12.dynamodb.Table(tableName, tableConfig, {
16502
16765
  import: tableName,
16503
16766
  customTimeouts: { create: "5m", update: "5m", delete: "5m" }
16504
- }) : new aws11.dynamodb.Table(tableName, tableConfig, {
16767
+ }) : new aws12.dynamodb.Table(tableName, tableConfig, {
16505
16768
  customTimeouts: { create: "5m", update: "5m", delete: "5m" }
16506
16769
  });
16507
16770
  }
16508
16771
  async function deploySMSLambdaFunction(config2) {
16509
16772
  const { getLambdaCode: getLambdaCode2 } = await Promise.resolve().then(() => (init_lambda(), lambda_exports));
16510
16773
  const codeDir = await getLambdaCode2("sms-event-processor");
16511
- const lambdaRole = new aws11.iam.Role("wraps-sms-lambda-role", {
16774
+ const lambdaRole = new aws12.iam.Role("wraps-sms-lambda-role", {
16512
16775
  name: "wraps-sms-lambda-role",
16513
16776
  assumeRolePolicy: JSON.stringify({
16514
16777
  Version: "2012-10-17",
@@ -16525,11 +16788,11 @@ async function deploySMSLambdaFunction(config2) {
16525
16788
  Service: "sms"
16526
16789
  }
16527
16790
  });
16528
- new aws11.iam.RolePolicyAttachment("wraps-sms-lambda-basic-execution", {
16791
+ new aws12.iam.RolePolicyAttachment("wraps-sms-lambda-basic-execution", {
16529
16792
  role: lambdaRole.name,
16530
16793
  policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
16531
16794
  });
16532
- new aws11.iam.RolePolicy("wraps-sms-lambda-policy", {
16795
+ new aws12.iam.RolePolicy("wraps-sms-lambda-policy", {
16533
16796
  role: lambdaRole.name,
16534
16797
  policy: pulumi14.all([config2.tableName, config2.queueArn]).apply(
16535
16798
  ([tableName, queueArn]) => JSON.stringify({
@@ -16561,7 +16824,7 @@ async function deploySMSLambdaFunction(config2) {
16561
16824
  })
16562
16825
  )
16563
16826
  });
16564
- const eventProcessor = new aws11.lambda.Function(
16827
+ const eventProcessor = new aws12.lambda.Function(
16565
16828
  "wraps-sms-event-processor",
16566
16829
  {
16567
16830
  name: "wraps-sms-event-processor",
@@ -16588,7 +16851,7 @@ async function deploySMSLambdaFunction(config2) {
16588
16851
  customTimeouts: { create: "5m", update: "5m", delete: "2m" }
16589
16852
  }
16590
16853
  );
16591
- new aws11.lambda.EventSourceMapping(
16854
+ new aws12.lambda.EventSourceMapping(
16592
16855
  "wraps-sms-event-source-mapping",
16593
16856
  {
16594
16857
  eventSourceArn: config2.queueArn,
@@ -16604,7 +16867,7 @@ async function deploySMSLambdaFunction(config2) {
16604
16867
  return eventProcessor;
16605
16868
  }
16606
16869
  async function deploySMSStack(config2) {
16607
- const identity = await aws11.getCallerIdentity();
16870
+ const identity = await aws12.getCallerIdentity();
16608
16871
  const accountId = identity.accountId;
16609
16872
  let oidcProvider;
16610
16873
  if (config2.provider === "vercel" && config2.vercel) {
@@ -20152,12 +20415,12 @@ import pc31 from "picocolors";
20152
20415
 
20153
20416
  // src/infrastructure/storage-stack.ts
20154
20417
  init_esm_shims();
20155
- import * as aws13 from "@pulumi/aws";
20418
+ import * as aws14 from "@pulumi/aws";
20156
20419
  import * as pulumi22 from "@pulumi/pulumi";
20157
20420
 
20158
20421
  // src/infrastructure/resources/s3-storage.ts
20159
20422
  init_esm_shims();
20160
- import * as aws12 from "@pulumi/aws";
20423
+ import * as aws13 from "@pulumi/aws";
20161
20424
  import * as pulumi21 from "@pulumi/pulumi";
20162
20425
  function retentionToDays(retention) {
20163
20426
  switch (retention) {
@@ -20179,7 +20442,7 @@ function retentionToDays(retention) {
20179
20442
  }
20180
20443
  async function createStorageBucket(config2) {
20181
20444
  const bucketName = config2.storageConfig.bucketName || `wraps-storage-${config2.accountId}`;
20182
- const bucket = new aws12.s3.BucketV2("wraps-storage-bucket", {
20445
+ const bucket = new aws13.s3.BucketV2("wraps-storage-bucket", {
20183
20446
  bucket: bucketName,
20184
20447
  tags: {
20185
20448
  ManagedBy: "wraps-cli",
@@ -20187,14 +20450,14 @@ async function createStorageBucket(config2) {
20187
20450
  }
20188
20451
  });
20189
20452
  if (config2.storageConfig.versioning) {
20190
- new aws12.s3.BucketVersioningV2("wraps-storage-versioning", {
20453
+ new aws13.s3.BucketVersioningV2("wraps-storage-versioning", {
20191
20454
  bucket: bucket.id,
20192
20455
  versioningConfiguration: {
20193
20456
  status: "Enabled"
20194
20457
  }
20195
20458
  });
20196
20459
  }
20197
- new aws12.s3.BucketServerSideEncryptionConfigurationV2(
20460
+ new aws13.s3.BucketServerSideEncryptionConfigurationV2(
20198
20461
  "wraps-storage-encryption",
20199
20462
  {
20200
20463
  bucket: bucket.id,
@@ -20220,7 +20483,7 @@ async function createStorageBucket(config2) {
20220
20483
  "http://localhost:8080",
20221
20484
  ...config2.storageConfig.additionalOrigins || []
20222
20485
  ];
20223
- new aws12.s3.BucketCorsConfigurationV2("wraps-storage-cors", {
20486
+ new aws13.s3.BucketCorsConfigurationV2("wraps-storage-cors", {
20224
20487
  bucket: bucket.id,
20225
20488
  corsRules: [
20226
20489
  {
@@ -20234,7 +20497,7 @@ async function createStorageBucket(config2) {
20234
20497
  });
20235
20498
  const retentionDays = config2.storageConfig.retention ? retentionToDays(config2.storageConfig.retention) : null;
20236
20499
  if (retentionDays) {
20237
- new aws12.s3.BucketLifecycleConfigurationV2("wraps-storage-lifecycle", {
20500
+ new aws13.s3.BucketLifecycleConfigurationV2("wraps-storage-lifecycle", {
20238
20501
  bucket: bucket.id,
20239
20502
  rules: [
20240
20503
  {
@@ -20247,7 +20510,7 @@ async function createStorageBucket(config2) {
20247
20510
  ]
20248
20511
  });
20249
20512
  }
20250
- new aws12.s3.BucketPublicAccessBlock("wraps-storage-public-access", {
20513
+ new aws13.s3.BucketPublicAccessBlock("wraps-storage-public-access", {
20251
20514
  bucket: bucket.id,
20252
20515
  blockPublicAcls: true,
20253
20516
  blockPublicPolicy: true,
@@ -20261,10 +20524,10 @@ async function createStorageBucket(config2) {
20261
20524
  };
20262
20525
  }
20263
20526
  async function createStorageWAF() {
20264
- const usEast1Provider = new aws12.Provider("storage-waf-us-east-1", {
20527
+ const usEast1Provider = new aws13.Provider("storage-waf-us-east-1", {
20265
20528
  region: "us-east-1"
20266
20529
  });
20267
- const webAcl = new aws12.wafv2.WebAcl(
20530
+ const webAcl = new aws13.wafv2.WebAcl(
20268
20531
  "wraps-storage-waf",
20269
20532
  {
20270
20533
  scope: "CLOUDFRONT",
@@ -20316,7 +20579,7 @@ async function createStorageWAF() {
20316
20579
  }
20317
20580
  async function createStorageCDN(config2) {
20318
20581
  const webAcl = config2.wafEnabled ? await createStorageWAF() : void 0;
20319
- const oac = new aws12.cloudfront.OriginAccessControl("wraps-storage-oac", {
20582
+ const oac = new aws13.cloudfront.OriginAccessControl("wraps-storage-oac", {
20320
20583
  name: "wraps-storage-oac",
20321
20584
  description: "OAC for Wraps storage S3 bucket",
20322
20585
  originAccessControlOriginType: "s3",
@@ -20351,7 +20614,7 @@ async function createStorageCDN(config2) {
20351
20614
  } : {
20352
20615
  restrictionType: "none"
20353
20616
  };
20354
- const distribution = new aws12.cloudfront.Distribution("wraps-storage-cdn", {
20617
+ const distribution = new aws13.cloudfront.Distribution("wraps-storage-cdn", {
20355
20618
  enabled: true,
20356
20619
  comment: "Wraps storage CDN",
20357
20620
  aliases,
@@ -20394,7 +20657,7 @@ async function createStorageCDN(config2) {
20394
20657
  Service: "storage"
20395
20658
  }
20396
20659
  });
20397
- new aws12.s3.BucketPolicy("wraps-storage-bucket-policy", {
20660
+ new aws13.s3.BucketPolicy("wraps-storage-bucket-policy", {
20398
20661
  bucket: config2.bucket.id,
20399
20662
  policy: pulumi21.interpolate`{
20400
20663
  "Version": "2012-10-17",
@@ -20423,10 +20686,10 @@ async function createStorageCDN(config2) {
20423
20686
  };
20424
20687
  }
20425
20688
  async function createStorageACMCertificate(config2) {
20426
- const usEast1Provider = new aws12.Provider("storage-acm-us-east-1", {
20689
+ const usEast1Provider = new aws13.Provider("storage-acm-us-east-1", {
20427
20690
  region: "us-east-1"
20428
20691
  });
20429
- const certificate = new aws12.acm.Certificate(
20692
+ const certificate = new aws13.acm.Certificate(
20430
20693
  "wraps-storage-cert",
20431
20694
  {
20432
20695
  domainName: config2.domain,
@@ -20450,7 +20713,7 @@ async function createStorageACMCertificate(config2) {
20450
20713
  );
20451
20714
  let certificateValidation;
20452
20715
  if (config2.hostedZoneId) {
20453
- const validationRecord = new aws12.route53.Record(
20716
+ const validationRecord = new aws13.route53.Record(
20454
20717
  "wraps-storage-cert-validation",
20455
20718
  {
20456
20719
  zoneId: config2.hostedZoneId,
@@ -20460,7 +20723,7 @@ async function createStorageACMCertificate(config2) {
20460
20723
  ttl: 60
20461
20724
  }
20462
20725
  );
20463
- certificateValidation = new aws12.acm.CertificateValidation(
20726
+ certificateValidation = new aws13.acm.CertificateValidation(
20464
20727
  "wraps-storage-cert-validation-waiter",
20465
20728
  {
20466
20729
  certificateArn: certificate.arn,
@@ -20482,10 +20745,10 @@ async function createStorageACMCertificate(config2) {
20482
20745
  async function roleExists3(roleName) {
20483
20746
  try {
20484
20747
  const { IAMClient: IAMClient3, GetRoleCommand: GetRoleCommand2 } = await import("@aws-sdk/client-iam");
20485
- const iam7 = new IAMClient3({
20748
+ const iam8 = new IAMClient3({
20486
20749
  region: process.env.AWS_REGION || "us-east-1"
20487
20750
  });
20488
- await iam7.send(new GetRoleCommand2({ RoleName: roleName }));
20751
+ await iam8.send(new GetRoleCommand2({ RoleName: roleName }));
20489
20752
  return true;
20490
20753
  } catch (error) {
20491
20754
  if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity") {
@@ -20532,7 +20795,7 @@ async function createStorageIAMRole(config2) {
20532
20795
  }
20533
20796
  const roleName = "wraps-storage-role";
20534
20797
  const exists = await roleExists3(roleName);
20535
- const role = exists ? new aws13.iam.Role(
20798
+ const role = exists ? new aws14.iam.Role(
20536
20799
  roleName,
20537
20800
  {
20538
20801
  name: roleName,
@@ -20546,7 +20809,7 @@ async function createStorageIAMRole(config2) {
20546
20809
  {
20547
20810
  import: roleName
20548
20811
  }
20549
- ) : new aws13.iam.Role(roleName, {
20812
+ ) : new aws14.iam.Role(roleName, {
20550
20813
  name: roleName,
20551
20814
  assumeRolePolicy,
20552
20815
  tags: {
@@ -20584,7 +20847,7 @@ async function createStorageIAMRole(config2) {
20584
20847
  Resource: config2.distributionArn
20585
20848
  });
20586
20849
  }
20587
- new aws13.iam.RolePolicy("wraps-storage-policy", {
20850
+ new aws14.iam.RolePolicy("wraps-storage-policy", {
20588
20851
  role: role.name,
20589
20852
  policy: pulumi22.all([statements]).apply(
20590
20853
  ([stmts]) => JSON.stringify({