@wraps.dev/cli 1.1.4 → 1.1.5

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
@@ -77,6 +77,7 @@ async function createDNSRecords(hostedZoneId, domain, dkimTokens, region, custom
77
77
  ResourceRecords: [{ Value: '"v=spf1 include:amazonses.com ~all"' }]
78
78
  }
79
79
  });
80
+ const dmarcReportEmail = mailFromDomain ? `postmaster@${mailFromDomain}` : `postmaster@${domain}`;
80
81
  changes.push({
81
82
  Action: "UPSERT",
82
83
  ResourceRecordSet: {
@@ -84,7 +85,7 @@ async function createDNSRecords(hostedZoneId, domain, dkimTokens, region, custom
84
85
  Type: "TXT",
85
86
  TTL: 1800,
86
87
  ResourceRecords: [
87
- { Value: `"v=DMARC1; p=quarantine; rua=mailto:postmaster@${domain}"` }
88
+ { Value: `"v=DMARC1; p=quarantine; rua=mailto:${dmarcReportEmail}"` }
88
89
  ]
89
90
  }
90
91
  });
@@ -755,6 +756,312 @@ var init_aws = __esm({
755
756
  }
756
757
  });
757
758
 
759
+ // src/utils/shared/fs.ts
760
+ import { existsSync as existsSync2 } from "fs";
761
+ import { mkdir } from "fs/promises";
762
+ import { homedir } from "os";
763
+ import { join as join2 } from "path";
764
+ function getWrapsDir() {
765
+ return join2(homedir(), ".wraps");
766
+ }
767
+ function getPulumiWorkDir() {
768
+ return join2(getWrapsDir(), "pulumi");
769
+ }
770
+ async function ensureWrapsDir() {
771
+ const wrapsDir = getWrapsDir();
772
+ if (!existsSync2(wrapsDir)) {
773
+ await mkdir(wrapsDir, { recursive: true });
774
+ }
775
+ }
776
+ async function ensurePulumiWorkDir() {
777
+ await ensureWrapsDir();
778
+ const pulumiDir = getPulumiWorkDir();
779
+ if (!existsSync2(pulumiDir)) {
780
+ await mkdir(pulumiDir, { recursive: true });
781
+ }
782
+ process.env.PULUMI_BACKEND_URL = `file://${pulumiDir}`;
783
+ process.env.PULUMI_CONFIG_PASSPHRASE = "";
784
+ }
785
+ var init_fs = __esm({
786
+ "src/utils/shared/fs.ts"() {
787
+ "use strict";
788
+ init_esm_shims();
789
+ }
790
+ });
791
+
792
+ // src/utils/shared/metadata.ts
793
+ var metadata_exports = {};
794
+ __export(metadata_exports, {
795
+ addServiceToConnection: () => addServiceToConnection,
796
+ applyConfigUpdates: () => applyConfigUpdates,
797
+ connectionExists: () => connectionExists,
798
+ createConnectionMetadata: () => createConnectionMetadata,
799
+ deleteConnectionMetadata: () => deleteConnectionMetadata,
800
+ getConfiguredServices: () => getConfiguredServices,
801
+ hasService: () => hasService,
802
+ listConnections: () => listConnections,
803
+ loadConnectionMetadata: () => loadConnectionMetadata,
804
+ removeServiceFromConnection: () => removeServiceFromConnection,
805
+ saveConnectionMetadata: () => saveConnectionMetadata,
806
+ updateEmailConfig: () => updateEmailConfig,
807
+ updateServiceConfig: () => updateServiceConfig
808
+ });
809
+ import { existsSync as existsSync3 } from "fs";
810
+ import { readFile, writeFile } from "fs/promises";
811
+ import { join as join3 } from "path";
812
+ function getConnectionsDir() {
813
+ return join3(getWrapsDir(), "connections");
814
+ }
815
+ function getMetadataPath(accountId, region) {
816
+ return join3(getConnectionsDir(), `${accountId}-${region}.json`);
817
+ }
818
+ async function ensureConnectionsDir() {
819
+ await ensureWrapsDir();
820
+ const connectionsDir = getConnectionsDir();
821
+ if (!existsSync3(connectionsDir)) {
822
+ const { mkdir: mkdir2 } = await import("fs/promises");
823
+ await mkdir2(connectionsDir, { recursive: true });
824
+ }
825
+ }
826
+ function migrateLegacyMetadata(legacy) {
827
+ return {
828
+ version: "1.0.0",
829
+ accountId: legacy.accountId,
830
+ region: legacy.region,
831
+ provider: legacy.provider,
832
+ timestamp: legacy.timestamp,
833
+ vercel: legacy.vercel,
834
+ services: {
835
+ email: {
836
+ preset: legacy.preset,
837
+ config: legacy.emailConfig,
838
+ pulumiStackName: legacy.pulumiStackName,
839
+ deployedAt: legacy.timestamp
840
+ }
841
+ }
842
+ };
843
+ }
844
+ function isLegacyMetadata(data) {
845
+ return "emailConfig" in data && !("services" in data) && typeof data.emailConfig === "object";
846
+ }
847
+ async function loadConnectionMetadata(accountId, region) {
848
+ const metadataPath = getMetadataPath(accountId, region);
849
+ if (!existsSync3(metadataPath)) {
850
+ return null;
851
+ }
852
+ try {
853
+ const content = await readFile(metadataPath, "utf-8");
854
+ const data = JSON.parse(content);
855
+ if (isLegacyMetadata(data)) {
856
+ const migrated = migrateLegacyMetadata(data);
857
+ await saveConnectionMetadata(migrated);
858
+ return migrated;
859
+ }
860
+ if (!data.version) {
861
+ data.version = "1.0.0";
862
+ await saveConnectionMetadata(data);
863
+ }
864
+ return data;
865
+ } catch (error) {
866
+ console.error("Error loading connection metadata:", error.message);
867
+ return null;
868
+ }
869
+ }
870
+ async function saveConnectionMetadata(metadata) {
871
+ await ensureConnectionsDir();
872
+ const metadataPath = getMetadataPath(metadata.accountId, metadata.region);
873
+ try {
874
+ const content = JSON.stringify(metadata, null, 2);
875
+ await writeFile(metadataPath, content, "utf-8");
876
+ } catch (error) {
877
+ console.error("Error saving connection metadata:", error.message);
878
+ throw error;
879
+ }
880
+ }
881
+ async function deleteConnectionMetadata(accountId, region) {
882
+ const metadataPath = getMetadataPath(accountId, region);
883
+ if (existsSync3(metadataPath)) {
884
+ const { unlink } = await import("fs/promises");
885
+ await unlink(metadataPath);
886
+ }
887
+ }
888
+ async function listConnections() {
889
+ const connectionsDir = getConnectionsDir();
890
+ if (!existsSync3(connectionsDir)) {
891
+ return [];
892
+ }
893
+ try {
894
+ const { readdir } = await import("fs/promises");
895
+ const files = await readdir(connectionsDir);
896
+ const connections = [];
897
+ for (const file of files) {
898
+ if (file.endsWith(".json")) {
899
+ const content = await readFile(join3(connectionsDir, file), "utf-8");
900
+ try {
901
+ const metadata = JSON.parse(content);
902
+ connections.push(metadata);
903
+ } catch (error) {
904
+ console.error(`Error parsing ${file}:`, error);
905
+ }
906
+ }
907
+ }
908
+ return connections;
909
+ } catch (error) {
910
+ console.error("Error listing connections:", error.message);
911
+ return [];
912
+ }
913
+ }
914
+ async function connectionExists(accountId, region) {
915
+ const metadataPath = getMetadataPath(accountId, region);
916
+ return existsSync3(metadataPath);
917
+ }
918
+ function createConnectionMetadata(accountId, region, provider, emailConfig, preset) {
919
+ return {
920
+ version: "1.0.0",
921
+ accountId,
922
+ region,
923
+ provider,
924
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
925
+ services: {
926
+ email: {
927
+ preset,
928
+ config: emailConfig,
929
+ deployedAt: (/* @__PURE__ */ new Date()).toISOString()
930
+ }
931
+ }
932
+ };
933
+ }
934
+ function applyConfigUpdates(existingConfig, updates) {
935
+ const result = { ...existingConfig };
936
+ for (const [key, value] of Object.entries(updates)) {
937
+ if (value === void 0) continue;
938
+ if (key === "tracking" && typeof value === "object") {
939
+ const trackingUpdate = value;
940
+ result.tracking = {
941
+ ...result.tracking,
942
+ ...trackingUpdate,
943
+ // Always preserve these if they exist in original
944
+ customRedirectDomain: result.tracking?.customRedirectDomain || trackingUpdate.customRedirectDomain,
945
+ httpsEnabled: result.tracking?.httpsEnabled ?? trackingUpdate.httpsEnabled
946
+ };
947
+ } else if (key === "eventTracking" && typeof value === "object") {
948
+ result.eventTracking = {
949
+ ...result.eventTracking,
950
+ ...value
951
+ };
952
+ } else if (key === "suppressionList" && typeof value === "object") {
953
+ result.suppressionList = {
954
+ ...result.suppressionList,
955
+ ...value
956
+ };
957
+ } else if (key === "emailArchiving" && typeof value === "object") {
958
+ result.emailArchiving = {
959
+ ...result.emailArchiving,
960
+ ...value
961
+ };
962
+ } else {
963
+ result[key] = value;
964
+ }
965
+ }
966
+ return result;
967
+ }
968
+ function updateEmailConfig(metadata, emailConfig) {
969
+ if (!metadata.services.email) {
970
+ throw new Error("Email service not configured in metadata");
971
+ }
972
+ metadata.services.email.config = applyConfigUpdates(
973
+ metadata.services.email.config,
974
+ emailConfig
975
+ );
976
+ metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
977
+ }
978
+ function addServiceToConnection(accountId, region, provider, service, config2, preset, existingMetadata) {
979
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
980
+ if (existingMetadata) {
981
+ if (service === "email") {
982
+ existingMetadata.services.email = {
983
+ preset,
984
+ config: config2,
985
+ deployedAt: timestamp
986
+ };
987
+ } else if (service === "sms") {
988
+ existingMetadata.services.sms = {
989
+ preset,
990
+ config: config2,
991
+ deployedAt: timestamp
992
+ };
993
+ }
994
+ existingMetadata.timestamp = timestamp;
995
+ return existingMetadata;
996
+ }
997
+ const metadata = {
998
+ version: "1.0.0",
999
+ accountId,
1000
+ region,
1001
+ provider,
1002
+ timestamp,
1003
+ services: {}
1004
+ };
1005
+ if (service === "email") {
1006
+ metadata.services.email = {
1007
+ preset,
1008
+ config: config2,
1009
+ deployedAt: timestamp
1010
+ };
1011
+ } else if (service === "sms") {
1012
+ metadata.services.sms = {
1013
+ preset,
1014
+ config: config2,
1015
+ deployedAt: timestamp
1016
+ };
1017
+ }
1018
+ return metadata;
1019
+ }
1020
+ function updateServiceConfig(metadata, service, config2) {
1021
+ if (service === "email" && metadata.services.email) {
1022
+ metadata.services.email.config = {
1023
+ ...metadata.services.email.config,
1024
+ ...config2
1025
+ };
1026
+ } else if (service === "sms" && metadata.services.sms) {
1027
+ metadata.services.sms.config = {
1028
+ ...metadata.services.sms.config,
1029
+ ...config2
1030
+ };
1031
+ } else {
1032
+ throw new Error(`${service} service not configured in metadata`);
1033
+ }
1034
+ metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
1035
+ }
1036
+ function removeServiceFromConnection(metadata, service) {
1037
+ if (service === "email") {
1038
+ const { email, ...rest } = metadata.services;
1039
+ metadata.services = rest;
1040
+ } else if (service === "sms") {
1041
+ const { sms, ...rest } = metadata.services;
1042
+ metadata.services = rest;
1043
+ }
1044
+ metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
1045
+ }
1046
+ function hasService(metadata, service) {
1047
+ if (service === "email") return metadata.services.email !== void 0;
1048
+ if (service === "sms") return metadata.services.sms !== void 0;
1049
+ return false;
1050
+ }
1051
+ function getConfiguredServices(metadata) {
1052
+ const services = [];
1053
+ if (metadata.services.email) services.push("email");
1054
+ if (metadata.services.sms) services.push("sms");
1055
+ return services;
1056
+ }
1057
+ var init_metadata = __esm({
1058
+ "src/utils/shared/metadata.ts"() {
1059
+ "use strict";
1060
+ init_esm_shims();
1061
+ init_fs();
1062
+ }
1063
+ });
1064
+
758
1065
  // src/utils/email/costs.ts
759
1066
  function estimateStorageSize(emailsPerMonth, retention, numEventTypes = 8) {
760
1067
  const avgRecordSizeKB = 2;
@@ -1712,12 +2019,12 @@ async function promptEmailArchiving() {
1712
2019
  retention
1713
2020
  };
1714
2021
  }
1715
- async function promptCustomConfig() {
2022
+ async function promptCustomConfig(existingConfig) {
1716
2023
  clack4.log.info("Custom configuration builder");
1717
2024
  clack4.log.info("Configure each feature individually");
1718
2025
  const trackingEnabled = await clack4.confirm({
1719
2026
  message: "Enable open & click tracking?",
1720
- initialValue: true
2027
+ initialValue: existingConfig?.tracking?.enabled ?? true
1721
2028
  });
1722
2029
  if (clack4.isCancel(trackingEnabled)) {
1723
2030
  clack4.cancel("Operation cancelled.");
@@ -1725,7 +2032,7 @@ async function promptCustomConfig() {
1725
2032
  }
1726
2033
  const eventTrackingEnabled = await clack4.confirm({
1727
2034
  message: "Enable real-time event tracking (EventBridge)?",
1728
- initialValue: true
2035
+ initialValue: existingConfig?.eventTracking?.enabled ?? true
1729
2036
  });
1730
2037
  if (clack4.isCancel(eventTrackingEnabled)) {
1731
2038
  clack4.cancel("Operation cancelled.");
@@ -1736,7 +2043,7 @@ async function promptCustomConfig() {
1736
2043
  if (eventTrackingEnabled) {
1737
2044
  dynamoDBHistory = await clack4.confirm({
1738
2045
  message: "Store email history in DynamoDB?",
1739
- initialValue: true
2046
+ initialValue: existingConfig?.eventTracking?.dynamoDBHistory ?? true
1740
2047
  });
1741
2048
  if (clack4.isCancel(dynamoDBHistory)) {
1742
2049
  clack4.cancel("Operation cancelled.");
@@ -1759,7 +2066,8 @@ async function promptCustomConfig() {
1759
2066
  label: "Indefinite",
1760
2067
  hint: "Higher storage cost"
1761
2068
  }
1762
- ]
2069
+ ],
2070
+ initialValue: existingConfig?.eventTracking?.archiveRetention || "90days"
1763
2071
  });
1764
2072
  if (clack4.isCancel(archiveRetention)) {
1765
2073
  clack4.cancel("Operation cancelled.");
@@ -1769,7 +2077,7 @@ async function promptCustomConfig() {
1769
2077
  }
1770
2078
  const tlsRequired = await clack4.confirm({
1771
2079
  message: "Require TLS encryption for all emails?",
1772
- initialValue: true
2080
+ initialValue: existingConfig?.tlsRequired ?? true
1773
2081
  });
1774
2082
  if (clack4.isCancel(tlsRequired)) {
1775
2083
  clack4.cancel("Operation cancelled.");
@@ -1777,7 +2085,7 @@ async function promptCustomConfig() {
1777
2085
  }
1778
2086
  const reputationMetrics = await clack4.confirm({
1779
2087
  message: "Enable reputation metrics dashboard?",
1780
- initialValue: true
2088
+ initialValue: existingConfig?.reputationMetrics ?? true
1781
2089
  });
1782
2090
  if (clack4.isCancel(reputationMetrics)) {
1783
2091
  clack4.cancel("Operation cancelled.");
@@ -1785,7 +2093,7 @@ async function promptCustomConfig() {
1785
2093
  }
1786
2094
  const dedicatedIp = await clack4.confirm({
1787
2095
  message: "Request dedicated IP address? (requires 100k+ emails/day)",
1788
- initialValue: false
2096
+ initialValue: existingConfig?.dedicatedIp ?? false
1789
2097
  });
1790
2098
  if (clack4.isCancel(dedicatedIp)) {
1791
2099
  clack4.cancel("Operation cancelled.");
@@ -1793,7 +2101,7 @@ async function promptCustomConfig() {
1793
2101
  }
1794
2102
  const emailArchivingEnabled = await clack4.confirm({
1795
2103
  message: "Enable email archiving? (Store full email content with HTML for viewing)",
1796
- initialValue: false
2104
+ initialValue: existingConfig?.emailArchiving?.enabled ?? false
1797
2105
  });
1798
2106
  if (clack4.isCancel(emailArchivingEnabled)) {
1799
2107
  clack4.cancel("Operation cancelled.");
@@ -1823,7 +2131,7 @@ async function promptCustomConfig() {
1823
2131
  hint: "~$35-60/mo for 10k emails"
1824
2132
  }
1825
2133
  ],
1826
- initialValue: "90days"
2134
+ initialValue: existingConfig?.emailArchiving?.retention || "90days"
1827
2135
  });
1828
2136
  if (clack4.isCancel(emailArchiveRetention)) {
1829
2137
  clack4.cancel("Operation cancelled.");
@@ -2790,27 +3098,13 @@ async function emailIdentityExists(emailIdentity, region) {
2790
3098
  return false;
2791
3099
  }
2792
3100
  }
2793
- async function eventDestinationExists(configSetName, eventDestName, region) {
2794
- try {
2795
- const { SESv2Client: SESv2Client5, GetConfigurationSetEventDestinationsCommand } = await import("@aws-sdk/client-sesv2");
2796
- const ses = new SESv2Client5({ region });
2797
- const response = await ses.send(
2798
- new GetConfigurationSetEventDestinationsCommand({
2799
- ConfigurationSetName: configSetName
2800
- })
2801
- );
2802
- return response.EventDestinations?.some((dest) => dest.Name === eventDestName) ?? false;
2803
- } catch (error) {
2804
- if (error.name === "NotFoundException") {
2805
- return false;
2806
- }
2807
- console.error("Error checking for existing event destination:", error);
2808
- return false;
2809
- }
2810
- }
2811
3101
  async function createSESResources(config2) {
2812
3102
  const configSetOptions = {
2813
3103
  configurationSetName: "wraps-email-tracking",
3104
+ deliveryOptions: config2.tlsRequired ? {
3105
+ tlsPolicy: "REQUIRE"
3106
+ // Require TLS 1.2+ for all emails
3107
+ } : void 0,
2814
3108
  tags: {
2815
3109
  ManagedBy: "wraps-cli",
2816
3110
  Description: "Wraps email tracking configuration set"
@@ -2834,13 +3128,8 @@ async function createSESResources(config2) {
2834
3128
  const defaultEventBus = aws5.cloudwatch.getEventBusOutput({
2835
3129
  name: "default"
2836
3130
  });
2837
- const eventDestName = "wraps-email-eventbridge";
2838
- const eventDestExists = await eventDestinationExists(
2839
- configSetName,
2840
- eventDestName,
2841
- config2.region
2842
- );
2843
- if (!eventDestExists) {
3131
+ if (config2.eventTrackingEnabled) {
3132
+ const eventDestName = "wraps-email-eventbridge";
2844
3133
  new aws5.sesv2.ConfigurationSetEventDestination("wraps-email-all-events", {
2845
3134
  configurationSetName: configSet.configurationSetName,
2846
3135
  eventDestinationName: eventDestName,
@@ -3068,7 +3357,9 @@ async function deployEmailStack(config2) {
3068
3357
  cloudFrontResources = await createCloudFrontTracking2({
3069
3358
  customTrackingDomain: emailConfig.tracking.customRedirectDomain,
3070
3359
  region: config2.region,
3071
- certificateArn
3360
+ certificateArn,
3361
+ hostedZoneId: hostedZone?.id
3362
+ // Pass hosted zone ID for automatic DNS record creation
3072
3363
  });
3073
3364
  }
3074
3365
  let sesResources;
@@ -3078,7 +3369,11 @@ async function deployEmailStack(config2) {
3078
3369
  mailFromDomain: emailConfig.mailFromDomain,
3079
3370
  region: config2.region,
3080
3371
  trackingConfig: emailConfig.tracking,
3081
- eventTypes: emailConfig.eventTracking?.events
3372
+ eventTypes: emailConfig.eventTracking?.events,
3373
+ eventTrackingEnabled: emailConfig.eventTracking?.enabled,
3374
+ // Pass flag to create EventBridge destination
3375
+ tlsRequired: emailConfig.tlsRequired
3376
+ // Require TLS encryption for all emails
3082
3377
  });
3083
3378
  }
3084
3379
  let dynamoTables;
@@ -3143,142 +3438,8 @@ async function deployEmailStack(config2) {
3143
3438
 
3144
3439
  // src/commands/email/config.ts
3145
3440
  init_aws();
3146
-
3147
- // src/utils/shared/fs.ts
3148
- init_esm_shims();
3149
- import { existsSync as existsSync2 } from "fs";
3150
- import { mkdir } from "fs/promises";
3151
- import { homedir } from "os";
3152
- import { join as join2 } from "path";
3153
- function getWrapsDir() {
3154
- return join2(homedir(), ".wraps");
3155
- }
3156
- function getPulumiWorkDir() {
3157
- return join2(getWrapsDir(), "pulumi");
3158
- }
3159
- async function ensureWrapsDir() {
3160
- const wrapsDir = getWrapsDir();
3161
- if (!existsSync2(wrapsDir)) {
3162
- await mkdir(wrapsDir, { recursive: true });
3163
- }
3164
- }
3165
- async function ensurePulumiWorkDir() {
3166
- await ensureWrapsDir();
3167
- const pulumiDir = getPulumiWorkDir();
3168
- if (!existsSync2(pulumiDir)) {
3169
- await mkdir(pulumiDir, { recursive: true });
3170
- }
3171
- process.env.PULUMI_BACKEND_URL = `file://${pulumiDir}`;
3172
- process.env.PULUMI_CONFIG_PASSPHRASE = "";
3173
- }
3174
-
3175
- // src/utils/shared/metadata.ts
3176
- init_esm_shims();
3177
- import { existsSync as existsSync3 } from "fs";
3178
- import { readFile, writeFile } from "fs/promises";
3179
- import { join as join3 } from "path";
3180
- function getConnectionsDir() {
3181
- return join3(getWrapsDir(), "connections");
3182
- }
3183
- function getMetadataPath(accountId, region) {
3184
- return join3(getConnectionsDir(), `${accountId}-${region}.json`);
3185
- }
3186
- async function ensureConnectionsDir() {
3187
- await ensureWrapsDir();
3188
- const connectionsDir = getConnectionsDir();
3189
- if (!existsSync3(connectionsDir)) {
3190
- const { mkdir: mkdir2 } = await import("fs/promises");
3191
- await mkdir2(connectionsDir, { recursive: true });
3192
- }
3193
- }
3194
- function migrateLegacyMetadata(legacy) {
3195
- return {
3196
- version: "1.0.0",
3197
- accountId: legacy.accountId,
3198
- region: legacy.region,
3199
- provider: legacy.provider,
3200
- timestamp: legacy.timestamp,
3201
- vercel: legacy.vercel,
3202
- services: {
3203
- email: {
3204
- preset: legacy.preset,
3205
- config: legacy.emailConfig,
3206
- pulumiStackName: legacy.pulumiStackName,
3207
- deployedAt: legacy.timestamp
3208
- }
3209
- }
3210
- };
3211
- }
3212
- function isLegacyMetadata(data) {
3213
- return "emailConfig" in data && !("services" in data) && typeof data.emailConfig === "object";
3214
- }
3215
- async function loadConnectionMetadata(accountId, region) {
3216
- const metadataPath = getMetadataPath(accountId, region);
3217
- if (!existsSync3(metadataPath)) {
3218
- return null;
3219
- }
3220
- try {
3221
- const content = await readFile(metadataPath, "utf-8");
3222
- const data = JSON.parse(content);
3223
- if (isLegacyMetadata(data)) {
3224
- const migrated = migrateLegacyMetadata(data);
3225
- await saveConnectionMetadata(migrated);
3226
- return migrated;
3227
- }
3228
- if (!data.version) {
3229
- data.version = "1.0.0";
3230
- await saveConnectionMetadata(data);
3231
- }
3232
- return data;
3233
- } catch (error) {
3234
- console.error("Error loading connection metadata:", error.message);
3235
- return null;
3236
- }
3237
- }
3238
- async function saveConnectionMetadata(metadata) {
3239
- await ensureConnectionsDir();
3240
- const metadataPath = getMetadataPath(metadata.accountId, metadata.region);
3241
- try {
3242
- const content = JSON.stringify(metadata, null, 2);
3243
- await writeFile(metadataPath, content, "utf-8");
3244
- } catch (error) {
3245
- console.error("Error saving connection metadata:", error.message);
3246
- throw error;
3247
- }
3248
- }
3249
- async function deleteConnectionMetadata(accountId, region) {
3250
- const metadataPath = getMetadataPath(accountId, region);
3251
- if (existsSync3(metadataPath)) {
3252
- const { unlink } = await import("fs/promises");
3253
- await unlink(metadataPath);
3254
- }
3255
- }
3256
- function createConnectionMetadata(accountId, region, provider, emailConfig, preset) {
3257
- return {
3258
- version: "1.0.0",
3259
- accountId,
3260
- region,
3261
- provider,
3262
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3263
- services: {
3264
- email: {
3265
- preset,
3266
- config: emailConfig,
3267
- deployedAt: (/* @__PURE__ */ new Date()).toISOString()
3268
- }
3269
- }
3270
- };
3271
- }
3272
- function updateEmailConfig(metadata, emailConfig) {
3273
- if (!metadata.services.email) {
3274
- throw new Error("Email service not configured in metadata");
3275
- }
3276
- metadata.services.email.config = {
3277
- ...metadata.services.email.config,
3278
- ...emailConfig
3279
- };
3280
- metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
3281
- }
3441
+ init_fs();
3442
+ init_metadata();
3282
3443
 
3283
3444
  // src/utils/shared/output.ts
3284
3445
  init_esm_shims();
@@ -3853,6 +4014,8 @@ import * as pulumi6 from "@pulumi/pulumi";
3853
4014
  import pc5 from "picocolors";
3854
4015
  init_presets();
3855
4016
  init_aws();
4017
+ init_fs();
4018
+ init_metadata();
3856
4019
  init_prompts();
3857
4020
 
3858
4021
  // src/utils/shared/scanner.ts
@@ -4236,6 +4399,32 @@ async function connect(options) {
4236
4399
  }
4237
4400
  throw new Error(`Pulumi deployment failed: ${error.message}`);
4238
4401
  }
4402
+ if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
4403
+ const { findHostedZone: findHostedZone2, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
4404
+ const hostedZone = await findHostedZone2(outputs.domain, region);
4405
+ if (hostedZone) {
4406
+ try {
4407
+ progress.start("Creating DNS records in Route53");
4408
+ const mailFromDomain = emailConfig.mailFromDomain || `mail.${outputs.domain}`;
4409
+ await createDNSRecords2(
4410
+ hostedZone.id,
4411
+ outputs.domain,
4412
+ outputs.dkimTokens,
4413
+ region,
4414
+ outputs.customTrackingDomain,
4415
+ mailFromDomain
4416
+ );
4417
+ progress.succeed("DNS records created in Route53");
4418
+ } catch (error) {
4419
+ progress.fail(
4420
+ `Failed to create DNS records automatically: ${error.message}`
4421
+ );
4422
+ progress.info(
4423
+ "You can manually add the required DNS records shown below"
4424
+ );
4425
+ }
4426
+ }
4427
+ }
4239
4428
  const metadata = createConnectionMetadata(
4240
4429
  identity.accountId,
4241
4430
  region,
@@ -4704,6 +4893,8 @@ import pc7 from "picocolors";
4704
4893
  init_costs();
4705
4894
  init_presets();
4706
4895
  init_aws();
4896
+ init_fs();
4897
+ init_metadata();
4707
4898
  init_prompts();
4708
4899
  async function init(options) {
4709
4900
  clack7.intro(pc7.bold("Wraps Email Infrastructure Setup"));
@@ -4927,6 +5118,8 @@ ${pc7.yellow(pc7.bold("Configuration Warnings:"))}`);
4927
5118
  // src/commands/email/restore.ts
4928
5119
  init_esm_shims();
4929
5120
  init_aws();
5121
+ init_fs();
5122
+ init_metadata();
4930
5123
  import * as clack8 from "@clack/prompts";
4931
5124
  import * as pulumi8 from "@pulumi/pulumi";
4932
5125
  import pc8 from "picocolors";
@@ -5043,6 +5236,8 @@ import pc9 from "picocolors";
5043
5236
  init_costs();
5044
5237
  init_presets();
5045
5238
  init_aws();
5239
+ init_fs();
5240
+ init_metadata();
5046
5241
  init_prompts();
5047
5242
  async function upgrade(options) {
5048
5243
  clack9.intro(pc9.bold("Wraps Upgrade - Enhance Your Email Infrastructure"));
@@ -5210,12 +5405,9 @@ ${pc9.bold("Current Configuration:")}
5210
5405
  clack9.cancel("Upgrade cancelled.");
5211
5406
  process.exit(0);
5212
5407
  }
5408
+ const { applyConfigUpdates: applyConfigUpdates2 } = await Promise.resolve().then(() => (init_metadata(), metadata_exports));
5213
5409
  const presetConfig = getPreset(selectedPreset);
5214
- updatedConfig = {
5215
- ...presetConfig,
5216
- domain: config2.domain
5217
- // Preserve original domain
5218
- };
5410
+ updatedConfig = applyConfigUpdates2(config2, presetConfig);
5219
5411
  newPreset = selectedPreset;
5220
5412
  break;
5221
5413
  }
@@ -5648,11 +5840,9 @@ ${pc9.bold("Current Configuration:")}
5648
5840
  }
5649
5841
  case "custom": {
5650
5842
  const { promptCustomConfig: promptCustomConfig2 } = await Promise.resolve().then(() => (init_prompts(), prompts_exports));
5651
- const customConfig = await promptCustomConfig2();
5652
- updatedConfig = {
5653
- ...customConfig,
5654
- domain: config2.domain
5655
- };
5843
+ const { applyConfigUpdates: applyConfigUpdates2 } = await Promise.resolve().then(() => (init_metadata(), metadata_exports));
5844
+ const customConfig = await promptCustomConfig2(config2);
5845
+ updatedConfig = applyConfigUpdates2(config2, customConfig);
5656
5846
  newPreset = void 0;
5657
5847
  break;
5658
5848
  }
@@ -5773,6 +5963,33 @@ ${pc9.bold("Cost Impact:")}`);
5773
5963
  }
5774
5964
  throw new Error(`Pulumi upgrade failed: ${error.message}`);
5775
5965
  }
5966
+ if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
5967
+ const { findHostedZone: findHostedZone2, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
5968
+ const hostedZone = await findHostedZone2(outputs.domain, region);
5969
+ if (hostedZone) {
5970
+ try {
5971
+ progress.start("Creating DNS records in Route53");
5972
+ const mailFromDomain = updatedConfig.mailFromDomain || `mail.${outputs.domain}`;
5973
+ await createDNSRecords2(
5974
+ hostedZone.id,
5975
+ outputs.domain,
5976
+ outputs.dkimTokens,
5977
+ region,
5978
+ outputs.customTrackingDomain,
5979
+ mailFromDomain,
5980
+ outputs.cloudFrontDomain
5981
+ );
5982
+ progress.succeed("DNS records created in Route53");
5983
+ } catch (error) {
5984
+ progress.fail(
5985
+ `Failed to create DNS records automatically: ${error.message}`
5986
+ );
5987
+ progress.info(
5988
+ "You can manually add the required DNS records shown below"
5989
+ );
5990
+ }
5991
+ }
5992
+ }
5776
5993
  updateEmailConfig(metadata, updatedConfig);
5777
5994
  if (metadata.services.email) {
5778
5995
  metadata.services.email.preset = newPreset;
@@ -6592,6 +6809,7 @@ function createMetricsRouter(config2) {
6592
6809
 
6593
6810
  // src/console/routes/settings.ts
6594
6811
  init_esm_shims();
6812
+ init_metadata();
6595
6813
  import dns from "dns/promises";
6596
6814
  import { Router as createRouter4 } from "express";
6597
6815
 
@@ -6934,6 +7152,7 @@ function createSettingsRouter(config2) {
6934
7152
 
6935
7153
  // src/console/routes/user.ts
6936
7154
  init_esm_shims();
7155
+ init_metadata();
6937
7156
  import { Router as createRouter5 } from "express";
6938
7157
  function createUserRouter(config2) {
6939
7158
  const router = createRouter5();
@@ -7079,6 +7298,7 @@ async function startConsoleServer(config2) {
7079
7298
 
7080
7299
  // src/commands/shared/dashboard.ts
7081
7300
  init_aws();
7301
+ init_fs();
7082
7302
  async function dashboard(options) {
7083
7303
  clack10.intro(pc10.bold("Wraps Dashboard"));
7084
7304
  const progress = new DeploymentProgress();
@@ -7135,6 +7355,8 @@ async function dashboard(options) {
7135
7355
  // src/commands/shared/destroy.ts
7136
7356
  init_esm_shims();
7137
7357
  init_aws();
7358
+ init_fs();
7359
+ init_metadata();
7138
7360
  import * as clack11 from "@clack/prompts";
7139
7361
  import * as pulumi11 from "@pulumi/pulumi";
7140
7362
  import pc11 from "picocolors";
@@ -7201,6 +7423,7 @@ Run ${pc11.cyan("wraps email init")} to deploy infrastructure again.
7201
7423
  // src/commands/shared/status.ts
7202
7424
  init_esm_shims();
7203
7425
  init_aws();
7426
+ init_fs();
7204
7427
  import * as clack12 from "@clack/prompts";
7205
7428
  import * as pulumi12 from "@pulumi/pulumi";
7206
7429
  import pc12 from "picocolors";