@wraps.dev/cli 1.1.4 → 1.1.6
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 +412 -184
- package/dist/cli.js.map +1 -1
- package/dist/lambda/event-processor/.bundled +1 -1
- package/package.json +1 -1
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
|
|
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,18 @@ 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,
|
|
3108
|
+
suppressionOptions: {
|
|
3109
|
+
// Automatically suppress hard bounces and complaints at the configuration set level
|
|
3110
|
+
// This provides protection even if account-level suppression isn't configured
|
|
3111
|
+
suppressedReasons: ["BOUNCE", "COMPLAINT"]
|
|
3112
|
+
},
|
|
2814
3113
|
tags: {
|
|
2815
3114
|
ManagedBy: "wraps-cli",
|
|
2816
3115
|
Description: "Wraps email tracking configuration set"
|
|
@@ -2834,13 +3133,8 @@ async function createSESResources(config2) {
|
|
|
2834
3133
|
const defaultEventBus = aws5.cloudwatch.getEventBusOutput({
|
|
2835
3134
|
name: "default"
|
|
2836
3135
|
});
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
configSetName,
|
|
2840
|
-
eventDestName,
|
|
2841
|
-
config2.region
|
|
2842
|
-
);
|
|
2843
|
-
if (!eventDestExists) {
|
|
3136
|
+
if (config2.eventTrackingEnabled) {
|
|
3137
|
+
const eventDestName = "wraps-email-eventbridge";
|
|
2844
3138
|
new aws5.sesv2.ConfigurationSetEventDestination("wraps-email-all-events", {
|
|
2845
3139
|
configurationSetName: configSet.configurationSetName,
|
|
2846
3140
|
eventDestinationName: eventDestName,
|
|
@@ -3068,7 +3362,9 @@ async function deployEmailStack(config2) {
|
|
|
3068
3362
|
cloudFrontResources = await createCloudFrontTracking2({
|
|
3069
3363
|
customTrackingDomain: emailConfig.tracking.customRedirectDomain,
|
|
3070
3364
|
region: config2.region,
|
|
3071
|
-
certificateArn
|
|
3365
|
+
certificateArn,
|
|
3366
|
+
hostedZoneId: hostedZone?.id
|
|
3367
|
+
// Pass hosted zone ID for automatic DNS record creation
|
|
3072
3368
|
});
|
|
3073
3369
|
}
|
|
3074
3370
|
let sesResources;
|
|
@@ -3078,7 +3374,11 @@ async function deployEmailStack(config2) {
|
|
|
3078
3374
|
mailFromDomain: emailConfig.mailFromDomain,
|
|
3079
3375
|
region: config2.region,
|
|
3080
3376
|
trackingConfig: emailConfig.tracking,
|
|
3081
|
-
eventTypes: emailConfig.eventTracking?.events
|
|
3377
|
+
eventTypes: emailConfig.eventTracking?.events,
|
|
3378
|
+
eventTrackingEnabled: emailConfig.eventTracking?.enabled,
|
|
3379
|
+
// Pass flag to create EventBridge destination
|
|
3380
|
+
tlsRequired: emailConfig.tlsRequired
|
|
3381
|
+
// Require TLS encryption for all emails
|
|
3082
3382
|
});
|
|
3083
3383
|
}
|
|
3084
3384
|
let dynamoTables;
|
|
@@ -3143,142 +3443,8 @@ async function deployEmailStack(config2) {
|
|
|
3143
3443
|
|
|
3144
3444
|
// src/commands/email/config.ts
|
|
3145
3445
|
init_aws();
|
|
3146
|
-
|
|
3147
|
-
|
|
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
|
-
}
|
|
3446
|
+
init_fs();
|
|
3447
|
+
init_metadata();
|
|
3282
3448
|
|
|
3283
3449
|
// src/utils/shared/output.ts
|
|
3284
3450
|
init_esm_shims();
|
|
@@ -3853,6 +4019,8 @@ import * as pulumi6 from "@pulumi/pulumi";
|
|
|
3853
4019
|
import pc5 from "picocolors";
|
|
3854
4020
|
init_presets();
|
|
3855
4021
|
init_aws();
|
|
4022
|
+
init_fs();
|
|
4023
|
+
init_metadata();
|
|
3856
4024
|
init_prompts();
|
|
3857
4025
|
|
|
3858
4026
|
// src/utils/shared/scanner.ts
|
|
@@ -4236,6 +4404,32 @@ async function connect(options) {
|
|
|
4236
4404
|
}
|
|
4237
4405
|
throw new Error(`Pulumi deployment failed: ${error.message}`);
|
|
4238
4406
|
}
|
|
4407
|
+
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
4408
|
+
const { findHostedZone: findHostedZone2, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
4409
|
+
const hostedZone = await findHostedZone2(outputs.domain, region);
|
|
4410
|
+
if (hostedZone) {
|
|
4411
|
+
try {
|
|
4412
|
+
progress.start("Creating DNS records in Route53");
|
|
4413
|
+
const mailFromDomain = emailConfig.mailFromDomain || `mail.${outputs.domain}`;
|
|
4414
|
+
await createDNSRecords2(
|
|
4415
|
+
hostedZone.id,
|
|
4416
|
+
outputs.domain,
|
|
4417
|
+
outputs.dkimTokens,
|
|
4418
|
+
region,
|
|
4419
|
+
outputs.customTrackingDomain,
|
|
4420
|
+
mailFromDomain
|
|
4421
|
+
);
|
|
4422
|
+
progress.succeed("DNS records created in Route53");
|
|
4423
|
+
} catch (error) {
|
|
4424
|
+
progress.fail(
|
|
4425
|
+
`Failed to create DNS records automatically: ${error.message}`
|
|
4426
|
+
);
|
|
4427
|
+
progress.info(
|
|
4428
|
+
"You can manually add the required DNS records shown below"
|
|
4429
|
+
);
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4239
4433
|
const metadata = createConnectionMetadata(
|
|
4240
4434
|
identity.accountId,
|
|
4241
4435
|
region,
|
|
@@ -4704,6 +4898,8 @@ import pc7 from "picocolors";
|
|
|
4704
4898
|
init_costs();
|
|
4705
4899
|
init_presets();
|
|
4706
4900
|
init_aws();
|
|
4901
|
+
init_fs();
|
|
4902
|
+
init_metadata();
|
|
4707
4903
|
init_prompts();
|
|
4708
4904
|
async function init(options) {
|
|
4709
4905
|
clack7.intro(pc7.bold("Wraps Email Infrastructure Setup"));
|
|
@@ -4927,6 +5123,8 @@ ${pc7.yellow(pc7.bold("Configuration Warnings:"))}`);
|
|
|
4927
5123
|
// src/commands/email/restore.ts
|
|
4928
5124
|
init_esm_shims();
|
|
4929
5125
|
init_aws();
|
|
5126
|
+
init_fs();
|
|
5127
|
+
init_metadata();
|
|
4930
5128
|
import * as clack8 from "@clack/prompts";
|
|
4931
5129
|
import * as pulumi8 from "@pulumi/pulumi";
|
|
4932
5130
|
import pc8 from "picocolors";
|
|
@@ -5043,6 +5241,8 @@ import pc9 from "picocolors";
|
|
|
5043
5241
|
init_costs();
|
|
5044
5242
|
init_presets();
|
|
5045
5243
|
init_aws();
|
|
5244
|
+
init_fs();
|
|
5245
|
+
init_metadata();
|
|
5046
5246
|
init_prompts();
|
|
5047
5247
|
async function upgrade(options) {
|
|
5048
5248
|
clack9.intro(pc9.bold("Wraps Upgrade - Enhance Your Email Infrastructure"));
|
|
@@ -5210,12 +5410,9 @@ ${pc9.bold("Current Configuration:")}
|
|
|
5210
5410
|
clack9.cancel("Upgrade cancelled.");
|
|
5211
5411
|
process.exit(0);
|
|
5212
5412
|
}
|
|
5413
|
+
const { applyConfigUpdates: applyConfigUpdates2 } = await Promise.resolve().then(() => (init_metadata(), metadata_exports));
|
|
5213
5414
|
const presetConfig = getPreset(selectedPreset);
|
|
5214
|
-
updatedConfig =
|
|
5215
|
-
...presetConfig,
|
|
5216
|
-
domain: config2.domain
|
|
5217
|
-
// Preserve original domain
|
|
5218
|
-
};
|
|
5415
|
+
updatedConfig = applyConfigUpdates2(config2, presetConfig);
|
|
5219
5416
|
newPreset = selectedPreset;
|
|
5220
5417
|
break;
|
|
5221
5418
|
}
|
|
@@ -5648,11 +5845,9 @@ ${pc9.bold("Current Configuration:")}
|
|
|
5648
5845
|
}
|
|
5649
5846
|
case "custom": {
|
|
5650
5847
|
const { promptCustomConfig: promptCustomConfig2 } = await Promise.resolve().then(() => (init_prompts(), prompts_exports));
|
|
5651
|
-
const
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
domain: config2.domain
|
|
5655
|
-
};
|
|
5848
|
+
const { applyConfigUpdates: applyConfigUpdates2 } = await Promise.resolve().then(() => (init_metadata(), metadata_exports));
|
|
5849
|
+
const customConfig = await promptCustomConfig2(config2);
|
|
5850
|
+
updatedConfig = applyConfigUpdates2(config2, customConfig);
|
|
5656
5851
|
newPreset = void 0;
|
|
5657
5852
|
break;
|
|
5658
5853
|
}
|
|
@@ -5773,6 +5968,33 @@ ${pc9.bold("Cost Impact:")}`);
|
|
|
5773
5968
|
}
|
|
5774
5969
|
throw new Error(`Pulumi upgrade failed: ${error.message}`);
|
|
5775
5970
|
}
|
|
5971
|
+
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
5972
|
+
const { findHostedZone: findHostedZone2, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
5973
|
+
const hostedZone = await findHostedZone2(outputs.domain, region);
|
|
5974
|
+
if (hostedZone) {
|
|
5975
|
+
try {
|
|
5976
|
+
progress.start("Creating DNS records in Route53");
|
|
5977
|
+
const mailFromDomain = updatedConfig.mailFromDomain || `mail.${outputs.domain}`;
|
|
5978
|
+
await createDNSRecords2(
|
|
5979
|
+
hostedZone.id,
|
|
5980
|
+
outputs.domain,
|
|
5981
|
+
outputs.dkimTokens,
|
|
5982
|
+
region,
|
|
5983
|
+
outputs.customTrackingDomain,
|
|
5984
|
+
mailFromDomain,
|
|
5985
|
+
outputs.cloudFrontDomain
|
|
5986
|
+
);
|
|
5987
|
+
progress.succeed("DNS records created in Route53");
|
|
5988
|
+
} catch (error) {
|
|
5989
|
+
progress.fail(
|
|
5990
|
+
`Failed to create DNS records automatically: ${error.message}`
|
|
5991
|
+
);
|
|
5992
|
+
progress.info(
|
|
5993
|
+
"You can manually add the required DNS records shown below"
|
|
5994
|
+
);
|
|
5995
|
+
}
|
|
5996
|
+
}
|
|
5997
|
+
}
|
|
5776
5998
|
updateEmailConfig(metadata, updatedConfig);
|
|
5777
5999
|
if (metadata.services.email) {
|
|
5778
6000
|
metadata.services.email.preset = newPreset;
|
|
@@ -6592,6 +6814,7 @@ function createMetricsRouter(config2) {
|
|
|
6592
6814
|
|
|
6593
6815
|
// src/console/routes/settings.ts
|
|
6594
6816
|
init_esm_shims();
|
|
6817
|
+
init_metadata();
|
|
6595
6818
|
import dns from "dns/promises";
|
|
6596
6819
|
import { Router as createRouter4 } from "express";
|
|
6597
6820
|
|
|
@@ -6934,6 +7157,7 @@ function createSettingsRouter(config2) {
|
|
|
6934
7157
|
|
|
6935
7158
|
// src/console/routes/user.ts
|
|
6936
7159
|
init_esm_shims();
|
|
7160
|
+
init_metadata();
|
|
6937
7161
|
import { Router as createRouter5 } from "express";
|
|
6938
7162
|
function createUserRouter(config2) {
|
|
6939
7163
|
const router = createRouter5();
|
|
@@ -7079,6 +7303,7 @@ async function startConsoleServer(config2) {
|
|
|
7079
7303
|
|
|
7080
7304
|
// src/commands/shared/dashboard.ts
|
|
7081
7305
|
init_aws();
|
|
7306
|
+
init_fs();
|
|
7082
7307
|
async function dashboard(options) {
|
|
7083
7308
|
clack10.intro(pc10.bold("Wraps Dashboard"));
|
|
7084
7309
|
const progress = new DeploymentProgress();
|
|
@@ -7135,6 +7360,8 @@ async function dashboard(options) {
|
|
|
7135
7360
|
// src/commands/shared/destroy.ts
|
|
7136
7361
|
init_esm_shims();
|
|
7137
7362
|
init_aws();
|
|
7363
|
+
init_fs();
|
|
7364
|
+
init_metadata();
|
|
7138
7365
|
import * as clack11 from "@clack/prompts";
|
|
7139
7366
|
import * as pulumi11 from "@pulumi/pulumi";
|
|
7140
7367
|
import pc11 from "picocolors";
|
|
@@ -7201,6 +7428,7 @@ Run ${pc11.cyan("wraps email init")} to deploy infrastructure again.
|
|
|
7201
7428
|
// src/commands/shared/status.ts
|
|
7202
7429
|
init_esm_shims();
|
|
7203
7430
|
init_aws();
|
|
7431
|
+
init_fs();
|
|
7204
7432
|
import * as clack12 from "@clack/prompts";
|
|
7205
7433
|
import * as pulumi12 from "@pulumi/pulumi";
|
|
7206
7434
|
import pc12 from "picocolors";
|