@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 +407 -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,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
|
-
|
|
2838
|
-
|
|
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
|
-
|
|
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
|
|
5652
|
-
|
|
5653
|
-
|
|
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";
|