@wraps.dev/cli 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +142 -24
- 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
|
@@ -1285,7 +1285,7 @@ async function findHostedZone(domain, region) {
|
|
|
1285
1285
|
return null;
|
|
1286
1286
|
}
|
|
1287
1287
|
}
|
|
1288
|
-
async function createDNSRecords(hostedZoneId, domain, dkimTokens, region, customTrackingDomain) {
|
|
1288
|
+
async function createDNSRecords(hostedZoneId, domain, dkimTokens, region, customTrackingDomain, mailFromDomain) {
|
|
1289
1289
|
const client = new Route53Client({ region });
|
|
1290
1290
|
const changes = [];
|
|
1291
1291
|
for (const token of dkimTokens) {
|
|
@@ -1330,6 +1330,28 @@ async function createDNSRecords(hostedZoneId, domain, dkimTokens, region, custom
|
|
|
1330
1330
|
}
|
|
1331
1331
|
});
|
|
1332
1332
|
}
|
|
1333
|
+
if (mailFromDomain) {
|
|
1334
|
+
changes.push({
|
|
1335
|
+
Action: "UPSERT",
|
|
1336
|
+
ResourceRecordSet: {
|
|
1337
|
+
Name: mailFromDomain,
|
|
1338
|
+
Type: "MX",
|
|
1339
|
+
TTL: 1800,
|
|
1340
|
+
ResourceRecords: [
|
|
1341
|
+
{ Value: `10 feedback-smtp.${region}.amazonses.com` }
|
|
1342
|
+
]
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
changes.push({
|
|
1346
|
+
Action: "UPSERT",
|
|
1347
|
+
ResourceRecordSet: {
|
|
1348
|
+
Name: mailFromDomain,
|
|
1349
|
+
Type: "TXT",
|
|
1350
|
+
TTL: 1800,
|
|
1351
|
+
ResourceRecords: [{ Value: '"v=spf1 include:amazonses.com ~all"' }]
|
|
1352
|
+
}
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1333
1355
|
await client.send(
|
|
1334
1356
|
new ChangeResourceRecordSetsCommand({
|
|
1335
1357
|
HostedZoneId: hostedZoneId,
|
|
@@ -1778,6 +1800,7 @@ async function createSESResources(config) {
|
|
|
1778
1800
|
});
|
|
1779
1801
|
let domainIdentity;
|
|
1780
1802
|
let dkimTokens;
|
|
1803
|
+
let mailFromDomain;
|
|
1781
1804
|
if (config.domain) {
|
|
1782
1805
|
domainIdentity = new aws5.sesv2.EmailIdentity("wraps-email-domain", {
|
|
1783
1806
|
emailIdentity: config.domain,
|
|
@@ -1793,6 +1816,20 @@ async function createSESResources(config) {
|
|
|
1793
1816
|
dkimTokens = domainIdentity.dkimSigningAttributes.apply(
|
|
1794
1817
|
(attrs) => attrs?.tokens || []
|
|
1795
1818
|
);
|
|
1819
|
+
mailFromDomain = config.mailFromDomain || `mail.${config.domain}`;
|
|
1820
|
+
new aws5.sesv2.EmailIdentityMailFromAttributes(
|
|
1821
|
+
"wraps-email-mail-from",
|
|
1822
|
+
{
|
|
1823
|
+
emailIdentity: config.domain,
|
|
1824
|
+
mailFromDomain,
|
|
1825
|
+
behaviorOnMxFailure: "USE_DEFAULT_VALUE"
|
|
1826
|
+
// Fallback to amazonses.com if MX record fails
|
|
1827
|
+
},
|
|
1828
|
+
{
|
|
1829
|
+
dependsOn: [domainIdentity]
|
|
1830
|
+
// Ensure domain identity exists first
|
|
1831
|
+
}
|
|
1832
|
+
);
|
|
1796
1833
|
}
|
|
1797
1834
|
return {
|
|
1798
1835
|
configSet,
|
|
@@ -1802,7 +1839,8 @@ async function createSESResources(config) {
|
|
|
1802
1839
|
dkimTokens,
|
|
1803
1840
|
dnsAutoCreated: false,
|
|
1804
1841
|
// Will be set after deployment
|
|
1805
|
-
customTrackingDomain: config.trackingConfig?.customRedirectDomain
|
|
1842
|
+
customTrackingDomain: config.trackingConfig?.customRedirectDomain,
|
|
1843
|
+
mailFromDomain
|
|
1806
1844
|
};
|
|
1807
1845
|
}
|
|
1808
1846
|
|
|
@@ -1887,6 +1925,7 @@ async function deployEmailStack(config) {
|
|
|
1887
1925
|
if (emailConfig.tracking?.enabled || emailConfig.eventTracking?.enabled) {
|
|
1888
1926
|
sesResources = await createSESResources({
|
|
1889
1927
|
domain: emailConfig.domain,
|
|
1928
|
+
mailFromDomain: emailConfig.mailFromDomain,
|
|
1890
1929
|
region: config.region,
|
|
1891
1930
|
trackingConfig: emailConfig.tracking,
|
|
1892
1931
|
eventTypes: emailConfig.eventTracking?.events
|
|
@@ -1930,7 +1969,8 @@ async function deployEmailStack(config) {
|
|
|
1930
1969
|
eventBusName: sesResources?.eventBus.name,
|
|
1931
1970
|
queueUrl: sqsResources?.queue.url,
|
|
1932
1971
|
dlqUrl: sqsResources?.dlq.url,
|
|
1933
|
-
customTrackingDomain: sesResources?.customTrackingDomain
|
|
1972
|
+
customTrackingDomain: sesResources?.customTrackingDomain,
|
|
1973
|
+
mailFromDomain: sesResources?.mailFromDomain
|
|
1934
1974
|
};
|
|
1935
1975
|
}
|
|
1936
1976
|
|
|
@@ -2149,6 +2189,14 @@ Verification should complete within a few minutes.`,
|
|
|
2149
2189
|
pc2.bold("DMARC Record (TXT):"),
|
|
2150
2190
|
` ${pc2.cyan(`_dmarc.${domain}`)} ${pc2.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${domain}"`
|
|
2151
2191
|
);
|
|
2192
|
+
if (outputs.mailFromDomain) {
|
|
2193
|
+
dnsLines.push(
|
|
2194
|
+
"",
|
|
2195
|
+
pc2.bold("MAIL FROM Domain Records (for DMARC alignment):"),
|
|
2196
|
+
` ${pc2.cyan(outputs.mailFromDomain)} ${pc2.dim("MX")} "10 feedback-smtp.${outputs.region}.amazonses.com"`,
|
|
2197
|
+
` ${pc2.cyan(outputs.mailFromDomain)} ${pc2.dim("TXT")} "v=spf1 include:amazonses.com ~all"`
|
|
2198
|
+
);
|
|
2199
|
+
}
|
|
2152
2200
|
}
|
|
2153
2201
|
clack2.note(dnsLines.join("\n"), "DNS Records to add:");
|
|
2154
2202
|
}
|
|
@@ -2201,7 +2249,14 @@ function displayStatus(status2) {
|
|
|
2201
2249
|
const domainStrings = status2.domains.map((d) => {
|
|
2202
2250
|
const statusIcon = d.status === "verified" ? "\u2713" : d.status === "pending" ? "\u23F1" : "\u2717";
|
|
2203
2251
|
const statusColor = d.status === "verified" ? pc2.green : d.status === "pending" ? pc2.yellow : pc2.red;
|
|
2204
|
-
|
|
2252
|
+
let domainLine = ` ${d.domain} ${statusColor(`${statusIcon} ${d.status}`)}`;
|
|
2253
|
+
if (d.mailFromDomain) {
|
|
2254
|
+
const mailFromStatusIcon = d.mailFromStatus === "SUCCESS" ? "\u2713" : "\u23F1";
|
|
2255
|
+
const mailFromColor = d.mailFromStatus === "SUCCESS" ? pc2.green : pc2.yellow;
|
|
2256
|
+
domainLine += `
|
|
2257
|
+
${pc2.dim("MAIL FROM:")} ${d.mailFromDomain} ${mailFromColor(mailFromStatusIcon)}`;
|
|
2258
|
+
}
|
|
2259
|
+
return domainLine;
|
|
2205
2260
|
});
|
|
2206
2261
|
infoLines.push(`${pc2.bold("Domains:")}
|
|
2207
2262
|
${domainStrings.join("\n")}`);
|
|
@@ -2260,13 +2315,14 @@ ${domainStrings.join("\n")}`);
|
|
|
2260
2315
|
);
|
|
2261
2316
|
}
|
|
2262
2317
|
clack2.note(resourceLines.join("\n"), "Resources");
|
|
2263
|
-
const
|
|
2264
|
-
(d) => d.status === "pending" && d.dkimTokens
|
|
2318
|
+
const domainsNeedingDNS = status2.domains.filter(
|
|
2319
|
+
(d) => d.status === "pending" && d.dkimTokens || d.mailFromDomain && d.mailFromStatus !== "SUCCESS"
|
|
2265
2320
|
);
|
|
2266
|
-
if (
|
|
2267
|
-
for (const domain of
|
|
2268
|
-
|
|
2269
|
-
|
|
2321
|
+
if (domainsNeedingDNS.length > 0) {
|
|
2322
|
+
for (const domain of domainsNeedingDNS) {
|
|
2323
|
+
const dnsLines = [];
|
|
2324
|
+
if (domain.status === "pending" && domain.dkimTokens && domain.dkimTokens.length > 0) {
|
|
2325
|
+
dnsLines.push(
|
|
2270
2326
|
pc2.bold("DKIM Records (CNAME):"),
|
|
2271
2327
|
...domain.dkimTokens.map(
|
|
2272
2328
|
(token) => ` ${pc2.cyan(`${token}._domainkey.${domain.domain}`)} ${pc2.dim("CNAME")} "${token}.dkim.amazonses.com"`
|
|
@@ -2277,11 +2333,21 @@ ${domainStrings.join("\n")}`);
|
|
|
2277
2333
|
"",
|
|
2278
2334
|
pc2.bold("DMARC Record (TXT):"),
|
|
2279
2335
|
` ${pc2.cyan(`_dmarc.${domain.domain}`)} ${pc2.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${domain.domain}"`
|
|
2280
|
-
|
|
2336
|
+
);
|
|
2337
|
+
}
|
|
2338
|
+
if (domain.mailFromDomain && domain.mailFromStatus !== "SUCCESS") {
|
|
2339
|
+
if (dnsLines.length > 0) dnsLines.push("");
|
|
2340
|
+
dnsLines.push(
|
|
2341
|
+
pc2.bold("MAIL FROM Domain Records (for DMARC alignment):"),
|
|
2342
|
+
` ${pc2.cyan(domain.mailFromDomain)} ${pc2.dim("MX")} "10 feedback-smtp.${status2.region}.amazonses.com"`,
|
|
2343
|
+
` ${pc2.cyan(domain.mailFromDomain)} ${pc2.dim("TXT")} "v=spf1 include:amazonses.com ~all"`
|
|
2344
|
+
);
|
|
2345
|
+
}
|
|
2346
|
+
if (dnsLines.length > 0) {
|
|
2281
2347
|
clack2.note(dnsLines.join("\n"), `DNS Records for ${domain.domain}`);
|
|
2282
2348
|
}
|
|
2283
2349
|
}
|
|
2284
|
-
const exampleDomain =
|
|
2350
|
+
const exampleDomain = domainsNeedingDNS[0].domain;
|
|
2285
2351
|
console.log(
|
|
2286
2352
|
`
|
|
2287
2353
|
${pc2.dim("Run:")} ${pc2.yellow(`wraps verify --domain ${exampleDomain}`)} ${pc2.dim(
|
|
@@ -4168,7 +4234,8 @@ async function init(options) {
|
|
|
4168
4234
|
outputs.domain,
|
|
4169
4235
|
outputs.dkimTokens,
|
|
4170
4236
|
region,
|
|
4171
|
-
outputs.customTrackingDomain
|
|
4237
|
+
outputs.customTrackingDomain,
|
|
4238
|
+
outputs.mailFromDomain
|
|
4172
4239
|
);
|
|
4173
4240
|
progress.succeed("DNS records created in Route53");
|
|
4174
4241
|
dnsAutoCreated = true;
|
|
@@ -4195,7 +4262,8 @@ async function init(options) {
|
|
|
4195
4262
|
tableName: outputs.tableName,
|
|
4196
4263
|
dnsRecords: dnsRecords.length > 0 ? dnsRecords : void 0,
|
|
4197
4264
|
dnsAutoCreated,
|
|
4198
|
-
domain: outputs.domain
|
|
4265
|
+
domain: outputs.domain,
|
|
4266
|
+
mailFromDomain: outputs.mailFromDomain
|
|
4199
4267
|
});
|
|
4200
4268
|
}
|
|
4201
4269
|
|
|
@@ -4345,13 +4413,17 @@ Run ${pc9.cyan("wraps init")} to deploy infrastructure.
|
|
|
4345
4413
|
return {
|
|
4346
4414
|
domain: d.domain,
|
|
4347
4415
|
status: d.verified ? "verified" : "pending",
|
|
4348
|
-
dkimTokens: identity2.DkimAttributes?.Tokens || []
|
|
4416
|
+
dkimTokens: identity2.DkimAttributes?.Tokens || [],
|
|
4417
|
+
mailFromDomain: identity2.MailFromAttributes?.MailFromDomain,
|
|
4418
|
+
mailFromStatus: identity2.MailFromAttributes?.MailFromDomainStatus
|
|
4349
4419
|
};
|
|
4350
4420
|
} catch (_error) {
|
|
4351
4421
|
return {
|
|
4352
4422
|
domain: d.domain,
|
|
4353
4423
|
status: d.verified ? "verified" : "pending",
|
|
4354
|
-
dkimTokens: void 0
|
|
4424
|
+
dkimTokens: void 0,
|
|
4425
|
+
mailFromDomain: void 0,
|
|
4426
|
+
mailFromStatus: void 0
|
|
4355
4427
|
};
|
|
4356
4428
|
}
|
|
4357
4429
|
})
|
|
@@ -5048,6 +5120,7 @@ async function verify(options) {
|
|
|
5048
5120
|
const sesClient = new SESv2Client3({ region });
|
|
5049
5121
|
let identity;
|
|
5050
5122
|
let dkimTokens = [];
|
|
5123
|
+
let mailFromDomain;
|
|
5051
5124
|
try {
|
|
5052
5125
|
identity = await progress.execute(
|
|
5053
5126
|
"Checking SES verification status",
|
|
@@ -5059,6 +5132,7 @@ async function verify(options) {
|
|
|
5059
5132
|
}
|
|
5060
5133
|
);
|
|
5061
5134
|
dkimTokens = identity.DkimAttributes?.Tokens || [];
|
|
5135
|
+
mailFromDomain = identity.MailFromAttributes?.MailFromDomain;
|
|
5062
5136
|
} catch (_error) {
|
|
5063
5137
|
progress.stop();
|
|
5064
5138
|
clack12.log.error(`Domain ${options.domain} not found in SES`);
|
|
@@ -5070,6 +5144,7 @@ Run ${pc12.cyan(`wraps init --domain ${options.domain}`)} to add this domain.
|
|
|
5070
5144
|
process.exit(1);
|
|
5071
5145
|
}
|
|
5072
5146
|
const resolver = new Resolver();
|
|
5147
|
+
resolver.setServers(["8.8.8.8", "1.1.1.1"]);
|
|
5073
5148
|
const dnsResults = [];
|
|
5074
5149
|
for (const token of dkimTokens) {
|
|
5075
5150
|
const dkimRecord = `${token}._domainkey.${options.domain}`;
|
|
@@ -5124,17 +5199,60 @@ Run ${pc12.cyan(`wraps init --domain ${options.domain}`)} to add this domain.
|
|
|
5124
5199
|
status: "missing"
|
|
5125
5200
|
});
|
|
5126
5201
|
}
|
|
5202
|
+
if (mailFromDomain) {
|
|
5203
|
+
try {
|
|
5204
|
+
const mxRecords = await resolver.resolveMx(mailFromDomain);
|
|
5205
|
+
const expectedMx = `feedback-smtp.${region}.amazonses.com`;
|
|
5206
|
+
const hasMx = mxRecords.some(
|
|
5207
|
+
(r) => r.exchange === expectedMx || r.exchange === `${expectedMx}.`
|
|
5208
|
+
);
|
|
5209
|
+
dnsResults.push({
|
|
5210
|
+
name: mailFromDomain,
|
|
5211
|
+
type: "MX",
|
|
5212
|
+
status: hasMx ? "verified" : mxRecords.length > 0 ? "incorrect" : "missing",
|
|
5213
|
+
records: mxRecords.map((r) => `${r.priority} ${r.exchange}`)
|
|
5214
|
+
});
|
|
5215
|
+
} catch (_error) {
|
|
5216
|
+
dnsResults.push({
|
|
5217
|
+
name: mailFromDomain,
|
|
5218
|
+
type: "MX",
|
|
5219
|
+
status: "missing"
|
|
5220
|
+
});
|
|
5221
|
+
}
|
|
5222
|
+
try {
|
|
5223
|
+
const records = await resolver.resolveTxt(mailFromDomain);
|
|
5224
|
+
const spfRecord = records.flat().find((r) => r.startsWith("v=spf1"));
|
|
5225
|
+
const hasAmazonSES = spfRecord?.includes("include:amazonses.com");
|
|
5226
|
+
dnsResults.push({
|
|
5227
|
+
name: mailFromDomain,
|
|
5228
|
+
type: "TXT (SPF)",
|
|
5229
|
+
status: hasAmazonSES ? "verified" : spfRecord ? "incorrect" : "missing",
|
|
5230
|
+
records: spfRecord ? [spfRecord] : void 0
|
|
5231
|
+
});
|
|
5232
|
+
} catch (_error) {
|
|
5233
|
+
dnsResults.push({
|
|
5234
|
+
name: mailFromDomain,
|
|
5235
|
+
type: "TXT (SPF)",
|
|
5236
|
+
status: "missing"
|
|
5237
|
+
});
|
|
5238
|
+
}
|
|
5239
|
+
}
|
|
5127
5240
|
progress.stop();
|
|
5128
5241
|
const verificationStatus = identity.VerifiedForSendingStatus ? "verified" : "pending";
|
|
5129
5242
|
const dkimStatus = identity.DkimAttributes?.Status || "PENDING";
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5243
|
+
const mailFromStatus = identity.MailFromAttributes?.MailFromDomainStatus || "NOT_CONFIGURED";
|
|
5244
|
+
const statusLines = [
|
|
5245
|
+
`${pc12.bold("Domain:")} ${options.domain}`,
|
|
5246
|
+
`${pc12.bold("Verification Status:")} ${verificationStatus === "verified" ? pc12.green("\u2713 Verified") : pc12.yellow("\u23F1 Pending")}`,
|
|
5247
|
+
`${pc12.bold("DKIM Status:")} ${dkimStatus === "SUCCESS" ? pc12.green("\u2713 Success") : pc12.yellow(`\u23F1 ${dkimStatus}`)}`
|
|
5248
|
+
];
|
|
5249
|
+
if (mailFromDomain) {
|
|
5250
|
+
statusLines.push(
|
|
5251
|
+
`${pc12.bold("MAIL FROM Domain:")} ${mailFromDomain}`,
|
|
5252
|
+
`${pc12.bold("MAIL FROM Status:")} ${mailFromStatus === "SUCCESS" ? pc12.green("\u2713 Success") : mailFromStatus === "NOT_CONFIGURED" ? pc12.yellow("\u23F1 Not Configured") : pc12.yellow(`\u23F1 ${mailFromStatus}`)}`
|
|
5253
|
+
);
|
|
5254
|
+
}
|
|
5255
|
+
clack12.note(statusLines.join("\n"), "SES Status");
|
|
5138
5256
|
const dnsLines = dnsResults.map((record) => {
|
|
5139
5257
|
let statusIcon;
|
|
5140
5258
|
let statusColor;
|