@wraps.dev/cli 0.1.4 → 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 +451 -119
- 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,
|
|
@@ -1348,9 +1370,12 @@ var init_route53 = __esm({
|
|
|
1348
1370
|
|
|
1349
1371
|
// src/cli.ts
|
|
1350
1372
|
init_esm_shims();
|
|
1351
|
-
import
|
|
1373
|
+
import { readFileSync } from "fs";
|
|
1374
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
1375
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1376
|
+
import * as clack13 from "@clack/prompts";
|
|
1352
1377
|
import args from "args";
|
|
1353
|
-
import
|
|
1378
|
+
import pc13 from "picocolors";
|
|
1354
1379
|
|
|
1355
1380
|
// src/commands/connect.ts
|
|
1356
1381
|
init_esm_shims();
|
|
@@ -1775,6 +1800,7 @@ async function createSESResources(config) {
|
|
|
1775
1800
|
});
|
|
1776
1801
|
let domainIdentity;
|
|
1777
1802
|
let dkimTokens;
|
|
1803
|
+
let mailFromDomain;
|
|
1778
1804
|
if (config.domain) {
|
|
1779
1805
|
domainIdentity = new aws5.sesv2.EmailIdentity("wraps-email-domain", {
|
|
1780
1806
|
emailIdentity: config.domain,
|
|
@@ -1790,6 +1816,20 @@ async function createSESResources(config) {
|
|
|
1790
1816
|
dkimTokens = domainIdentity.dkimSigningAttributes.apply(
|
|
1791
1817
|
(attrs) => attrs?.tokens || []
|
|
1792
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
|
+
);
|
|
1793
1833
|
}
|
|
1794
1834
|
return {
|
|
1795
1835
|
configSet,
|
|
@@ -1799,7 +1839,8 @@ async function createSESResources(config) {
|
|
|
1799
1839
|
dkimTokens,
|
|
1800
1840
|
dnsAutoCreated: false,
|
|
1801
1841
|
// Will be set after deployment
|
|
1802
|
-
customTrackingDomain: config.trackingConfig?.customRedirectDomain
|
|
1842
|
+
customTrackingDomain: config.trackingConfig?.customRedirectDomain,
|
|
1843
|
+
mailFromDomain
|
|
1803
1844
|
};
|
|
1804
1845
|
}
|
|
1805
1846
|
|
|
@@ -1884,6 +1925,7 @@ async function deployEmailStack(config) {
|
|
|
1884
1925
|
if (emailConfig.tracking?.enabled || emailConfig.eventTracking?.enabled) {
|
|
1885
1926
|
sesResources = await createSESResources({
|
|
1886
1927
|
domain: emailConfig.domain,
|
|
1928
|
+
mailFromDomain: emailConfig.mailFromDomain,
|
|
1887
1929
|
region: config.region,
|
|
1888
1930
|
trackingConfig: emailConfig.tracking,
|
|
1889
1931
|
eventTypes: emailConfig.eventTracking?.events
|
|
@@ -1927,7 +1969,8 @@ async function deployEmailStack(config) {
|
|
|
1927
1969
|
eventBusName: sesResources?.eventBus.name,
|
|
1928
1970
|
queueUrl: sqsResources?.queue.url,
|
|
1929
1971
|
dlqUrl: sqsResources?.dlq.url,
|
|
1930
|
-
customTrackingDomain: sesResources?.customTrackingDomain
|
|
1972
|
+
customTrackingDomain: sesResources?.customTrackingDomain,
|
|
1973
|
+
mailFromDomain: sesResources?.mailFromDomain
|
|
1931
1974
|
};
|
|
1932
1975
|
}
|
|
1933
1976
|
|
|
@@ -2146,6 +2189,14 @@ Verification should complete within a few minutes.`,
|
|
|
2146
2189
|
pc2.bold("DMARC Record (TXT):"),
|
|
2147
2190
|
` ${pc2.cyan(`_dmarc.${domain}`)} ${pc2.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${domain}"`
|
|
2148
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
|
+
}
|
|
2149
2200
|
}
|
|
2150
2201
|
clack2.note(dnsLines.join("\n"), "DNS Records to add:");
|
|
2151
2202
|
}
|
|
@@ -2198,7 +2249,14 @@ function displayStatus(status2) {
|
|
|
2198
2249
|
const domainStrings = status2.domains.map((d) => {
|
|
2199
2250
|
const statusIcon = d.status === "verified" ? "\u2713" : d.status === "pending" ? "\u23F1" : "\u2717";
|
|
2200
2251
|
const statusColor = d.status === "verified" ? pc2.green : d.status === "pending" ? pc2.yellow : pc2.red;
|
|
2201
|
-
|
|
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;
|
|
2202
2260
|
});
|
|
2203
2261
|
infoLines.push(`${pc2.bold("Domains:")}
|
|
2204
2262
|
${domainStrings.join("\n")}`);
|
|
@@ -2257,13 +2315,14 @@ ${domainStrings.join("\n")}`);
|
|
|
2257
2315
|
);
|
|
2258
2316
|
}
|
|
2259
2317
|
clack2.note(resourceLines.join("\n"), "Resources");
|
|
2260
|
-
const
|
|
2261
|
-
(d) => d.status === "pending" && d.dkimTokens
|
|
2318
|
+
const domainsNeedingDNS = status2.domains.filter(
|
|
2319
|
+
(d) => d.status === "pending" && d.dkimTokens || d.mailFromDomain && d.mailFromStatus !== "SUCCESS"
|
|
2262
2320
|
);
|
|
2263
|
-
if (
|
|
2264
|
-
for (const domain of
|
|
2265
|
-
|
|
2266
|
-
|
|
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(
|
|
2267
2326
|
pc2.bold("DKIM Records (CNAME):"),
|
|
2268
2327
|
...domain.dkimTokens.map(
|
|
2269
2328
|
(token) => ` ${pc2.cyan(`${token}._domainkey.${domain.domain}`)} ${pc2.dim("CNAME")} "${token}.dkim.amazonses.com"`
|
|
@@ -2274,11 +2333,21 @@ ${domainStrings.join("\n")}`);
|
|
|
2274
2333
|
"",
|
|
2275
2334
|
pc2.bold("DMARC Record (TXT):"),
|
|
2276
2335
|
` ${pc2.cyan(`_dmarc.${domain.domain}`)} ${pc2.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${domain.domain}"`
|
|
2277
|
-
|
|
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) {
|
|
2278
2347
|
clack2.note(dnsLines.join("\n"), `DNS Records for ${domain.domain}`);
|
|
2279
2348
|
}
|
|
2280
2349
|
}
|
|
2281
|
-
const exampleDomain =
|
|
2350
|
+
const exampleDomain = domainsNeedingDNS[0].domain;
|
|
2282
2351
|
console.log(
|
|
2283
2352
|
`
|
|
2284
2353
|
${pc2.dim("Run:")} ${pc2.yellow(`wraps verify --domain ${exampleDomain}`)} ${pc2.dim(
|
|
@@ -4165,7 +4234,8 @@ async function init(options) {
|
|
|
4165
4234
|
outputs.domain,
|
|
4166
4235
|
outputs.dkimTokens,
|
|
4167
4236
|
region,
|
|
4168
|
-
outputs.customTrackingDomain
|
|
4237
|
+
outputs.customTrackingDomain,
|
|
4238
|
+
outputs.mailFromDomain
|
|
4169
4239
|
);
|
|
4170
4240
|
progress.succeed("DNS records created in Route53");
|
|
4171
4241
|
dnsAutoCreated = true;
|
|
@@ -4192,7 +4262,8 @@ async function init(options) {
|
|
|
4192
4262
|
tableName: outputs.tableName,
|
|
4193
4263
|
dnsRecords: dnsRecords.length > 0 ? dnsRecords : void 0,
|
|
4194
4264
|
dnsAutoCreated,
|
|
4195
|
-
domain: outputs.domain
|
|
4265
|
+
domain: outputs.domain,
|
|
4266
|
+
mailFromDomain: outputs.mailFromDomain
|
|
4196
4267
|
});
|
|
4197
4268
|
}
|
|
4198
4269
|
|
|
@@ -4342,13 +4413,17 @@ Run ${pc9.cyan("wraps init")} to deploy infrastructure.
|
|
|
4342
4413
|
return {
|
|
4343
4414
|
domain: d.domain,
|
|
4344
4415
|
status: d.verified ? "verified" : "pending",
|
|
4345
|
-
dkimTokens: identity2.DkimAttributes?.Tokens || []
|
|
4416
|
+
dkimTokens: identity2.DkimAttributes?.Tokens || [],
|
|
4417
|
+
mailFromDomain: identity2.MailFromAttributes?.MailFromDomain,
|
|
4418
|
+
mailFromStatus: identity2.MailFromAttributes?.MailFromDomainStatus
|
|
4346
4419
|
};
|
|
4347
4420
|
} catch (_error) {
|
|
4348
4421
|
return {
|
|
4349
4422
|
domain: d.domain,
|
|
4350
4423
|
status: d.verified ? "verified" : "pending",
|
|
4351
|
-
dkimTokens: void 0
|
|
4424
|
+
dkimTokens: void 0,
|
|
4425
|
+
mailFromDomain: void 0,
|
|
4426
|
+
mailFromStatus: void 0
|
|
4352
4427
|
};
|
|
4353
4428
|
}
|
|
4354
4429
|
})
|
|
@@ -4369,17 +4444,14 @@ Run ${pc9.cyan("wraps init")} to deploy infrastructure.
|
|
|
4369
4444
|
});
|
|
4370
4445
|
}
|
|
4371
4446
|
|
|
4372
|
-
// src/commands/
|
|
4447
|
+
// src/commands/update.ts
|
|
4373
4448
|
init_esm_shims();
|
|
4374
4449
|
import * as clack10 from "@clack/prompts";
|
|
4375
4450
|
import * as pulumi11 from "@pulumi/pulumi";
|
|
4376
4451
|
import pc10 from "picocolors";
|
|
4377
4452
|
init_aws();
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
init_prompts();
|
|
4381
|
-
async function upgrade(options) {
|
|
4382
|
-
clack10.intro(pc10.bold("Wraps Upgrade - Enhance Your Email Infrastructure"));
|
|
4453
|
+
async function update(options) {
|
|
4454
|
+
clack10.intro(pc10.bold("Wraps Update - Apply CLI Updates to Infrastructure"));
|
|
4383
4455
|
const progress = new DeploymentProgress();
|
|
4384
4456
|
const wasAutoInstalled = await progress.execute(
|
|
4385
4457
|
"Checking Pulumi CLI installation",
|
|
@@ -4423,33 +4495,224 @@ ${pc10.bold("Current Configuration:")}
|
|
|
4423
4495
|
}
|
|
4424
4496
|
if (config.tracking?.enabled) {
|
|
4425
4497
|
console.log(` ${pc10.green("\u2713")} Open & Click Tracking`);
|
|
4498
|
+
}
|
|
4499
|
+
if (config.suppressionList?.enabled) {
|
|
4500
|
+
console.log(` ${pc10.green("\u2713")} Bounce/Complaint Suppression`);
|
|
4501
|
+
}
|
|
4502
|
+
if (config.eventTracking?.enabled) {
|
|
4503
|
+
console.log(` ${pc10.green("\u2713")} Event Tracking (EventBridge)`);
|
|
4504
|
+
}
|
|
4505
|
+
if (config.dedicatedIp) {
|
|
4506
|
+
console.log(` ${pc10.green("\u2713")} Dedicated IP Address`);
|
|
4507
|
+
}
|
|
4508
|
+
console.log("");
|
|
4509
|
+
console.log(`${pc10.bold("What will be updated:")}
|
|
4510
|
+
`);
|
|
4511
|
+
console.log(
|
|
4512
|
+
` ${pc10.cyan("\u2022")} Lambda function code (if event tracking enabled)`
|
|
4513
|
+
);
|
|
4514
|
+
console.log(
|
|
4515
|
+
` ${pc10.cyan("\u2022")} EventBridge rules (if event tracking enabled)`
|
|
4516
|
+
);
|
|
4517
|
+
console.log(` ${pc10.cyan("\u2022")} IAM policies (security improvements)`);
|
|
4518
|
+
console.log(` ${pc10.cyan("\u2022")} SES configuration set (feature updates)`);
|
|
4519
|
+
console.log("");
|
|
4520
|
+
progress.info(
|
|
4521
|
+
"Your current configuration will be preserved - no features will be added or removed"
|
|
4522
|
+
);
|
|
4523
|
+
console.log("");
|
|
4524
|
+
if (!options.yes) {
|
|
4525
|
+
const confirmed = await clack10.confirm({
|
|
4526
|
+
message: "Proceed with update?",
|
|
4527
|
+
initialValue: true
|
|
4528
|
+
});
|
|
4529
|
+
if (clack10.isCancel(confirmed) || !confirmed) {
|
|
4530
|
+
clack10.cancel("Update cancelled.");
|
|
4531
|
+
process.exit(0);
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
let vercelConfig;
|
|
4535
|
+
if (metadata.provider === "vercel" && metadata.vercel) {
|
|
4536
|
+
vercelConfig = metadata.vercel;
|
|
4537
|
+
}
|
|
4538
|
+
const stackConfig = {
|
|
4539
|
+
provider: metadata.provider,
|
|
4540
|
+
region,
|
|
4541
|
+
vercel: vercelConfig,
|
|
4542
|
+
emailConfig: config
|
|
4543
|
+
};
|
|
4544
|
+
let outputs;
|
|
4545
|
+
try {
|
|
4546
|
+
outputs = await progress.execute(
|
|
4547
|
+
"Updating Wraps infrastructure (this may take 2-3 minutes)",
|
|
4548
|
+
async () => {
|
|
4549
|
+
await ensurePulumiWorkDir();
|
|
4550
|
+
const stack = await pulumi11.automation.LocalWorkspace.createOrSelectStack(
|
|
4551
|
+
{
|
|
4552
|
+
stackName: metadata.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
4553
|
+
projectName: "wraps-email",
|
|
4554
|
+
program: async () => {
|
|
4555
|
+
const result = await deployEmailStack(stackConfig);
|
|
4556
|
+
return {
|
|
4557
|
+
roleArn: result.roleArn,
|
|
4558
|
+
configSetName: result.configSetName,
|
|
4559
|
+
tableName: result.tableName,
|
|
4560
|
+
region: result.region,
|
|
4561
|
+
lambdaFunctions: result.lambdaFunctions,
|
|
4562
|
+
domain: result.domain,
|
|
4563
|
+
dkimTokens: result.dkimTokens,
|
|
4564
|
+
customTrackingDomain: result.customTrackingDomain
|
|
4565
|
+
};
|
|
4566
|
+
}
|
|
4567
|
+
},
|
|
4568
|
+
{
|
|
4569
|
+
workDir: getPulumiWorkDir(),
|
|
4570
|
+
envVars: {
|
|
4571
|
+
PULUMI_CONFIG_PASSPHRASE: ""
|
|
4572
|
+
},
|
|
4573
|
+
secretsProvider: "passphrase"
|
|
4574
|
+
}
|
|
4575
|
+
);
|
|
4576
|
+
await stack.workspace.selectStack(
|
|
4577
|
+
metadata.pulumiStackName || `wraps-${identity.accountId}-${region}`
|
|
4578
|
+
);
|
|
4579
|
+
await stack.setConfig("aws:region", { value: region });
|
|
4580
|
+
const upResult = await stack.up({ onOutput: () => {
|
|
4581
|
+
} });
|
|
4582
|
+
const pulumiOutputs = upResult.outputs;
|
|
4583
|
+
return {
|
|
4584
|
+
roleArn: pulumiOutputs.roleArn?.value,
|
|
4585
|
+
configSetName: pulumiOutputs.configSetName?.value,
|
|
4586
|
+
tableName: pulumiOutputs.tableName?.value,
|
|
4587
|
+
region: pulumiOutputs.region?.value,
|
|
4588
|
+
lambdaFunctions: pulumiOutputs.lambdaFunctions?.value,
|
|
4589
|
+
domain: pulumiOutputs.domain?.value,
|
|
4590
|
+
dkimTokens: pulumiOutputs.dkimTokens?.value,
|
|
4591
|
+
customTrackingDomain: pulumiOutputs.customTrackingDomain?.value
|
|
4592
|
+
};
|
|
4593
|
+
}
|
|
4594
|
+
);
|
|
4595
|
+
} catch (error) {
|
|
4596
|
+
clack10.log.error("Infrastructure update failed");
|
|
4597
|
+
if (error.message?.includes("stack is currently locked")) {
|
|
4598
|
+
clack10.log.warn("\nThe Pulumi stack is locked from a previous run.");
|
|
4599
|
+
clack10.log.info("To fix this, run:");
|
|
4600
|
+
clack10.log.info(` ${pc10.cyan("rm -rf ~/.wraps/pulumi/.pulumi/locks")}`);
|
|
4601
|
+
clack10.log.info("\nThen try running wraps update again.");
|
|
4602
|
+
}
|
|
4603
|
+
throw new Error(`Pulumi update failed: ${error.message}`);
|
|
4604
|
+
}
|
|
4605
|
+
metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
4606
|
+
await saveConnectionMetadata(metadata);
|
|
4607
|
+
progress.info("Connection metadata updated");
|
|
4608
|
+
displaySuccess({
|
|
4609
|
+
roleArn: outputs.roleArn,
|
|
4610
|
+
configSetName: outputs.configSetName,
|
|
4611
|
+
region: outputs.region,
|
|
4612
|
+
tableName: outputs.tableName,
|
|
4613
|
+
customTrackingDomain: outputs.customTrackingDomain
|
|
4614
|
+
});
|
|
4615
|
+
console.log(`
|
|
4616
|
+
${pc10.green("\u2713")} ${pc10.bold("Update complete!")}
|
|
4617
|
+
`);
|
|
4618
|
+
console.log(
|
|
4619
|
+
"Infrastructure has been updated with the latest CLI improvements.\n"
|
|
4620
|
+
);
|
|
4621
|
+
console.log(`${pc10.bold("Next steps:")}
|
|
4622
|
+
`);
|
|
4623
|
+
console.log(
|
|
4624
|
+
` ${pc10.cyan("1.")} No code changes needed - your existing SDK integration continues to work`
|
|
4625
|
+
);
|
|
4626
|
+
console.log(
|
|
4627
|
+
` ${pc10.cyan("2.")} Check ${pc10.cyan("wraps status")} to verify all resources are healthy`
|
|
4628
|
+
);
|
|
4629
|
+
console.log(
|
|
4630
|
+
` ${pc10.cyan("3.")} View analytics at ${pc10.cyan("wraps console")}
|
|
4631
|
+
`
|
|
4632
|
+
);
|
|
4633
|
+
}
|
|
4634
|
+
|
|
4635
|
+
// src/commands/upgrade.ts
|
|
4636
|
+
init_esm_shims();
|
|
4637
|
+
import * as clack11 from "@clack/prompts";
|
|
4638
|
+
import * as pulumi12 from "@pulumi/pulumi";
|
|
4639
|
+
import pc11 from "picocolors";
|
|
4640
|
+
init_aws();
|
|
4641
|
+
init_costs();
|
|
4642
|
+
init_presets();
|
|
4643
|
+
init_prompts();
|
|
4644
|
+
async function upgrade(options) {
|
|
4645
|
+
clack11.intro(pc11.bold("Wraps Upgrade - Enhance Your Email Infrastructure"));
|
|
4646
|
+
const progress = new DeploymentProgress();
|
|
4647
|
+
const wasAutoInstalled = await progress.execute(
|
|
4648
|
+
"Checking Pulumi CLI installation",
|
|
4649
|
+
async () => await ensurePulumiInstalled()
|
|
4650
|
+
);
|
|
4651
|
+
if (wasAutoInstalled) {
|
|
4652
|
+
progress.info("Pulumi CLI was automatically installed");
|
|
4653
|
+
}
|
|
4654
|
+
const identity = await progress.execute(
|
|
4655
|
+
"Validating AWS credentials",
|
|
4656
|
+
async () => validateAWSCredentials()
|
|
4657
|
+
);
|
|
4658
|
+
progress.info(`Connected to AWS account: ${pc11.cyan(identity.accountId)}`);
|
|
4659
|
+
let region = options.region;
|
|
4660
|
+
if (!region) {
|
|
4661
|
+
const defaultRegion = await getAWSRegion();
|
|
4662
|
+
region = defaultRegion;
|
|
4663
|
+
}
|
|
4664
|
+
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
4665
|
+
if (!metadata) {
|
|
4666
|
+
clack11.log.error(
|
|
4667
|
+
`No Wraps connection found for account ${pc11.cyan(identity.accountId)} in region ${pc11.cyan(region)}`
|
|
4668
|
+
);
|
|
4669
|
+
clack11.log.info(
|
|
4670
|
+
`Use ${pc11.cyan("wraps init")} to create new infrastructure or ${pc11.cyan("wraps connect")} to connect existing.`
|
|
4671
|
+
);
|
|
4672
|
+
process.exit(1);
|
|
4673
|
+
}
|
|
4674
|
+
progress.info(`Found existing connection created: ${metadata.timestamp}`);
|
|
4675
|
+
console.log(`
|
|
4676
|
+
${pc11.bold("Current Configuration:")}
|
|
4677
|
+
`);
|
|
4678
|
+
if (metadata.preset) {
|
|
4679
|
+
console.log(` Preset: ${pc11.cyan(metadata.preset)}`);
|
|
4680
|
+
} else {
|
|
4681
|
+
console.log(` Preset: ${pc11.cyan("custom")}`);
|
|
4682
|
+
}
|
|
4683
|
+
const config = metadata.emailConfig;
|
|
4684
|
+
if (config.domain) {
|
|
4685
|
+
console.log(` Sending Domain: ${pc11.cyan(config.domain)}`);
|
|
4686
|
+
}
|
|
4687
|
+
if (config.tracking?.enabled) {
|
|
4688
|
+
console.log(` ${pc11.green("\u2713")} Open & Click Tracking`);
|
|
4426
4689
|
if (config.tracking.customRedirectDomain) {
|
|
4427
4690
|
console.log(
|
|
4428
|
-
` ${
|
|
4691
|
+
` ${pc11.dim("\u2514\u2500")} Custom domain: ${pc11.cyan(config.tracking.customRedirectDomain)}`
|
|
4429
4692
|
);
|
|
4430
4693
|
}
|
|
4431
4694
|
}
|
|
4432
4695
|
if (config.suppressionList?.enabled) {
|
|
4433
|
-
console.log(` ${
|
|
4696
|
+
console.log(` ${pc11.green("\u2713")} Bounce/Complaint Suppression`);
|
|
4434
4697
|
}
|
|
4435
4698
|
if (config.eventTracking?.enabled) {
|
|
4436
|
-
console.log(` ${
|
|
4699
|
+
console.log(` ${pc11.green("\u2713")} Event Tracking (EventBridge)`);
|
|
4437
4700
|
if (config.eventTracking.dynamoDBHistory) {
|
|
4438
4701
|
console.log(
|
|
4439
|
-
` ${
|
|
4702
|
+
` ${pc11.dim("\u2514\u2500")} Email History: ${pc11.cyan(config.eventTracking.archiveRetention || "90days")}`
|
|
4440
4703
|
);
|
|
4441
4704
|
}
|
|
4442
4705
|
}
|
|
4443
4706
|
if (config.dedicatedIp) {
|
|
4444
|
-
console.log(` ${
|
|
4707
|
+
console.log(` ${pc11.green("\u2713")} Dedicated IP Address`);
|
|
4445
4708
|
}
|
|
4446
4709
|
const currentCostData = calculateCosts(config, 5e4);
|
|
4447
4710
|
console.log(
|
|
4448
4711
|
`
|
|
4449
|
-
Estimated Cost: ${
|
|
4712
|
+
Estimated Cost: ${pc11.cyan(`~${formatCost(currentCostData.total.monthly)}/mo`)}`
|
|
4450
4713
|
);
|
|
4451
4714
|
console.log("");
|
|
4452
|
-
const upgradeAction = await
|
|
4715
|
+
const upgradeAction = await clack11.select({
|
|
4453
4716
|
message: "What would you like to do?",
|
|
4454
4717
|
options: [
|
|
4455
4718
|
{
|
|
@@ -4484,8 +4747,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
4484
4747
|
}
|
|
4485
4748
|
]
|
|
4486
4749
|
});
|
|
4487
|
-
if (
|
|
4488
|
-
|
|
4750
|
+
if (clack11.isCancel(upgradeAction)) {
|
|
4751
|
+
clack11.cancel("Upgrade cancelled.");
|
|
4489
4752
|
process.exit(0);
|
|
4490
4753
|
}
|
|
4491
4754
|
let updatedConfig = { ...config };
|
|
@@ -4503,15 +4766,15 @@ ${pc10.bold("Current Configuration:")}
|
|
|
4503
4766
|
disabled: currentPresetIdx >= 0 && idx <= currentPresetIdx ? "Current or lower tier" : void 0
|
|
4504
4767
|
})).filter((p) => !p.disabled);
|
|
4505
4768
|
if (availablePresets.length === 0) {
|
|
4506
|
-
|
|
4769
|
+
clack11.log.warn("Already on highest preset (Enterprise)");
|
|
4507
4770
|
process.exit(0);
|
|
4508
4771
|
}
|
|
4509
|
-
const selectedPreset = await
|
|
4772
|
+
const selectedPreset = await clack11.select({
|
|
4510
4773
|
message: "Select new preset:",
|
|
4511
4774
|
options: availablePresets
|
|
4512
4775
|
});
|
|
4513
|
-
if (
|
|
4514
|
-
|
|
4776
|
+
if (clack11.isCancel(selectedPreset)) {
|
|
4777
|
+
clack11.cancel("Upgrade cancelled.");
|
|
4515
4778
|
process.exit(0);
|
|
4516
4779
|
}
|
|
4517
4780
|
const presetConfig = getPreset(selectedPreset);
|
|
@@ -4525,11 +4788,11 @@ ${pc10.bold("Current Configuration:")}
|
|
|
4525
4788
|
}
|
|
4526
4789
|
case "tracking-domain": {
|
|
4527
4790
|
if (!config.domain) {
|
|
4528
|
-
|
|
4791
|
+
clack11.log.error(
|
|
4529
4792
|
"No sending domain configured. You must configure a sending domain before adding a custom tracking domain."
|
|
4530
4793
|
);
|
|
4531
|
-
|
|
4532
|
-
`Use ${
|
|
4794
|
+
clack11.log.info(
|
|
4795
|
+
`Use ${pc11.cyan("wraps init")} to set up a sending domain first.`
|
|
4533
4796
|
);
|
|
4534
4797
|
process.exit(1);
|
|
4535
4798
|
}
|
|
@@ -4540,21 +4803,21 @@ ${pc10.bold("Current Configuration:")}
|
|
|
4540
4803
|
);
|
|
4541
4804
|
const sendingDomain = domains.find((d) => d.domain === config.domain);
|
|
4542
4805
|
if (!sendingDomain?.verified) {
|
|
4543
|
-
|
|
4544
|
-
`Sending domain ${
|
|
4806
|
+
clack11.log.error(
|
|
4807
|
+
`Sending domain ${pc11.cyan(config.domain)} is not verified.`
|
|
4545
4808
|
);
|
|
4546
|
-
|
|
4809
|
+
clack11.log.info(
|
|
4547
4810
|
"You must verify your sending domain before adding a custom tracking domain."
|
|
4548
4811
|
);
|
|
4549
|
-
|
|
4550
|
-
`Use ${
|
|
4812
|
+
clack11.log.info(
|
|
4813
|
+
`Use ${pc11.cyan("wraps verify")} to check DNS records and complete verification.`
|
|
4551
4814
|
);
|
|
4552
4815
|
process.exit(1);
|
|
4553
4816
|
}
|
|
4554
4817
|
progress.info(
|
|
4555
|
-
`Sending domain ${
|
|
4818
|
+
`Sending domain ${pc11.cyan(config.domain)} is verified ${pc11.green("\u2713")}`
|
|
4556
4819
|
);
|
|
4557
|
-
const trackingDomain = await
|
|
4820
|
+
const trackingDomain = await clack11.text({
|
|
4558
4821
|
message: "Custom tracking redirect domain:",
|
|
4559
4822
|
placeholder: "track.yourdomain.com",
|
|
4560
4823
|
initialValue: config.tracking?.customRedirectDomain || "",
|
|
@@ -4564,8 +4827,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
4564
4827
|
}
|
|
4565
4828
|
}
|
|
4566
4829
|
});
|
|
4567
|
-
if (
|
|
4568
|
-
|
|
4830
|
+
if (clack11.isCancel(trackingDomain)) {
|
|
4831
|
+
clack11.cancel("Upgrade cancelled.");
|
|
4569
4832
|
process.exit(0);
|
|
4570
4833
|
}
|
|
4571
4834
|
updatedConfig = {
|
|
@@ -4580,7 +4843,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
4580
4843
|
break;
|
|
4581
4844
|
}
|
|
4582
4845
|
case "retention": {
|
|
4583
|
-
const retention = await
|
|
4846
|
+
const retention = await clack11.select({
|
|
4584
4847
|
message: "Email history retention period:",
|
|
4585
4848
|
options: [
|
|
4586
4849
|
{ value: "7days", label: "7 days", hint: "Minimal storage cost" },
|
|
@@ -4599,8 +4862,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
4599
4862
|
],
|
|
4600
4863
|
initialValue: config.eventTracking?.archiveRetention || "90days"
|
|
4601
4864
|
});
|
|
4602
|
-
if (
|
|
4603
|
-
|
|
4865
|
+
if (clack11.isCancel(retention)) {
|
|
4866
|
+
clack11.cancel("Upgrade cancelled.");
|
|
4604
4867
|
process.exit(0);
|
|
4605
4868
|
}
|
|
4606
4869
|
updatedConfig = {
|
|
@@ -4616,7 +4879,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
4616
4879
|
break;
|
|
4617
4880
|
}
|
|
4618
4881
|
case "events": {
|
|
4619
|
-
const selectedEvents = await
|
|
4882
|
+
const selectedEvents = await clack11.multiselect({
|
|
4620
4883
|
message: "Select SES event types to track:",
|
|
4621
4884
|
options: [
|
|
4622
4885
|
{ value: "SEND", label: "Send", hint: "Email sent to SES" },
|
|
@@ -4660,8 +4923,8 @@ ${pc10.bold("Current Configuration:")}
|
|
|
4660
4923
|
],
|
|
4661
4924
|
required: true
|
|
4662
4925
|
});
|
|
4663
|
-
if (
|
|
4664
|
-
|
|
4926
|
+
if (clack11.isCancel(selectedEvents)) {
|
|
4927
|
+
clack11.cancel("Upgrade cancelled.");
|
|
4665
4928
|
process.exit(0);
|
|
4666
4929
|
}
|
|
4667
4930
|
updatedConfig = {
|
|
@@ -4676,16 +4939,16 @@ ${pc10.bold("Current Configuration:")}
|
|
|
4676
4939
|
break;
|
|
4677
4940
|
}
|
|
4678
4941
|
case "dedicated-ip": {
|
|
4679
|
-
const confirmed = await
|
|
4942
|
+
const confirmed = await clack11.confirm({
|
|
4680
4943
|
message: "Enable dedicated IP? (Requires 100k+ emails/day, adds ~$50-100/mo)",
|
|
4681
4944
|
initialValue: false
|
|
4682
4945
|
});
|
|
4683
|
-
if (
|
|
4684
|
-
|
|
4946
|
+
if (clack11.isCancel(confirmed)) {
|
|
4947
|
+
clack11.cancel("Upgrade cancelled.");
|
|
4685
4948
|
process.exit(0);
|
|
4686
4949
|
}
|
|
4687
4950
|
if (!confirmed) {
|
|
4688
|
-
|
|
4951
|
+
clack11.log.info("Dedicated IP not enabled.");
|
|
4689
4952
|
process.exit(0);
|
|
4690
4953
|
}
|
|
4691
4954
|
updatedConfig = {
|
|
@@ -4709,28 +4972,28 @@ ${pc10.bold("Current Configuration:")}
|
|
|
4709
4972
|
const newCostData = calculateCosts(updatedConfig, 5e4);
|
|
4710
4973
|
const costDiff = newCostData.total.monthly - currentCostData.total.monthly;
|
|
4711
4974
|
console.log(`
|
|
4712
|
-
${
|
|
4975
|
+
${pc11.bold("Cost Impact:")}`);
|
|
4713
4976
|
console.log(
|
|
4714
|
-
` Current: ${
|
|
4977
|
+
` Current: ${pc11.cyan(`${formatCost(currentCostData.total.monthly)}/mo`)}`
|
|
4715
4978
|
);
|
|
4716
4979
|
console.log(
|
|
4717
|
-
` New: ${
|
|
4980
|
+
` New: ${pc11.cyan(`${formatCost(newCostData.total.monthly)}/mo`)}`
|
|
4718
4981
|
);
|
|
4719
4982
|
if (costDiff > 0) {
|
|
4720
|
-
console.log(` Change: ${
|
|
4983
|
+
console.log(` Change: ${pc11.yellow(`+${formatCost(costDiff)}/mo`)}`);
|
|
4721
4984
|
} else if (costDiff < 0) {
|
|
4722
4985
|
console.log(
|
|
4723
|
-
` Change: ${
|
|
4986
|
+
` Change: ${pc11.green(`${formatCost(Math.abs(costDiff))}/mo`)}`
|
|
4724
4987
|
);
|
|
4725
4988
|
}
|
|
4726
4989
|
console.log("");
|
|
4727
4990
|
if (!options.yes) {
|
|
4728
|
-
const confirmed = await
|
|
4991
|
+
const confirmed = await clack11.confirm({
|
|
4729
4992
|
message: "Proceed with upgrade?",
|
|
4730
4993
|
initialValue: true
|
|
4731
4994
|
});
|
|
4732
|
-
if (
|
|
4733
|
-
|
|
4995
|
+
if (clack11.isCancel(confirmed) || !confirmed) {
|
|
4996
|
+
clack11.cancel("Upgrade cancelled.");
|
|
4734
4997
|
process.exit(0);
|
|
4735
4998
|
}
|
|
4736
4999
|
}
|
|
@@ -4752,7 +5015,7 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
4752
5015
|
"Updating Wraps infrastructure (this may take 2-3 minutes)",
|
|
4753
5016
|
async () => {
|
|
4754
5017
|
await ensurePulumiWorkDir();
|
|
4755
|
-
const stack = await
|
|
5018
|
+
const stack = await pulumi12.automation.LocalWorkspace.createOrSelectStack(
|
|
4756
5019
|
{
|
|
4757
5020
|
stackName: metadata.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
4758
5021
|
projectName: "wraps-email",
|
|
@@ -4798,12 +5061,12 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
4798
5061
|
}
|
|
4799
5062
|
);
|
|
4800
5063
|
} catch (error) {
|
|
4801
|
-
|
|
5064
|
+
clack11.log.error("Infrastructure upgrade failed");
|
|
4802
5065
|
if (error.message?.includes("stack is currently locked")) {
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
5066
|
+
clack11.log.warn("\nThe Pulumi stack is locked from a previous run.");
|
|
5067
|
+
clack11.log.info("To fix this, run:");
|
|
5068
|
+
clack11.log.info(` ${pc11.cyan("rm -rf ~/.wraps/pulumi/.pulumi/locks")}`);
|
|
5069
|
+
clack11.log.info("\nThen try running wraps upgrade again.");
|
|
4807
5070
|
}
|
|
4808
5071
|
throw new Error(`Pulumi upgrade failed: ${error.message}`);
|
|
4809
5072
|
}
|
|
@@ -4828,16 +5091,16 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
4828
5091
|
customTrackingDomain: outputs.customTrackingDomain
|
|
4829
5092
|
});
|
|
4830
5093
|
console.log(`
|
|
4831
|
-
${
|
|
5094
|
+
${pc11.green("\u2713")} ${pc11.bold("Upgrade complete!")}
|
|
4832
5095
|
`);
|
|
4833
5096
|
if (upgradeAction === "preset" && newPreset) {
|
|
4834
5097
|
console.log(
|
|
4835
|
-
`Upgraded to ${
|
|
5098
|
+
`Upgraded to ${pc11.cyan(newPreset)} preset (${pc11.green(formatCost(newCostData.total.monthly) + "/mo")})
|
|
4836
5099
|
`
|
|
4837
5100
|
);
|
|
4838
5101
|
} else {
|
|
4839
5102
|
console.log(
|
|
4840
|
-
`Updated configuration (${
|
|
5103
|
+
`Updated configuration (${pc11.green(formatCost(newCostData.total.monthly) + "/mo")})
|
|
4841
5104
|
`
|
|
4842
5105
|
);
|
|
4843
5106
|
}
|
|
@@ -4848,15 +5111,16 @@ init_esm_shims();
|
|
|
4848
5111
|
init_aws();
|
|
4849
5112
|
import { Resolver } from "dns/promises";
|
|
4850
5113
|
import { GetEmailIdentityCommand as GetEmailIdentityCommand3, SESv2Client as SESv2Client3 } from "@aws-sdk/client-sesv2";
|
|
4851
|
-
import * as
|
|
4852
|
-
import
|
|
5114
|
+
import * as clack12 from "@clack/prompts";
|
|
5115
|
+
import pc12 from "picocolors";
|
|
4853
5116
|
async function verify(options) {
|
|
4854
|
-
|
|
5117
|
+
clack12.intro(pc12.bold(`Verifying ${options.domain}`));
|
|
4855
5118
|
const progress = new DeploymentProgress();
|
|
4856
5119
|
const region = await getAWSRegion();
|
|
4857
5120
|
const sesClient = new SESv2Client3({ region });
|
|
4858
5121
|
let identity;
|
|
4859
5122
|
let dkimTokens = [];
|
|
5123
|
+
let mailFromDomain;
|
|
4860
5124
|
try {
|
|
4861
5125
|
identity = await progress.execute(
|
|
4862
5126
|
"Checking SES verification status",
|
|
@@ -4868,17 +5132,19 @@ async function verify(options) {
|
|
|
4868
5132
|
}
|
|
4869
5133
|
);
|
|
4870
5134
|
dkimTokens = identity.DkimAttributes?.Tokens || [];
|
|
5135
|
+
mailFromDomain = identity.MailFromAttributes?.MailFromDomain;
|
|
4871
5136
|
} catch (_error) {
|
|
4872
5137
|
progress.stop();
|
|
4873
|
-
|
|
5138
|
+
clack12.log.error(`Domain ${options.domain} not found in SES`);
|
|
4874
5139
|
console.log(
|
|
4875
5140
|
`
|
|
4876
|
-
Run ${
|
|
5141
|
+
Run ${pc12.cyan(`wraps init --domain ${options.domain}`)} to add this domain.
|
|
4877
5142
|
`
|
|
4878
5143
|
);
|
|
4879
5144
|
process.exit(1);
|
|
4880
5145
|
}
|
|
4881
5146
|
const resolver = new Resolver();
|
|
5147
|
+
resolver.setServers(["8.8.8.8", "1.1.1.1"]);
|
|
4882
5148
|
const dnsResults = [];
|
|
4883
5149
|
for (const token of dkimTokens) {
|
|
4884
5150
|
const dkimRecord = `${token}._domainkey.${options.domain}`;
|
|
@@ -4933,54 +5199,97 @@ Run ${pc11.cyan(`wraps init --domain ${options.domain}`)} to add this domain.
|
|
|
4933
5199
|
status: "missing"
|
|
4934
5200
|
});
|
|
4935
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
|
+
}
|
|
4936
5240
|
progress.stop();
|
|
4937
5241
|
const verificationStatus = identity.VerifiedForSendingStatus ? "verified" : "pending";
|
|
4938
5242
|
const dkimStatus = identity.DkimAttributes?.Status || "PENDING";
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
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");
|
|
4947
5256
|
const dnsLines = dnsResults.map((record) => {
|
|
4948
5257
|
let statusIcon;
|
|
4949
5258
|
let statusColor;
|
|
4950
5259
|
if (record.status === "verified") {
|
|
4951
5260
|
statusIcon = "\u2713";
|
|
4952
|
-
statusColor =
|
|
5261
|
+
statusColor = pc12.green;
|
|
4953
5262
|
} else if (record.status === "incorrect") {
|
|
4954
5263
|
statusIcon = "\u2717";
|
|
4955
|
-
statusColor =
|
|
5264
|
+
statusColor = pc12.red;
|
|
4956
5265
|
} else {
|
|
4957
5266
|
statusIcon = "\u2717";
|
|
4958
|
-
statusColor =
|
|
5267
|
+
statusColor = pc12.red;
|
|
4959
5268
|
}
|
|
4960
5269
|
const recordInfo = record.records ? ` \u2192 ${record.records.join(", ")}` : "";
|
|
4961
5270
|
return ` ${statusColor(statusIcon)} ${record.name} (${record.type}) ${statusColor(
|
|
4962
5271
|
record.status
|
|
4963
5272
|
)}${recordInfo}`;
|
|
4964
5273
|
});
|
|
4965
|
-
|
|
5274
|
+
clack12.note(dnsLines.join("\n"), "DNS Records");
|
|
4966
5275
|
const allVerified = dnsResults.every((r) => r.status === "verified");
|
|
4967
5276
|
const someIncorrect = dnsResults.some((r) => r.status === "incorrect");
|
|
4968
5277
|
if (verificationStatus === "verified" && allVerified) {
|
|
4969
|
-
|
|
4970
|
-
|
|
5278
|
+
clack12.outro(
|
|
5279
|
+
pc12.green("\u2713 Domain is fully verified and ready to send emails!")
|
|
4971
5280
|
);
|
|
4972
5281
|
} else if (someIncorrect) {
|
|
4973
|
-
|
|
4974
|
-
|
|
5282
|
+
clack12.outro(
|
|
5283
|
+
pc12.red("\u2717 Some DNS records are incorrect. Please update them.")
|
|
4975
5284
|
);
|
|
4976
5285
|
console.log(
|
|
4977
5286
|
`
|
|
4978
|
-
Run ${
|
|
5287
|
+
Run ${pc12.cyan("wraps status")} to see the correct DNS records.
|
|
4979
5288
|
`
|
|
4980
5289
|
);
|
|
4981
5290
|
} else {
|
|
4982
|
-
|
|
4983
|
-
|
|
5291
|
+
clack12.outro(
|
|
5292
|
+
pc12.yellow("\u23F1 Waiting for DNS propagation and SES verification")
|
|
4984
5293
|
);
|
|
4985
5294
|
console.log("\nDNS records can take up to 48 hours to propagate.");
|
|
4986
5295
|
console.log(
|
|
@@ -5015,44 +5324,61 @@ function printCompletionScript() {
|
|
|
5015
5324
|
|
|
5016
5325
|
// src/cli.ts
|
|
5017
5326
|
init_errors();
|
|
5327
|
+
var __filename2 = fileURLToPath4(import.meta.url);
|
|
5328
|
+
var __dirname3 = dirname2(__filename2);
|
|
5329
|
+
var packageJson = JSON.parse(
|
|
5330
|
+
readFileSync(join4(__dirname3, "../package.json"), "utf-8")
|
|
5331
|
+
);
|
|
5332
|
+
var VERSION = packageJson.version;
|
|
5018
5333
|
setupTabCompletion();
|
|
5334
|
+
function showVersion() {
|
|
5335
|
+
console.log(`wraps v${VERSION}`);
|
|
5336
|
+
process.exit(0);
|
|
5337
|
+
}
|
|
5019
5338
|
function showHelp() {
|
|
5020
|
-
|
|
5339
|
+
clack13.intro(pc13.bold(`WRAPS CLI v${VERSION}`));
|
|
5021
5340
|
console.log("Deploy email infrastructure to your AWS account\n");
|
|
5022
5341
|
console.log("Usage: wraps <command> [options]\n");
|
|
5023
5342
|
console.log("Commands:");
|
|
5024
|
-
console.log(` ${
|
|
5343
|
+
console.log(` ${pc13.cyan("init")} Deploy new email infrastructure`);
|
|
5025
5344
|
console.log(
|
|
5026
|
-
` ${
|
|
5345
|
+
` ${pc13.cyan("connect")} Connect to existing AWS SES infrastructure`
|
|
5027
5346
|
);
|
|
5028
5347
|
console.log(
|
|
5029
|
-
` ${
|
|
5348
|
+
` ${pc13.cyan("console")} Start local web dashboard for monitoring`
|
|
5030
5349
|
);
|
|
5031
5350
|
console.log(
|
|
5032
|
-
` ${
|
|
5351
|
+
` ${pc13.cyan("update")} Update infrastructure with latest CLI changes`
|
|
5033
5352
|
);
|
|
5034
|
-
console.log(` ${pc12.cyan("status")} Show current infrastructure status`);
|
|
5035
5353
|
console.log(
|
|
5036
|
-
` ${
|
|
5354
|
+
` ${pc13.cyan("upgrade")} Add features to existing connection`
|
|
5037
5355
|
);
|
|
5038
|
-
console.log(` ${
|
|
5039
|
-
console.log(
|
|
5040
|
-
|
|
5356
|
+
console.log(` ${pc13.cyan("status")} Show current infrastructure status`);
|
|
5357
|
+
console.log(
|
|
5358
|
+
` ${pc13.cyan("verify")} Verify domain DNS records and SES status`
|
|
5359
|
+
);
|
|
5360
|
+
console.log(` ${pc13.cyan("restore")} Restore original AWS configuration`);
|
|
5361
|
+
console.log(` ${pc13.cyan("destroy")} Remove all deployed infrastructure`);
|
|
5362
|
+
console.log(` ${pc13.cyan("completion")} Generate shell completion script
|
|
5041
5363
|
`);
|
|
5042
5364
|
console.log("Options:");
|
|
5043
5365
|
console.log(
|
|
5044
|
-
` ${
|
|
5366
|
+
` ${pc13.dim("--provider")} Hosting provider (vercel, aws, railway, other)`
|
|
5045
5367
|
);
|
|
5046
|
-
console.log(` ${
|
|
5047
|
-
console.log(` ${
|
|
5048
|
-
console.log(` ${
|
|
5368
|
+
console.log(` ${pc13.dim("--region")} AWS region`);
|
|
5369
|
+
console.log(` ${pc13.dim("--domain")} Domain to verify`);
|
|
5370
|
+
console.log(` ${pc13.dim("--account")} AWS account ID or alias`);
|
|
5371
|
+
console.log(` ${pc13.dim("--version, -v")} Show version number
|
|
5049
5372
|
`);
|
|
5050
5373
|
console.log(
|
|
5051
|
-
`Run ${
|
|
5374
|
+
`Run ${pc13.cyan("wraps <command> --help")} for more information on a command.
|
|
5052
5375
|
`
|
|
5053
5376
|
);
|
|
5054
5377
|
process.exit(0);
|
|
5055
5378
|
}
|
|
5379
|
+
if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
|
5380
|
+
showVersion();
|
|
5381
|
+
}
|
|
5056
5382
|
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
5057
5383
|
showHelp();
|
|
5058
5384
|
}
|
|
@@ -5122,10 +5448,10 @@ async function run() {
|
|
|
5122
5448
|
break;
|
|
5123
5449
|
case "verify":
|
|
5124
5450
|
if (!flags.domain) {
|
|
5125
|
-
|
|
5451
|
+
clack13.log.error("--domain flag is required");
|
|
5126
5452
|
console.log(
|
|
5127
5453
|
`
|
|
5128
|
-
Usage: ${
|
|
5454
|
+
Usage: ${pc13.cyan("wraps verify --domain yourapp.com")}
|
|
5129
5455
|
`
|
|
5130
5456
|
);
|
|
5131
5457
|
process.exit(1);
|
|
@@ -5147,6 +5473,12 @@ Usage: ${pc12.cyan("wraps verify --domain yourapp.com")}
|
|
|
5147
5473
|
noOpen: flags.noOpen
|
|
5148
5474
|
});
|
|
5149
5475
|
break;
|
|
5476
|
+
case "update":
|
|
5477
|
+
await update({
|
|
5478
|
+
region: flags.region,
|
|
5479
|
+
yes: flags.yes
|
|
5480
|
+
});
|
|
5481
|
+
break;
|
|
5150
5482
|
case "upgrade":
|
|
5151
5483
|
await upgrade({
|
|
5152
5484
|
region: flags.region,
|
|
@@ -5168,10 +5500,10 @@ Usage: ${pc12.cyan("wraps verify --domain yourapp.com")}
|
|
|
5168
5500
|
printCompletionScript();
|
|
5169
5501
|
break;
|
|
5170
5502
|
default:
|
|
5171
|
-
|
|
5503
|
+
clack13.log.error(`Unknown command: ${command}`);
|
|
5172
5504
|
console.log(
|
|
5173
5505
|
`
|
|
5174
|
-
Run ${
|
|
5506
|
+
Run ${pc13.cyan("wraps --help")} for available commands.
|
|
5175
5507
|
`
|
|
5176
5508
|
);
|
|
5177
5509
|
process.exit(1);
|