@wraps.dev/cli 2.3.5 → 2.5.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/README.md +55 -0
- package/dist/cli.js +2366 -735
- package/dist/cli.js.map +1 -1
- package/dist/lambda/event-processor/.bundled +1 -1
- package/dist/lambda/sms-event-processor/.bundled +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -1989,9 +1989,11 @@ __export(prompts_exports, {
|
|
|
1989
1989
|
getAvailableFeatures: () => getAvailableFeatures,
|
|
1990
1990
|
promptConfigPreset: () => promptConfigPreset,
|
|
1991
1991
|
promptConflictResolution: () => promptConflictResolution,
|
|
1992
|
+
promptContinueManualDNS: () => promptContinueManualDNS,
|
|
1992
1993
|
promptCustomConfig: () => promptCustomConfig,
|
|
1993
1994
|
promptDNSConfirmation: () => promptDNSConfirmation,
|
|
1994
1995
|
promptDNSManagement: () => promptDNSManagement,
|
|
1996
|
+
promptDNSProvider: () => promptDNSProvider,
|
|
1995
1997
|
promptDomain: () => promptDomain,
|
|
1996
1998
|
promptEmailArchiving: () => promptEmailArchiving,
|
|
1997
1999
|
promptEstimatedVolume: () => promptEstimatedVolume,
|
|
@@ -2713,6 +2715,58 @@ async function promptDNSManagement(domain) {
|
|
|
2713
2715
|
}
|
|
2714
2716
|
return manage;
|
|
2715
2717
|
}
|
|
2718
|
+
async function promptDNSProvider(domain, availableProviders) {
|
|
2719
|
+
const options = availableProviders.map((p) => {
|
|
2720
|
+
let label;
|
|
2721
|
+
let hint;
|
|
2722
|
+
switch (p.provider) {
|
|
2723
|
+
case "route53":
|
|
2724
|
+
label = "AWS Route53";
|
|
2725
|
+
hint = p.detected ? "Hosted zone detected" : "Requires Route53 hosted zone";
|
|
2726
|
+
break;
|
|
2727
|
+
case "vercel":
|
|
2728
|
+
label = "Vercel DNS";
|
|
2729
|
+
hint = p.detected ? "Token found" : "Requires VERCEL_TOKEN";
|
|
2730
|
+
break;
|
|
2731
|
+
case "cloudflare":
|
|
2732
|
+
label = "Cloudflare";
|
|
2733
|
+
hint = p.detected ? "Token found" : "Requires CLOUDFLARE_API_TOKEN";
|
|
2734
|
+
break;
|
|
2735
|
+
case "manual":
|
|
2736
|
+
label = "Manual";
|
|
2737
|
+
hint = "I'll add DNS records myself";
|
|
2738
|
+
break;
|
|
2739
|
+
}
|
|
2740
|
+
if (p.detected && p.provider !== "manual") {
|
|
2741
|
+
label = `${label} (Recommended)`;
|
|
2742
|
+
}
|
|
2743
|
+
return {
|
|
2744
|
+
value: p.provider,
|
|
2745
|
+
label,
|
|
2746
|
+
hint: p.hint || hint
|
|
2747
|
+
};
|
|
2748
|
+
});
|
|
2749
|
+
const provider = await clack3.select({
|
|
2750
|
+
message: `Where do you manage DNS for ${pc4.cyan(domain)}?`,
|
|
2751
|
+
options
|
|
2752
|
+
});
|
|
2753
|
+
if (clack3.isCancel(provider)) {
|
|
2754
|
+
clack3.cancel("Operation cancelled.");
|
|
2755
|
+
process.exit(0);
|
|
2756
|
+
}
|
|
2757
|
+
return provider;
|
|
2758
|
+
}
|
|
2759
|
+
async function promptContinueManualDNS() {
|
|
2760
|
+
const continueManual = await clack3.confirm({
|
|
2761
|
+
message: "Continue with manual DNS setup?",
|
|
2762
|
+
initialValue: true
|
|
2763
|
+
});
|
|
2764
|
+
if (clack3.isCancel(continueManual)) {
|
|
2765
|
+
clack3.cancel("Operation cancelled.");
|
|
2766
|
+
process.exit(0);
|
|
2767
|
+
}
|
|
2768
|
+
return continueManual;
|
|
2769
|
+
}
|
|
2716
2770
|
var DNS_CATEGORY_LABELS, DNS_STATUS_SYMBOLS;
|
|
2717
2771
|
var init_prompts = __esm({
|
|
2718
2772
|
"src/utils/shared/prompts.ts"() {
|
|
@@ -4433,185 +4487,1108 @@ var init_mail_manager = __esm({
|
|
|
4433
4487
|
}
|
|
4434
4488
|
});
|
|
4435
4489
|
|
|
4436
|
-
// src/utils/
|
|
4437
|
-
var
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
});
|
|
4441
|
-
import { AssumeRoleCommand, STSClient as STSClient3 } from "@aws-sdk/client-sts";
|
|
4442
|
-
async function assumeRole(roleArn, region, sessionName = "wraps-console") {
|
|
4443
|
-
const sts = new STSClient3({ region });
|
|
4444
|
-
const response = await sts.send(
|
|
4445
|
-
new AssumeRoleCommand({
|
|
4446
|
-
RoleArn: roleArn,
|
|
4447
|
-
RoleSessionName: sessionName,
|
|
4448
|
-
DurationSeconds: 3600
|
|
4449
|
-
// 1 hour
|
|
4450
|
-
})
|
|
4451
|
-
);
|
|
4452
|
-
if (!response.Credentials) {
|
|
4453
|
-
throw new Error("Failed to assume role: No credentials returned");
|
|
4454
|
-
}
|
|
4455
|
-
return {
|
|
4456
|
-
accessKeyId: response.Credentials.AccessKeyId,
|
|
4457
|
-
secretAccessKey: response.Credentials.SecretAccessKey,
|
|
4458
|
-
sessionToken: response.Credentials.SessionToken,
|
|
4459
|
-
expiration: response.Credentials.Expiration
|
|
4460
|
-
};
|
|
4461
|
-
}
|
|
4462
|
-
var init_assume_role = __esm({
|
|
4463
|
-
"src/utils/shared/assume-role.ts"() {
|
|
4490
|
+
// src/utils/dns/cloudflare.ts
|
|
4491
|
+
var CLOUDFLARE_API_BASE, CloudflareDNSClient;
|
|
4492
|
+
var init_cloudflare = __esm({
|
|
4493
|
+
"src/utils/dns/cloudflare.ts"() {
|
|
4464
4494
|
"use strict";
|
|
4465
4495
|
init_esm_shims();
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
}
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
const archiveId = extractArchiveId(archiveArnOrId);
|
|
4488
|
-
const searchTime = searchCriteria.timestamp || /* @__PURE__ */ new Date();
|
|
4489
|
-
const dayBefore = new Date(searchTime.getTime() - 24 * 60 * 60 * 1e3);
|
|
4490
|
-
const dayAfter = new Date(searchTime.getTime() + 24 * 60 * 60 * 1e3);
|
|
4491
|
-
const filters = [];
|
|
4492
|
-
if (searchCriteria.from) {
|
|
4493
|
-
filters.push({
|
|
4494
|
-
StringExpression: {
|
|
4495
|
-
Evaluate: {
|
|
4496
|
-
Attribute: "FROM"
|
|
4497
|
-
},
|
|
4498
|
-
Operator: "CONTAINS",
|
|
4499
|
-
Values: [searchCriteria.from]
|
|
4496
|
+
CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4";
|
|
4497
|
+
CloudflareDNSClient = class {
|
|
4498
|
+
zoneId;
|
|
4499
|
+
apiToken;
|
|
4500
|
+
constructor(zoneId, apiToken) {
|
|
4501
|
+
this.zoneId = zoneId;
|
|
4502
|
+
this.apiToken = apiToken;
|
|
4503
|
+
}
|
|
4504
|
+
async request(endpoint, method = "GET", body) {
|
|
4505
|
+
const response = await fetch(
|
|
4506
|
+
`${CLOUDFLARE_API_BASE}/zones/${this.zoneId}${endpoint}`,
|
|
4507
|
+
{
|
|
4508
|
+
method,
|
|
4509
|
+
headers: {
|
|
4510
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
4511
|
+
"Content-Type": "application/json"
|
|
4512
|
+
},
|
|
4513
|
+
body: body ? JSON.stringify(body) : void 0
|
|
4514
|
+
}
|
|
4515
|
+
);
|
|
4516
|
+
return response.json();
|
|
4500
4517
|
}
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4518
|
+
async createRecord(name, type, content, priority) {
|
|
4519
|
+
const body = {
|
|
4520
|
+
name,
|
|
4521
|
+
type,
|
|
4522
|
+
content,
|
|
4523
|
+
ttl: 1800,
|
|
4524
|
+
proxied: false
|
|
4525
|
+
// Must not be proxied for email records
|
|
4526
|
+
};
|
|
4527
|
+
if (priority !== void 0) {
|
|
4528
|
+
body.priority = priority;
|
|
4529
|
+
}
|
|
4530
|
+
const result = await this.request(
|
|
4531
|
+
"/dns_records",
|
|
4532
|
+
"POST",
|
|
4533
|
+
body
|
|
4534
|
+
);
|
|
4535
|
+
return result.success;
|
|
4511
4536
|
}
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
Operator: "CONTAINS",
|
|
4521
|
-
Values: [searchCriteria.subject]
|
|
4537
|
+
async findRecord(name, type) {
|
|
4538
|
+
const result = await this.request(
|
|
4539
|
+
`/dns_records?name=${encodeURIComponent(name)}&type=${type}`
|
|
4540
|
+
);
|
|
4541
|
+
if (result.success && result.result.length > 0) {
|
|
4542
|
+
return result.result[0];
|
|
4543
|
+
}
|
|
4544
|
+
return null;
|
|
4522
4545
|
}
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
}
|
|
4530
|
-
const searchCommand = new StartArchiveSearchCommand({
|
|
4531
|
-
ArchiveId: archiveId,
|
|
4532
|
-
FromTimestamp: dayBefore,
|
|
4533
|
-
ToTimestamp: dayAfter,
|
|
4534
|
-
Filters: {
|
|
4535
|
-
Include: filters
|
|
4536
|
-
},
|
|
4537
|
-
MaxResults: 10
|
|
4538
|
-
// Get a few results in case there are multiple matches
|
|
4539
|
-
});
|
|
4540
|
-
const searchResponse = await client.send(searchCommand);
|
|
4541
|
-
const searchId = searchResponse.SearchId;
|
|
4542
|
-
if (!searchId) {
|
|
4543
|
-
throw new Error("Failed to start archive search");
|
|
4544
|
-
}
|
|
4545
|
-
let archivedMessageId;
|
|
4546
|
-
let attempts = 0;
|
|
4547
|
-
const maxAttempts = 20;
|
|
4548
|
-
const pollInterval = 1e3;
|
|
4549
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
4550
|
-
while (attempts < maxAttempts) {
|
|
4551
|
-
try {
|
|
4552
|
-
const resultsCommand = new GetArchiveSearchResultsCommand({
|
|
4553
|
-
SearchId: searchId
|
|
4554
|
-
});
|
|
4555
|
-
const resultsResponse = await client.send(resultsCommand);
|
|
4556
|
-
if (resultsResponse.Rows && resultsResponse.Rows.length > 0) {
|
|
4557
|
-
archivedMessageId = resultsResponse.Rows[0].ArchivedMessageId;
|
|
4558
|
-
break;
|
|
4546
|
+
async deleteRecord(recordId) {
|
|
4547
|
+
const result = await this.request(
|
|
4548
|
+
`/dns_records/${recordId}`,
|
|
4549
|
+
"DELETE"
|
|
4550
|
+
);
|
|
4551
|
+
return result.success;
|
|
4559
4552
|
}
|
|
4560
|
-
|
|
4561
|
-
|
|
4553
|
+
async createEmailRecords(data) {
|
|
4554
|
+
const { domain, dkimTokens, mailFromDomain, region } = data;
|
|
4555
|
+
const errors2 = [];
|
|
4556
|
+
let recordsCreated = 0;
|
|
4557
|
+
try {
|
|
4558
|
+
for (const token of dkimTokens) {
|
|
4559
|
+
const name = `${token}._domainkey.${domain}`;
|
|
4560
|
+
const success = await this.createRecord(
|
|
4561
|
+
name,
|
|
4562
|
+
"CNAME",
|
|
4563
|
+
`${token}.dkim.amazonses.com`
|
|
4564
|
+
);
|
|
4565
|
+
if (success) {
|
|
4566
|
+
recordsCreated++;
|
|
4567
|
+
} else {
|
|
4568
|
+
errors2.push(`Failed to create DKIM record: ${name}`);
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
const spfSuccess = await this.createRecord(
|
|
4572
|
+
domain,
|
|
4573
|
+
"TXT",
|
|
4574
|
+
"v=spf1 include:amazonses.com ~all"
|
|
4575
|
+
);
|
|
4576
|
+
if (spfSuccess) {
|
|
4577
|
+
recordsCreated++;
|
|
4578
|
+
} else {
|
|
4579
|
+
errors2.push(`Failed to create SPF record for ${domain}`);
|
|
4580
|
+
}
|
|
4581
|
+
const dmarcSuccess = await this.createRecord(
|
|
4582
|
+
`_dmarc.${domain}`,
|
|
4583
|
+
"TXT",
|
|
4584
|
+
`v=DMARC1; p=quarantine; rua=mailto:postmaster@${mailFromDomain || domain}`
|
|
4585
|
+
);
|
|
4586
|
+
if (dmarcSuccess) {
|
|
4587
|
+
recordsCreated++;
|
|
4588
|
+
} else {
|
|
4589
|
+
errors2.push(`Failed to create DMARC record for ${domain}`);
|
|
4590
|
+
}
|
|
4591
|
+
if (mailFromDomain) {
|
|
4592
|
+
const mxSuccess = await this.createRecord(
|
|
4593
|
+
mailFromDomain,
|
|
4594
|
+
"MX",
|
|
4595
|
+
`feedback-smtp.${region}.amazonses.com`,
|
|
4596
|
+
10
|
|
4597
|
+
);
|
|
4598
|
+
if (mxSuccess) {
|
|
4599
|
+
recordsCreated++;
|
|
4600
|
+
} else {
|
|
4601
|
+
errors2.push(`Failed to create MX record for ${mailFromDomain}`);
|
|
4602
|
+
}
|
|
4603
|
+
const mailFromSpfSuccess = await this.createRecord(
|
|
4604
|
+
mailFromDomain,
|
|
4605
|
+
"TXT",
|
|
4606
|
+
"v=spf1 include:amazonses.com ~all"
|
|
4607
|
+
);
|
|
4608
|
+
if (mailFromSpfSuccess) {
|
|
4609
|
+
recordsCreated++;
|
|
4610
|
+
} else {
|
|
4611
|
+
errors2.push(`Failed to create SPF record for ${mailFromDomain}`);
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4614
|
+
return {
|
|
4615
|
+
success: errors2.length === 0,
|
|
4616
|
+
recordsCreated,
|
|
4617
|
+
errors: errors2.length > 0 ? errors2 : void 0
|
|
4618
|
+
};
|
|
4619
|
+
} catch (error) {
|
|
4620
|
+
return {
|
|
4621
|
+
success: false,
|
|
4622
|
+
recordsCreated,
|
|
4623
|
+
errors: [
|
|
4624
|
+
...errors2,
|
|
4625
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
4626
|
+
]
|
|
4627
|
+
};
|
|
4628
|
+
}
|
|
4562
4629
|
}
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4630
|
+
async deleteEmailRecords(data) {
|
|
4631
|
+
const { domain, dkimTokens, mailFromDomain } = data;
|
|
4632
|
+
const errors2 = [];
|
|
4633
|
+
let recordsCreated = 0;
|
|
4634
|
+
try {
|
|
4635
|
+
for (const token of dkimTokens) {
|
|
4636
|
+
const name = `${token}._domainkey.${domain}`;
|
|
4637
|
+
const record = await this.findRecord(name, "CNAME");
|
|
4638
|
+
if (record) {
|
|
4639
|
+
const success = await this.deleteRecord(record.id);
|
|
4640
|
+
if (success) {
|
|
4641
|
+
recordsCreated++;
|
|
4642
|
+
} else {
|
|
4643
|
+
errors2.push(`Failed to delete DKIM record: ${name}`);
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4647
|
+
const dmarcRecord = await this.findRecord(`_dmarc.${domain}`, "TXT");
|
|
4648
|
+
if (dmarcRecord) {
|
|
4649
|
+
const success = await this.deleteRecord(dmarcRecord.id);
|
|
4650
|
+
if (success) {
|
|
4651
|
+
recordsCreated++;
|
|
4652
|
+
} else {
|
|
4653
|
+
errors2.push("Failed to delete DMARC record");
|
|
4654
|
+
}
|
|
4655
|
+
}
|
|
4656
|
+
if (mailFromDomain) {
|
|
4657
|
+
const mxRecord = await this.findRecord(mailFromDomain, "MX");
|
|
4658
|
+
if (mxRecord) {
|
|
4659
|
+
const success = await this.deleteRecord(mxRecord.id);
|
|
4660
|
+
if (success) {
|
|
4661
|
+
recordsCreated++;
|
|
4662
|
+
} else {
|
|
4663
|
+
errors2.push(`Failed to delete MX record for ${mailFromDomain}`);
|
|
4664
|
+
}
|
|
4665
|
+
}
|
|
4666
|
+
const spfRecord = await this.findRecord(mailFromDomain, "TXT");
|
|
4667
|
+
if (spfRecord) {
|
|
4668
|
+
const success = await this.deleteRecord(spfRecord.id);
|
|
4669
|
+
if (success) {
|
|
4670
|
+
recordsCreated++;
|
|
4671
|
+
} else {
|
|
4672
|
+
errors2.push(`Failed to delete SPF record for ${mailFromDomain}`);
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
}
|
|
4676
|
+
return {
|
|
4677
|
+
success: errors2.length === 0,
|
|
4678
|
+
recordsCreated,
|
|
4679
|
+
errors: errors2.length > 0 ? errors2 : void 0
|
|
4680
|
+
};
|
|
4681
|
+
} catch (error) {
|
|
4682
|
+
return {
|
|
4683
|
+
success: false,
|
|
4684
|
+
recordsCreated,
|
|
4685
|
+
errors: [
|
|
4686
|
+
...errors2,
|
|
4687
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
4688
|
+
]
|
|
4689
|
+
};
|
|
4690
|
+
}
|
|
4568
4691
|
}
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4692
|
+
async verifyRecords(data) {
|
|
4693
|
+
const { domain, dkimTokens, mailFromDomain, region } = data;
|
|
4694
|
+
const missing = [];
|
|
4695
|
+
const incorrect = [];
|
|
4696
|
+
for (const token of dkimTokens) {
|
|
4697
|
+
const name = `${token}._domainkey.${domain}`;
|
|
4698
|
+
const expectedValue = `${token}.dkim.amazonses.com`;
|
|
4699
|
+
const record = await this.findRecord(name, "CNAME");
|
|
4700
|
+
if (!record) {
|
|
4701
|
+
missing.push(`DKIM: ${name}`);
|
|
4702
|
+
} else if (record.content !== expectedValue) {
|
|
4703
|
+
incorrect.push(`DKIM: ${name} (expected ${expectedValue})`);
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
const spfRecord = await this.findRecord(domain, "TXT");
|
|
4707
|
+
if (!spfRecord) {
|
|
4708
|
+
missing.push(`SPF: ${domain}`);
|
|
4709
|
+
} else if (!spfRecord.content.includes("include:amazonses.com")) {
|
|
4710
|
+
incorrect.push(`SPF: ${domain} (missing amazonses.com include)`);
|
|
4711
|
+
}
|
|
4712
|
+
const dmarcRecord = await this.findRecord(`_dmarc.${domain}`, "TXT");
|
|
4713
|
+
if (!dmarcRecord) {
|
|
4714
|
+
missing.push(`DMARC: _dmarc.${domain}`);
|
|
4715
|
+
}
|
|
4716
|
+
if (mailFromDomain) {
|
|
4717
|
+
const mxRecord = await this.findRecord(mailFromDomain, "MX");
|
|
4718
|
+
if (!mxRecord) {
|
|
4719
|
+
missing.push(`MX: ${mailFromDomain}`);
|
|
4720
|
+
} else if (!mxRecord.content.includes(`feedback-smtp.${region}.amazonses.com`)) {
|
|
4721
|
+
incorrect.push(`MX: ${mailFromDomain}`);
|
|
4722
|
+
}
|
|
4723
|
+
const mailFromSpf = await this.findRecord(mailFromDomain, "TXT");
|
|
4724
|
+
if (!mailFromSpf) {
|
|
4725
|
+
missing.push(`SPF: ${mailFromDomain}`);
|
|
4726
|
+
}
|
|
4727
|
+
}
|
|
4728
|
+
return {
|
|
4729
|
+
verified: missing.length === 0 && incorrect.length === 0,
|
|
4730
|
+
missing,
|
|
4731
|
+
incorrect
|
|
4732
|
+
};
|
|
4607
4733
|
}
|
|
4608
|
-
}
|
|
4734
|
+
};
|
|
4609
4735
|
}
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4736
|
+
});
|
|
4737
|
+
|
|
4738
|
+
// src/utils/dns/vercel.ts
|
|
4739
|
+
var VERCEL_API_BASE, VercelDNSClient;
|
|
4740
|
+
var init_vercel = __esm({
|
|
4741
|
+
"src/utils/dns/vercel.ts"() {
|
|
4742
|
+
"use strict";
|
|
4743
|
+
init_esm_shims();
|
|
4744
|
+
VERCEL_API_BASE = "https://api.vercel.com";
|
|
4745
|
+
VercelDNSClient = class {
|
|
4746
|
+
domain;
|
|
4747
|
+
apiToken;
|
|
4748
|
+
teamId;
|
|
4749
|
+
constructor(domain, apiToken, teamId) {
|
|
4750
|
+
this.domain = domain;
|
|
4751
|
+
this.apiToken = apiToken;
|
|
4752
|
+
this.teamId = teamId;
|
|
4753
|
+
}
|
|
4754
|
+
get teamParam() {
|
|
4755
|
+
return this.teamId ? `&teamId=${this.teamId}` : "";
|
|
4756
|
+
}
|
|
4757
|
+
async request(endpoint, method = "GET", body) {
|
|
4758
|
+
const url = `${VERCEL_API_BASE}${endpoint}${endpoint.includes("?") ? "&" : "?"}${this.teamParam.slice(1)}`;
|
|
4759
|
+
const response = await fetch(url, {
|
|
4760
|
+
method,
|
|
4761
|
+
headers: {
|
|
4762
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
4763
|
+
"Content-Type": "application/json"
|
|
4764
|
+
},
|
|
4765
|
+
body: body ? JSON.stringify(body) : void 0
|
|
4766
|
+
});
|
|
4767
|
+
return response.json();
|
|
4768
|
+
}
|
|
4769
|
+
async createRecord(name, type, value, mxPriority) {
|
|
4770
|
+
const relativeName = name === this.domain ? "@" : name.replace(`.${this.domain}`, "");
|
|
4771
|
+
const body = {
|
|
4772
|
+
name: relativeName,
|
|
4773
|
+
type,
|
|
4774
|
+
value,
|
|
4775
|
+
ttl: 1800
|
|
4776
|
+
};
|
|
4777
|
+
if (mxPriority !== void 0) {
|
|
4778
|
+
body.mxPriority = mxPriority;
|
|
4779
|
+
}
|
|
4780
|
+
const result = await this.request(
|
|
4781
|
+
`/v2/domains/${this.domain}/records`,
|
|
4782
|
+
"POST",
|
|
4783
|
+
body
|
|
4784
|
+
);
|
|
4785
|
+
return !result.error;
|
|
4786
|
+
}
|
|
4787
|
+
async findRecord(name, type) {
|
|
4788
|
+
const result = await this.request(
|
|
4789
|
+
`/v4/domains/${this.domain}/records`
|
|
4790
|
+
);
|
|
4791
|
+
if (result.error || !result.records) {
|
|
4792
|
+
return null;
|
|
4793
|
+
}
|
|
4794
|
+
const relativeName = name === this.domain ? "@" : name.replace(`.${this.domain}`, "");
|
|
4795
|
+
return result.records.find(
|
|
4796
|
+
(r) => (r.name === relativeName || r.name === name) && r.type === type
|
|
4797
|
+
) || null;
|
|
4798
|
+
}
|
|
4799
|
+
async deleteRecord(recordId) {
|
|
4800
|
+
const result = await this.request(
|
|
4801
|
+
`/v2/domains/${this.domain}/records/${recordId}`,
|
|
4802
|
+
"DELETE"
|
|
4803
|
+
);
|
|
4804
|
+
return !result.error;
|
|
4805
|
+
}
|
|
4806
|
+
async createEmailRecords(data) {
|
|
4807
|
+
const { domain, dkimTokens, mailFromDomain, region } = data;
|
|
4808
|
+
const errors2 = [];
|
|
4809
|
+
let recordsCreated = 0;
|
|
4810
|
+
try {
|
|
4811
|
+
for (const token of dkimTokens) {
|
|
4812
|
+
const name = `${token}._domainkey.${domain}`;
|
|
4813
|
+
const success = await this.createRecord(
|
|
4814
|
+
name,
|
|
4815
|
+
"CNAME",
|
|
4816
|
+
`${token}.dkim.amazonses.com`
|
|
4817
|
+
);
|
|
4818
|
+
if (success) {
|
|
4819
|
+
recordsCreated++;
|
|
4820
|
+
} else {
|
|
4821
|
+
errors2.push(`Failed to create DKIM record: ${name}`);
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
const spfSuccess = await this.createRecord(
|
|
4825
|
+
domain,
|
|
4826
|
+
"TXT",
|
|
4827
|
+
"v=spf1 include:amazonses.com ~all"
|
|
4828
|
+
);
|
|
4829
|
+
if (spfSuccess) {
|
|
4830
|
+
recordsCreated++;
|
|
4831
|
+
} else {
|
|
4832
|
+
errors2.push(`Failed to create SPF record for ${domain}`);
|
|
4833
|
+
}
|
|
4834
|
+
const dmarcSuccess = await this.createRecord(
|
|
4835
|
+
`_dmarc.${domain}`,
|
|
4836
|
+
"TXT",
|
|
4837
|
+
`v=DMARC1; p=quarantine; rua=mailto:postmaster@${mailFromDomain || domain}`
|
|
4838
|
+
);
|
|
4839
|
+
if (dmarcSuccess) {
|
|
4840
|
+
recordsCreated++;
|
|
4841
|
+
} else {
|
|
4842
|
+
errors2.push(`Failed to create DMARC record for ${domain}`);
|
|
4843
|
+
}
|
|
4844
|
+
if (mailFromDomain) {
|
|
4845
|
+
const mxSuccess = await this.createRecord(
|
|
4846
|
+
mailFromDomain,
|
|
4847
|
+
"MX",
|
|
4848
|
+
`feedback-smtp.${region}.amazonses.com`,
|
|
4849
|
+
10
|
|
4850
|
+
);
|
|
4851
|
+
if (mxSuccess) {
|
|
4852
|
+
recordsCreated++;
|
|
4853
|
+
} else {
|
|
4854
|
+
errors2.push(`Failed to create MX record for ${mailFromDomain}`);
|
|
4855
|
+
}
|
|
4856
|
+
const mailFromSpfSuccess = await this.createRecord(
|
|
4857
|
+
mailFromDomain,
|
|
4858
|
+
"TXT",
|
|
4859
|
+
"v=spf1 include:amazonses.com ~all"
|
|
4860
|
+
);
|
|
4861
|
+
if (mailFromSpfSuccess) {
|
|
4862
|
+
recordsCreated++;
|
|
4863
|
+
} else {
|
|
4864
|
+
errors2.push(`Failed to create SPF record for ${mailFromDomain}`);
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
return {
|
|
4868
|
+
success: errors2.length === 0,
|
|
4869
|
+
recordsCreated,
|
|
4870
|
+
errors: errors2.length > 0 ? errors2 : void 0
|
|
4871
|
+
};
|
|
4872
|
+
} catch (error) {
|
|
4873
|
+
return {
|
|
4874
|
+
success: false,
|
|
4875
|
+
recordsCreated,
|
|
4876
|
+
errors: [
|
|
4877
|
+
...errors2,
|
|
4878
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
4879
|
+
]
|
|
4880
|
+
};
|
|
4881
|
+
}
|
|
4882
|
+
}
|
|
4883
|
+
async deleteEmailRecords(data) {
|
|
4884
|
+
const { domain, dkimTokens, mailFromDomain } = data;
|
|
4885
|
+
const errors2 = [];
|
|
4886
|
+
let recordsCreated = 0;
|
|
4887
|
+
try {
|
|
4888
|
+
for (const token of dkimTokens) {
|
|
4889
|
+
const name = `${token}._domainkey.${domain}`;
|
|
4890
|
+
const record = await this.findRecord(name, "CNAME");
|
|
4891
|
+
if (record) {
|
|
4892
|
+
const success = await this.deleteRecord(record.id);
|
|
4893
|
+
if (success) {
|
|
4894
|
+
recordsCreated++;
|
|
4895
|
+
} else {
|
|
4896
|
+
errors2.push(`Failed to delete DKIM record: ${name}`);
|
|
4897
|
+
}
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4900
|
+
const dmarcRecord = await this.findRecord(`_dmarc.${domain}`, "TXT");
|
|
4901
|
+
if (dmarcRecord) {
|
|
4902
|
+
const success = await this.deleteRecord(dmarcRecord.id);
|
|
4903
|
+
if (success) {
|
|
4904
|
+
recordsCreated++;
|
|
4905
|
+
} else {
|
|
4906
|
+
errors2.push("Failed to delete DMARC record");
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
4909
|
+
if (mailFromDomain) {
|
|
4910
|
+
const mxRecord = await this.findRecord(mailFromDomain, "MX");
|
|
4911
|
+
if (mxRecord) {
|
|
4912
|
+
const success = await this.deleteRecord(mxRecord.id);
|
|
4913
|
+
if (success) {
|
|
4914
|
+
recordsCreated++;
|
|
4915
|
+
} else {
|
|
4916
|
+
errors2.push(`Failed to delete MX record for ${mailFromDomain}`);
|
|
4917
|
+
}
|
|
4918
|
+
}
|
|
4919
|
+
const spfRecord = await this.findRecord(mailFromDomain, "TXT");
|
|
4920
|
+
if (spfRecord) {
|
|
4921
|
+
const success = await this.deleteRecord(spfRecord.id);
|
|
4922
|
+
if (success) {
|
|
4923
|
+
recordsCreated++;
|
|
4924
|
+
} else {
|
|
4925
|
+
errors2.push(`Failed to delete SPF record for ${mailFromDomain}`);
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4928
|
+
}
|
|
4929
|
+
return {
|
|
4930
|
+
success: errors2.length === 0,
|
|
4931
|
+
recordsCreated,
|
|
4932
|
+
errors: errors2.length > 0 ? errors2 : void 0
|
|
4933
|
+
};
|
|
4934
|
+
} catch (error) {
|
|
4935
|
+
return {
|
|
4936
|
+
success: false,
|
|
4937
|
+
recordsCreated,
|
|
4938
|
+
errors: [
|
|
4939
|
+
...errors2,
|
|
4940
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
4941
|
+
]
|
|
4942
|
+
};
|
|
4943
|
+
}
|
|
4944
|
+
}
|
|
4945
|
+
async verifyRecords(data) {
|
|
4946
|
+
const { domain, dkimTokens, mailFromDomain, region } = data;
|
|
4947
|
+
const missing = [];
|
|
4948
|
+
const incorrect = [];
|
|
4949
|
+
for (const token of dkimTokens) {
|
|
4950
|
+
const name = `${token}._domainkey.${domain}`;
|
|
4951
|
+
const expectedValue = `${token}.dkim.amazonses.com`;
|
|
4952
|
+
const record = await this.findRecord(name, "CNAME");
|
|
4953
|
+
if (!record) {
|
|
4954
|
+
missing.push(`DKIM: ${name}`);
|
|
4955
|
+
} else if (record.value !== expectedValue) {
|
|
4956
|
+
incorrect.push(`DKIM: ${name} (expected ${expectedValue})`);
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
const spfRecord = await this.findRecord(domain, "TXT");
|
|
4960
|
+
if (!spfRecord) {
|
|
4961
|
+
missing.push(`SPF: ${domain}`);
|
|
4962
|
+
} else if (!spfRecord.value.includes("include:amazonses.com")) {
|
|
4963
|
+
incorrect.push(`SPF: ${domain} (missing amazonses.com include)`);
|
|
4964
|
+
}
|
|
4965
|
+
const dmarcRecord = await this.findRecord(`_dmarc.${domain}`, "TXT");
|
|
4966
|
+
if (!dmarcRecord) {
|
|
4967
|
+
missing.push(`DMARC: _dmarc.${domain}`);
|
|
4968
|
+
}
|
|
4969
|
+
if (mailFromDomain) {
|
|
4970
|
+
const mxRecord = await this.findRecord(mailFromDomain, "MX");
|
|
4971
|
+
if (!mxRecord) {
|
|
4972
|
+
missing.push(`MX: ${mailFromDomain}`);
|
|
4973
|
+
} else if (!mxRecord.value.includes(`feedback-smtp.${region}.amazonses.com`)) {
|
|
4974
|
+
incorrect.push(`MX: ${mailFromDomain}`);
|
|
4975
|
+
}
|
|
4976
|
+
const mailFromSpf = await this.findRecord(mailFromDomain, "TXT");
|
|
4977
|
+
if (!mailFromSpf) {
|
|
4978
|
+
missing.push(`SPF: ${mailFromDomain}`);
|
|
4979
|
+
}
|
|
4980
|
+
}
|
|
4981
|
+
return {
|
|
4982
|
+
verified: missing.length === 0 && incorrect.length === 0,
|
|
4983
|
+
missing,
|
|
4984
|
+
incorrect
|
|
4985
|
+
};
|
|
4986
|
+
}
|
|
4987
|
+
};
|
|
4988
|
+
}
|
|
4989
|
+
});
|
|
4990
|
+
|
|
4991
|
+
// src/utils/dns/create-records.ts
|
|
4992
|
+
function buildEmailDNSRecords(data) {
|
|
4993
|
+
const { domain, dkimTokens, mailFromDomain, region } = data;
|
|
4994
|
+
const records = [];
|
|
4995
|
+
for (const token of dkimTokens) {
|
|
4996
|
+
records.push({
|
|
4997
|
+
name: `${token}._domainkey.${domain}`,
|
|
4998
|
+
type: "CNAME",
|
|
4999
|
+
value: `${token}.dkim.amazonses.com`,
|
|
5000
|
+
category: "dkim"
|
|
5001
|
+
});
|
|
5002
|
+
}
|
|
5003
|
+
records.push({
|
|
5004
|
+
name: domain,
|
|
5005
|
+
type: "TXT",
|
|
5006
|
+
value: "v=spf1 include:amazonses.com ~all",
|
|
5007
|
+
category: "spf"
|
|
5008
|
+
});
|
|
5009
|
+
const dmarcRuaDomain = mailFromDomain || domain;
|
|
5010
|
+
records.push({
|
|
5011
|
+
name: `_dmarc.${domain}`,
|
|
5012
|
+
type: "TXT",
|
|
5013
|
+
value: `v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}`,
|
|
5014
|
+
category: "dmarc"
|
|
5015
|
+
});
|
|
5016
|
+
if (mailFromDomain) {
|
|
5017
|
+
records.push({
|
|
5018
|
+
name: mailFromDomain,
|
|
5019
|
+
type: "MX",
|
|
5020
|
+
value: `feedback-smtp.${region}.amazonses.com`,
|
|
5021
|
+
priority: 10,
|
|
5022
|
+
category: "mailfrom_mx"
|
|
5023
|
+
});
|
|
5024
|
+
records.push({
|
|
5025
|
+
name: mailFromDomain,
|
|
5026
|
+
type: "TXT",
|
|
5027
|
+
value: "v=spf1 include:amazonses.com ~all",
|
|
5028
|
+
category: "mailfrom_spf"
|
|
5029
|
+
});
|
|
5030
|
+
}
|
|
5031
|
+
return records;
|
|
5032
|
+
}
|
|
5033
|
+
function formatDNSRecordsForDisplay(records) {
|
|
5034
|
+
return records.map((r) => ({
|
|
5035
|
+
name: r.name,
|
|
5036
|
+
type: r.type,
|
|
5037
|
+
value: r.priority ? `${r.priority} ${r.value}` : r.value
|
|
5038
|
+
}));
|
|
5039
|
+
}
|
|
5040
|
+
async function createDNSRecordsForProvider(credentials, data, selectedCategories) {
|
|
5041
|
+
switch (credentials.provider) {
|
|
5042
|
+
case "route53": {
|
|
5043
|
+
const categories = selectedCategories || /* @__PURE__ */ new Set([
|
|
5044
|
+
"dkim",
|
|
5045
|
+
"spf",
|
|
5046
|
+
"dmarc",
|
|
5047
|
+
"mailfrom_mx",
|
|
5048
|
+
"mailfrom_spf"
|
|
5049
|
+
]);
|
|
5050
|
+
try {
|
|
5051
|
+
await createSelectedDNSRecords(
|
|
5052
|
+
credentials.hostedZoneId,
|
|
5053
|
+
data.domain,
|
|
5054
|
+
data.dkimTokens,
|
|
5055
|
+
data.region,
|
|
5056
|
+
categories,
|
|
5057
|
+
void 0,
|
|
5058
|
+
// customTrackingDomain - not used here
|
|
5059
|
+
data.mailFromDomain
|
|
5060
|
+
);
|
|
5061
|
+
let recordsCreated = 0;
|
|
5062
|
+
if (categories.has("dkim")) recordsCreated += data.dkimTokens.length;
|
|
5063
|
+
if (categories.has("spf")) recordsCreated += 1;
|
|
5064
|
+
if (categories.has("dmarc")) recordsCreated += 1;
|
|
5065
|
+
if (data.mailFromDomain) {
|
|
5066
|
+
if (categories.has("mailfrom_mx")) recordsCreated += 1;
|
|
5067
|
+
if (categories.has("mailfrom_spf")) recordsCreated += 1;
|
|
5068
|
+
}
|
|
5069
|
+
return {
|
|
5070
|
+
success: true,
|
|
5071
|
+
recordsCreated
|
|
5072
|
+
};
|
|
5073
|
+
} catch (error) {
|
|
5074
|
+
return {
|
|
5075
|
+
success: false,
|
|
5076
|
+
recordsCreated: 0,
|
|
5077
|
+
errors: [error instanceof Error ? error.message : "Unknown error"]
|
|
5078
|
+
};
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
case "vercel": {
|
|
5082
|
+
const client = new VercelDNSClient(
|
|
5083
|
+
data.domain,
|
|
5084
|
+
credentials.token,
|
|
5085
|
+
credentials.teamId
|
|
5086
|
+
);
|
|
5087
|
+
return client.createEmailRecords(data);
|
|
5088
|
+
}
|
|
5089
|
+
case "cloudflare": {
|
|
5090
|
+
const client = new CloudflareDNSClient(
|
|
5091
|
+
credentials.zoneId,
|
|
5092
|
+
credentials.token
|
|
5093
|
+
);
|
|
5094
|
+
return client.createEmailRecords(data);
|
|
5095
|
+
}
|
|
5096
|
+
case "manual": {
|
|
5097
|
+
return {
|
|
5098
|
+
success: true,
|
|
5099
|
+
recordsCreated: 0
|
|
5100
|
+
};
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
}
|
|
5104
|
+
function getDNSProviderDisplayName(provider) {
|
|
5105
|
+
switch (provider) {
|
|
5106
|
+
case "route53":
|
|
5107
|
+
return "AWS Route53";
|
|
5108
|
+
case "vercel":
|
|
5109
|
+
return "Vercel DNS";
|
|
5110
|
+
case "cloudflare":
|
|
5111
|
+
return "Cloudflare";
|
|
5112
|
+
case "manual":
|
|
5113
|
+
return "Manual";
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5116
|
+
function getDNSProviderTokenUrl(provider) {
|
|
5117
|
+
switch (provider) {
|
|
5118
|
+
case "vercel":
|
|
5119
|
+
return "https://vercel.com/account/tokens";
|
|
5120
|
+
case "cloudflare":
|
|
5121
|
+
return "https://dash.cloudflare.com/profile/api-tokens";
|
|
5122
|
+
}
|
|
5123
|
+
}
|
|
5124
|
+
var init_create_records = __esm({
|
|
5125
|
+
"src/utils/dns/create-records.ts"() {
|
|
5126
|
+
"use strict";
|
|
5127
|
+
init_esm_shims();
|
|
5128
|
+
init_route53();
|
|
5129
|
+
init_cloudflare();
|
|
5130
|
+
init_vercel();
|
|
5131
|
+
}
|
|
5132
|
+
});
|
|
5133
|
+
|
|
5134
|
+
// src/utils/dns/credentials.ts
|
|
5135
|
+
function getDNSProviderEnvVars(provider) {
|
|
5136
|
+
switch (provider) {
|
|
5137
|
+
case "route53":
|
|
5138
|
+
return [];
|
|
5139
|
+
// Uses AWS credentials from environment/config
|
|
5140
|
+
case "vercel":
|
|
5141
|
+
return ["VERCEL_TOKEN"];
|
|
5142
|
+
case "cloudflare":
|
|
5143
|
+
return ["CLOUDFLARE_API_TOKEN"];
|
|
5144
|
+
case "manual":
|
|
5145
|
+
return [];
|
|
5146
|
+
}
|
|
5147
|
+
}
|
|
5148
|
+
function getDNSProviderOptionalEnvVars(provider) {
|
|
5149
|
+
switch (provider) {
|
|
5150
|
+
case "vercel":
|
|
5151
|
+
return ["VERCEL_TEAM_ID"];
|
|
5152
|
+
case "cloudflare":
|
|
5153
|
+
return ["CLOUDFLARE_ZONE_ID"];
|
|
5154
|
+
default:
|
|
5155
|
+
return [];
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
5158
|
+
function hasVercelToken() {
|
|
5159
|
+
return !!process.env.VERCEL_TOKEN;
|
|
5160
|
+
}
|
|
5161
|
+
function hasCloudflareToken() {
|
|
5162
|
+
return !!process.env.CLOUDFLARE_API_TOKEN;
|
|
5163
|
+
}
|
|
5164
|
+
async function validateVercelCredentials(token) {
|
|
5165
|
+
try {
|
|
5166
|
+
const response = await fetch("https://api.vercel.com/v2/user", {
|
|
5167
|
+
headers: {
|
|
5168
|
+
Authorization: `Bearer ${token}`
|
|
5169
|
+
}
|
|
5170
|
+
});
|
|
5171
|
+
return response.ok;
|
|
5172
|
+
} catch {
|
|
5173
|
+
return false;
|
|
5174
|
+
}
|
|
5175
|
+
}
|
|
5176
|
+
async function checkVercelDomain(token, domain, teamId) {
|
|
5177
|
+
try {
|
|
5178
|
+
const teamParam = teamId ? `&teamId=${teamId}` : "";
|
|
5179
|
+
const response = await fetch(
|
|
5180
|
+
`https://api.vercel.com/v5/domains/${domain}?${teamParam}`,
|
|
5181
|
+
{
|
|
5182
|
+
headers: {
|
|
5183
|
+
Authorization: `Bearer ${token}`
|
|
5184
|
+
}
|
|
5185
|
+
}
|
|
5186
|
+
);
|
|
5187
|
+
return response.ok;
|
|
5188
|
+
} catch {
|
|
5189
|
+
return false;
|
|
5190
|
+
}
|
|
5191
|
+
}
|
|
5192
|
+
async function validateCloudflareCredentials(token) {
|
|
5193
|
+
try {
|
|
5194
|
+
const response = await fetch(
|
|
5195
|
+
"https://api.cloudflare.com/client/v4/user/tokens/verify",
|
|
5196
|
+
{
|
|
5197
|
+
headers: {
|
|
5198
|
+
Authorization: `Bearer ${token}`
|
|
5199
|
+
}
|
|
5200
|
+
}
|
|
5201
|
+
);
|
|
5202
|
+
const data = await response.json();
|
|
5203
|
+
return data.success === true;
|
|
5204
|
+
} catch {
|
|
5205
|
+
return false;
|
|
5206
|
+
}
|
|
5207
|
+
}
|
|
5208
|
+
async function findCloudflareZoneId(token, domain) {
|
|
5209
|
+
try {
|
|
5210
|
+
const response = await fetch(
|
|
5211
|
+
`https://api.cloudflare.com/client/v4/zones?name=${encodeURIComponent(domain)}`,
|
|
5212
|
+
{
|
|
5213
|
+
headers: {
|
|
5214
|
+
Authorization: `Bearer ${token}`,
|
|
5215
|
+
"Content-Type": "application/json"
|
|
5216
|
+
}
|
|
5217
|
+
}
|
|
5218
|
+
);
|
|
5219
|
+
const data = await response.json();
|
|
5220
|
+
if (data.success && data.result.length > 0) {
|
|
5221
|
+
return data.result[0].id;
|
|
5222
|
+
}
|
|
5223
|
+
const parts = domain.split(".");
|
|
5224
|
+
if (parts.length > 2) {
|
|
5225
|
+
const parentDomain = parts.slice(-2).join(".");
|
|
5226
|
+
const parentResponse = await fetch(
|
|
5227
|
+
`https://api.cloudflare.com/client/v4/zones?name=${encodeURIComponent(parentDomain)}`,
|
|
5228
|
+
{
|
|
5229
|
+
headers: {
|
|
5230
|
+
Authorization: `Bearer ${token}`,
|
|
5231
|
+
"Content-Type": "application/json"
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
);
|
|
5235
|
+
const parentData = await parentResponse.json();
|
|
5236
|
+
if (parentData.success && parentData.result.length > 0) {
|
|
5237
|
+
return parentData.result[0].id;
|
|
5238
|
+
}
|
|
5239
|
+
}
|
|
5240
|
+
return null;
|
|
5241
|
+
} catch {
|
|
5242
|
+
return null;
|
|
5243
|
+
}
|
|
5244
|
+
}
|
|
5245
|
+
async function getDNSCredentials(provider, domain, region) {
|
|
5246
|
+
switch (provider) {
|
|
5247
|
+
case "route53": {
|
|
5248
|
+
const hostedZone = await findHostedZone(domain, region);
|
|
5249
|
+
if (hostedZone) {
|
|
5250
|
+
return {
|
|
5251
|
+
valid: true,
|
|
5252
|
+
credentials: { provider: "route53", hostedZoneId: hostedZone.id }
|
|
5253
|
+
};
|
|
5254
|
+
}
|
|
5255
|
+
return {
|
|
5256
|
+
valid: false,
|
|
5257
|
+
error: `No Route53 hosted zone found for ${domain}`
|
|
5258
|
+
};
|
|
5259
|
+
}
|
|
5260
|
+
case "vercel": {
|
|
5261
|
+
const token = process.env.VERCEL_TOKEN;
|
|
5262
|
+
if (!token) {
|
|
5263
|
+
return {
|
|
5264
|
+
valid: false,
|
|
5265
|
+
error: "VERCEL_TOKEN environment variable is not set"
|
|
5266
|
+
};
|
|
5267
|
+
}
|
|
5268
|
+
const isValid = await validateVercelCredentials(token);
|
|
5269
|
+
if (!isValid) {
|
|
5270
|
+
return {
|
|
5271
|
+
valid: false,
|
|
5272
|
+
error: "Invalid VERCEL_TOKEN - authentication failed"
|
|
5273
|
+
};
|
|
5274
|
+
}
|
|
5275
|
+
const teamId = process.env.VERCEL_TEAM_ID;
|
|
5276
|
+
const hasDomain = await checkVercelDomain(token, domain, teamId);
|
|
5277
|
+
if (!hasDomain) {
|
|
5278
|
+
return {
|
|
5279
|
+
valid: false,
|
|
5280
|
+
error: `Domain ${domain} not found in Vercel DNS`
|
|
5281
|
+
};
|
|
5282
|
+
}
|
|
5283
|
+
return {
|
|
5284
|
+
valid: true,
|
|
5285
|
+
credentials: { provider: "vercel", token, teamId }
|
|
5286
|
+
};
|
|
5287
|
+
}
|
|
5288
|
+
case "cloudflare": {
|
|
5289
|
+
const token = process.env.CLOUDFLARE_API_TOKEN;
|
|
5290
|
+
if (!token) {
|
|
5291
|
+
return {
|
|
5292
|
+
valid: false,
|
|
5293
|
+
error: "CLOUDFLARE_API_TOKEN environment variable is not set"
|
|
5294
|
+
};
|
|
5295
|
+
}
|
|
5296
|
+
const isValid = await validateCloudflareCredentials(token);
|
|
5297
|
+
if (!isValid) {
|
|
5298
|
+
return {
|
|
5299
|
+
valid: false,
|
|
5300
|
+
error: "Invalid CLOUDFLARE_API_TOKEN - authentication failed"
|
|
5301
|
+
};
|
|
5302
|
+
}
|
|
5303
|
+
let zoneId = process.env.CLOUDFLARE_ZONE_ID;
|
|
5304
|
+
if (!zoneId) {
|
|
5305
|
+
const detectedZoneId = await findCloudflareZoneId(token, domain);
|
|
5306
|
+
zoneId = detectedZoneId ?? void 0;
|
|
5307
|
+
}
|
|
5308
|
+
if (!zoneId) {
|
|
5309
|
+
return {
|
|
5310
|
+
valid: false,
|
|
5311
|
+
error: `Could not find Cloudflare zone for ${domain}. Set CLOUDFLARE_ZONE_ID if the domain uses a different zone.`
|
|
5312
|
+
};
|
|
5313
|
+
}
|
|
5314
|
+
return {
|
|
5315
|
+
valid: true,
|
|
5316
|
+
credentials: { provider: "cloudflare", token, zoneId }
|
|
5317
|
+
};
|
|
5318
|
+
}
|
|
5319
|
+
case "manual":
|
|
5320
|
+
return {
|
|
5321
|
+
valid: true,
|
|
5322
|
+
credentials: { provider: "manual" }
|
|
5323
|
+
};
|
|
5324
|
+
}
|
|
5325
|
+
}
|
|
5326
|
+
async function detectAvailableDNSProviders(domain, region) {
|
|
5327
|
+
const providers = [];
|
|
5328
|
+
const hostedZone = await findHostedZone(domain, region);
|
|
5329
|
+
providers.push({
|
|
5330
|
+
provider: "route53",
|
|
5331
|
+
detected: !!hostedZone,
|
|
5332
|
+
hint: hostedZone ? "Hosted zone detected" : void 0
|
|
5333
|
+
});
|
|
5334
|
+
const vercelToken = process.env.VERCEL_TOKEN;
|
|
5335
|
+
if (vercelToken) {
|
|
5336
|
+
const teamId = process.env.VERCEL_TEAM_ID;
|
|
5337
|
+
const hasDomain = await checkVercelDomain(vercelToken, domain, teamId);
|
|
5338
|
+
providers.push({
|
|
5339
|
+
provider: "vercel",
|
|
5340
|
+
detected: hasDomain,
|
|
5341
|
+
hint: hasDomain ? "Domain found in Vercel DNS" : "Token found"
|
|
5342
|
+
});
|
|
5343
|
+
} else {
|
|
5344
|
+
providers.push({
|
|
5345
|
+
provider: "vercel",
|
|
5346
|
+
detected: false
|
|
5347
|
+
});
|
|
5348
|
+
}
|
|
5349
|
+
const cfToken = process.env.CLOUDFLARE_API_TOKEN;
|
|
5350
|
+
if (cfToken) {
|
|
5351
|
+
const zoneId = process.env.CLOUDFLARE_ZONE_ID || await findCloudflareZoneId(cfToken, domain);
|
|
5352
|
+
providers.push({
|
|
5353
|
+
provider: "cloudflare",
|
|
5354
|
+
detected: !!zoneId,
|
|
5355
|
+
hint: zoneId ? "Zone found" : "Token found"
|
|
5356
|
+
});
|
|
5357
|
+
} else {
|
|
5358
|
+
providers.push({
|
|
5359
|
+
provider: "cloudflare",
|
|
5360
|
+
detected: false
|
|
5361
|
+
});
|
|
5362
|
+
}
|
|
5363
|
+
providers.push({
|
|
5364
|
+
provider: "manual",
|
|
5365
|
+
detected: true,
|
|
5366
|
+
hint: "I'll add DNS records myself"
|
|
5367
|
+
});
|
|
5368
|
+
return providers.sort((a, b) => {
|
|
5369
|
+
if (a.provider === "manual") return 1;
|
|
5370
|
+
if (b.provider === "manual") return -1;
|
|
5371
|
+
if (a.detected && !b.detected) return -1;
|
|
5372
|
+
if (!a.detected && b.detected) return 1;
|
|
5373
|
+
return 0;
|
|
5374
|
+
});
|
|
5375
|
+
}
|
|
5376
|
+
var init_credentials = __esm({
|
|
5377
|
+
"src/utils/dns/credentials.ts"() {
|
|
5378
|
+
"use strict";
|
|
5379
|
+
init_esm_shims();
|
|
5380
|
+
init_route53();
|
|
5381
|
+
}
|
|
5382
|
+
});
|
|
5383
|
+
|
|
5384
|
+
// src/utils/dns/index.ts
|
|
5385
|
+
var dns_exports = {};
|
|
5386
|
+
__export(dns_exports, {
|
|
5387
|
+
CloudflareDNSClient: () => CloudflareDNSClient,
|
|
5388
|
+
VercelDNSClient: () => VercelDNSClient,
|
|
5389
|
+
buildEmailDNSRecords: () => buildEmailDNSRecords,
|
|
5390
|
+
createDNSRecordsForProvider: () => createDNSRecordsForProvider,
|
|
5391
|
+
detectAvailableDNSProviders: () => detectAvailableDNSProviders,
|
|
5392
|
+
findCloudflareZoneId: () => findCloudflareZoneId,
|
|
5393
|
+
formatDNSRecordsForDisplay: () => formatDNSRecordsForDisplay,
|
|
5394
|
+
getDNSCredentials: () => getDNSCredentials,
|
|
5395
|
+
getDNSProviderDisplayName: () => getDNSProviderDisplayName,
|
|
5396
|
+
getDNSProviderEnvVars: () => getDNSProviderEnvVars,
|
|
5397
|
+
getDNSProviderOptionalEnvVars: () => getDNSProviderOptionalEnvVars,
|
|
5398
|
+
getDNSProviderTokenUrl: () => getDNSProviderTokenUrl,
|
|
5399
|
+
hasCloudflareToken: () => hasCloudflareToken,
|
|
5400
|
+
hasVercelToken: () => hasVercelToken
|
|
5401
|
+
});
|
|
5402
|
+
var init_dns = __esm({
|
|
5403
|
+
"src/utils/dns/index.ts"() {
|
|
5404
|
+
"use strict";
|
|
5405
|
+
init_esm_shims();
|
|
5406
|
+
init_cloudflare();
|
|
5407
|
+
init_create_records();
|
|
5408
|
+
init_credentials();
|
|
5409
|
+
init_vercel();
|
|
5410
|
+
}
|
|
5411
|
+
});
|
|
5412
|
+
|
|
5413
|
+
// src/utils/shared/assume-role.ts
|
|
5414
|
+
var assume_role_exports = {};
|
|
5415
|
+
__export(assume_role_exports, {
|
|
5416
|
+
assumeRole: () => assumeRole
|
|
5417
|
+
});
|
|
5418
|
+
import { AssumeRoleCommand, STSClient as STSClient3 } from "@aws-sdk/client-sts";
|
|
5419
|
+
async function assumeRole(roleArn, region, sessionName = "wraps-console") {
|
|
5420
|
+
const sts = new STSClient3({ region });
|
|
5421
|
+
const response = await sts.send(
|
|
5422
|
+
new AssumeRoleCommand({
|
|
5423
|
+
RoleArn: roleArn,
|
|
5424
|
+
RoleSessionName: sessionName,
|
|
5425
|
+
DurationSeconds: 3600
|
|
5426
|
+
// 1 hour
|
|
5427
|
+
})
|
|
5428
|
+
);
|
|
5429
|
+
if (!response.Credentials) {
|
|
5430
|
+
throw new Error("Failed to assume role: No credentials returned");
|
|
5431
|
+
}
|
|
5432
|
+
return {
|
|
5433
|
+
accessKeyId: response.Credentials.AccessKeyId,
|
|
5434
|
+
secretAccessKey: response.Credentials.SecretAccessKey,
|
|
5435
|
+
sessionToken: response.Credentials.SessionToken,
|
|
5436
|
+
expiration: response.Credentials.Expiration
|
|
5437
|
+
};
|
|
5438
|
+
}
|
|
5439
|
+
var init_assume_role = __esm({
|
|
5440
|
+
"src/utils/shared/assume-role.ts"() {
|
|
5441
|
+
"use strict";
|
|
5442
|
+
init_esm_shims();
|
|
5443
|
+
}
|
|
5444
|
+
});
|
|
5445
|
+
|
|
5446
|
+
// src/utils/archive.ts
|
|
5447
|
+
import {
|
|
5448
|
+
GetArchiveMessageCommand,
|
|
5449
|
+
GetArchiveSearchResultsCommand,
|
|
5450
|
+
MailManagerClient as MailManagerClient2,
|
|
5451
|
+
StartArchiveSearchCommand
|
|
5452
|
+
} from "@aws-sdk/client-mailmanager";
|
|
5453
|
+
import DOMPurify from "isomorphic-dompurify";
|
|
5454
|
+
import { simpleParser } from "mailparser";
|
|
5455
|
+
function extractArchiveId(archiveArnOrId) {
|
|
5456
|
+
if (archiveArnOrId.startsWith("arn:")) {
|
|
5457
|
+
const parts = archiveArnOrId.split("/");
|
|
5458
|
+
return parts.at(-1);
|
|
5459
|
+
}
|
|
5460
|
+
return archiveArnOrId;
|
|
5461
|
+
}
|
|
5462
|
+
async function getArchivedEmail(archiveArnOrId, searchCriteria, region) {
|
|
5463
|
+
const client = new MailManagerClient2({ region });
|
|
5464
|
+
const archiveId = extractArchiveId(archiveArnOrId);
|
|
5465
|
+
const searchTime = searchCriteria.timestamp || /* @__PURE__ */ new Date();
|
|
5466
|
+
const dayBefore = new Date(searchTime.getTime() - 24 * 60 * 60 * 1e3);
|
|
5467
|
+
const dayAfter = new Date(searchTime.getTime() + 24 * 60 * 60 * 1e3);
|
|
5468
|
+
const filters = [];
|
|
5469
|
+
if (searchCriteria.from) {
|
|
5470
|
+
filters.push({
|
|
5471
|
+
StringExpression: {
|
|
5472
|
+
Evaluate: {
|
|
5473
|
+
Attribute: "FROM"
|
|
5474
|
+
},
|
|
5475
|
+
Operator: "CONTAINS",
|
|
5476
|
+
Values: [searchCriteria.from]
|
|
5477
|
+
}
|
|
5478
|
+
});
|
|
5479
|
+
}
|
|
5480
|
+
if (searchCriteria.to) {
|
|
5481
|
+
filters.push({
|
|
5482
|
+
StringExpression: {
|
|
5483
|
+
Evaluate: {
|
|
5484
|
+
Attribute: "TO"
|
|
5485
|
+
},
|
|
5486
|
+
Operator: "CONTAINS",
|
|
5487
|
+
Values: [searchCriteria.to]
|
|
5488
|
+
}
|
|
5489
|
+
});
|
|
5490
|
+
}
|
|
5491
|
+
if (searchCriteria.subject) {
|
|
5492
|
+
filters.push({
|
|
5493
|
+
StringExpression: {
|
|
5494
|
+
Evaluate: {
|
|
5495
|
+
Attribute: "SUBJECT"
|
|
5496
|
+
},
|
|
5497
|
+
Operator: "CONTAINS",
|
|
5498
|
+
Values: [searchCriteria.subject]
|
|
5499
|
+
}
|
|
5500
|
+
});
|
|
5501
|
+
}
|
|
5502
|
+
if (filters.length === 0) {
|
|
5503
|
+
throw new Error(
|
|
5504
|
+
"At least one search criterion (from, to, or subject) is required"
|
|
5505
|
+
);
|
|
5506
|
+
}
|
|
5507
|
+
const searchCommand = new StartArchiveSearchCommand({
|
|
5508
|
+
ArchiveId: archiveId,
|
|
5509
|
+
FromTimestamp: dayBefore,
|
|
5510
|
+
ToTimestamp: dayAfter,
|
|
5511
|
+
Filters: {
|
|
5512
|
+
Include: filters
|
|
5513
|
+
},
|
|
5514
|
+
MaxResults: 10
|
|
5515
|
+
// Get a few results in case there are multiple matches
|
|
5516
|
+
});
|
|
5517
|
+
const searchResponse = await client.send(searchCommand);
|
|
5518
|
+
const searchId = searchResponse.SearchId;
|
|
5519
|
+
if (!searchId) {
|
|
5520
|
+
throw new Error("Failed to start archive search");
|
|
5521
|
+
}
|
|
5522
|
+
let archivedMessageId;
|
|
5523
|
+
let attempts = 0;
|
|
5524
|
+
const maxAttempts = 20;
|
|
5525
|
+
const pollInterval = 1e3;
|
|
5526
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
5527
|
+
while (attempts < maxAttempts) {
|
|
5528
|
+
try {
|
|
5529
|
+
const resultsCommand = new GetArchiveSearchResultsCommand({
|
|
5530
|
+
SearchId: searchId
|
|
5531
|
+
});
|
|
5532
|
+
const resultsResponse = await client.send(resultsCommand);
|
|
5533
|
+
if (resultsResponse.Rows && resultsResponse.Rows.length > 0) {
|
|
5534
|
+
archivedMessageId = resultsResponse.Rows[0].ArchivedMessageId;
|
|
5535
|
+
break;
|
|
5536
|
+
}
|
|
5537
|
+
if (resultsResponse.Rows && resultsResponse.Rows.length === 0) {
|
|
5538
|
+
break;
|
|
5539
|
+
}
|
|
5540
|
+
} catch (error) {
|
|
5541
|
+
if (error instanceof Error && error.name === "ConflictException" && error.message.includes("still in progress")) {
|
|
5542
|
+
console.log(`Search still in progress, attempt ${attempts + 1}...`);
|
|
5543
|
+
} else {
|
|
5544
|
+
throw error;
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5547
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
5548
|
+
attempts++;
|
|
5549
|
+
}
|
|
5550
|
+
if (!archivedMessageId) {
|
|
5551
|
+
throw new Error(
|
|
5552
|
+
"Email not found in archive with the provided search criteria. It may have been sent before archiving was enabled."
|
|
5553
|
+
);
|
|
5554
|
+
}
|
|
5555
|
+
const command = new GetArchiveMessageCommand({
|
|
5556
|
+
ArchivedMessageId: archivedMessageId
|
|
5557
|
+
});
|
|
5558
|
+
const response = await client.send(command);
|
|
5559
|
+
if (!response.MessageDownloadLink) {
|
|
5560
|
+
throw new Error("No download link available for archived message");
|
|
5561
|
+
}
|
|
5562
|
+
const emailResponse = await fetch(response.MessageDownloadLink);
|
|
5563
|
+
if (!emailResponse.ok) {
|
|
5564
|
+
throw new Error(`Failed to download email: ${emailResponse.statusText}`);
|
|
5565
|
+
}
|
|
5566
|
+
const emailRaw = await emailResponse.text();
|
|
5567
|
+
const parsed = await simpleParser(emailRaw);
|
|
5568
|
+
const attachments = parsed.attachments?.map((att) => ({
|
|
5569
|
+
filename: att.filename,
|
|
5570
|
+
contentType: att.contentType,
|
|
5571
|
+
size: att.size
|
|
5572
|
+
})) || [];
|
|
5573
|
+
const headers = {};
|
|
5574
|
+
if (parsed.headers) {
|
|
5575
|
+
for (const [key, value] of parsed.headers) {
|
|
5576
|
+
if (value instanceof Date) {
|
|
5577
|
+
headers[key] = value.toISOString();
|
|
5578
|
+
} else if (typeof value === "string") {
|
|
5579
|
+
headers[key] = value;
|
|
5580
|
+
} else if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
|
|
5581
|
+
headers[key] = value;
|
|
5582
|
+
} else {
|
|
5583
|
+
headers[key] = JSON.stringify(value);
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5586
|
+
}
|
|
5587
|
+
const getAddressText = (addr) => {
|
|
5588
|
+
if (!addr) {
|
|
5589
|
+
return "";
|
|
5590
|
+
}
|
|
5591
|
+
if (Array.isArray(addr)) {
|
|
4615
5592
|
return addr.map((a) => a.text).join(", ");
|
|
4616
5593
|
}
|
|
4617
5594
|
return addr.text || "";
|
|
@@ -4758,7 +5735,7 @@ import { dirname as dirname2, join as join5 } from "path";
|
|
|
4758
5735
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4759
5736
|
import * as clack37 from "@clack/prompts";
|
|
4760
5737
|
import args from "args";
|
|
4761
|
-
import
|
|
5738
|
+
import pc40 from "picocolors";
|
|
4762
5739
|
|
|
4763
5740
|
// src/commands/aws/doctor.ts
|
|
4764
5741
|
init_esm_shims();
|
|
@@ -6885,8 +7862,8 @@ init_esm_shims();
|
|
|
6885
7862
|
import * as aws2 from "@pulumi/aws";
|
|
6886
7863
|
async function getExistingOIDCProviderArn(url) {
|
|
6887
7864
|
try {
|
|
6888
|
-
const { IAMClient:
|
|
6889
|
-
const iam8 = new
|
|
7865
|
+
const { IAMClient: IAMClient4, ListOpenIDConnectProvidersCommand } = await import("@aws-sdk/client-iam");
|
|
7866
|
+
const iam8 = new IAMClient4({
|
|
6890
7867
|
region: process.env.AWS_REGION || "us-east-1"
|
|
6891
7868
|
});
|
|
6892
7869
|
const response = await iam8.send(new ListOpenIDConnectProvidersCommand({}));
|
|
@@ -6943,11 +7920,11 @@ async function createVercelOIDC(config2) {
|
|
|
6943
7920
|
// src/infrastructure/cdn-stack.ts
|
|
6944
7921
|
async function roleExists(roleName) {
|
|
6945
7922
|
try {
|
|
6946
|
-
const { IAMClient:
|
|
6947
|
-
const iam8 = new
|
|
7923
|
+
const { IAMClient: IAMClient4, GetRoleCommand: GetRoleCommand3 } = await import("@aws-sdk/client-iam");
|
|
7924
|
+
const iam8 = new IAMClient4({
|
|
6948
7925
|
region: process.env.AWS_REGION || "us-east-1"
|
|
6949
7926
|
});
|
|
6950
|
-
await iam8.send(new
|
|
7927
|
+
await iam8.send(new GetRoleCommand3({ RoleName: roleName }));
|
|
6951
7928
|
return true;
|
|
6952
7929
|
} catch (error) {
|
|
6953
7930
|
if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity") {
|
|
@@ -12300,11 +13277,11 @@ import * as aws7 from "@pulumi/aws";
|
|
|
12300
13277
|
import * as pulumi10 from "@pulumi/pulumi";
|
|
12301
13278
|
async function roleExists2(roleName) {
|
|
12302
13279
|
try {
|
|
12303
|
-
const { IAMClient:
|
|
12304
|
-
const iam8 = new
|
|
13280
|
+
const { IAMClient: IAMClient4, GetRoleCommand: GetRoleCommand3 } = await import("@aws-sdk/client-iam");
|
|
13281
|
+
const iam8 = new IAMClient4({
|
|
12305
13282
|
region: process.env.AWS_REGION || "us-east-1"
|
|
12306
13283
|
});
|
|
12307
|
-
await iam8.send(new
|
|
13284
|
+
await iam8.send(new GetRoleCommand3({ RoleName: roleName }));
|
|
12308
13285
|
return true;
|
|
12309
13286
|
} catch (error) {
|
|
12310
13287
|
if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity") {
|
|
@@ -12689,8 +13666,8 @@ function convertToSMTPPassword2(secretAccessKey, region) {
|
|
|
12689
13666
|
}
|
|
12690
13667
|
async function userExists(userName) {
|
|
12691
13668
|
try {
|
|
12692
|
-
const { IAMClient:
|
|
12693
|
-
const iam8 = new
|
|
13669
|
+
const { IAMClient: IAMClient4, GetUserCommand } = await import("@aws-sdk/client-iam");
|
|
13670
|
+
const iam8 = new IAMClient4({
|
|
12694
13671
|
region: process.env.AWS_REGION || "us-east-1"
|
|
12695
13672
|
});
|
|
12696
13673
|
await iam8.send(new GetUserCommand({ UserName: userName }));
|
|
@@ -14585,8 +15562,8 @@ function getRequiredActions(config2) {
|
|
|
14585
15562
|
}
|
|
14586
15563
|
async function checkIAMPermissions(userArn, actions, region) {
|
|
14587
15564
|
try {
|
|
14588
|
-
const { IAMClient:
|
|
14589
|
-
const client = new
|
|
15565
|
+
const { IAMClient: IAMClient4, SimulatePrincipalPolicyCommand } = await import("@aws-sdk/client-iam");
|
|
15566
|
+
const client = new IAMClient4({ region });
|
|
14590
15567
|
const batchSize = 100;
|
|
14591
15568
|
const batches = [];
|
|
14592
15569
|
for (let i = 0; i < actions.length; i += batchSize) {
|
|
@@ -14978,51 +15955,149 @@ ${pc18.yellow(pc18.bold("Configuration Warnings:"))}`);
|
|
|
14978
15955
|
await saveConnectionMetadata(metadata);
|
|
14979
15956
|
progress.info("Connection metadata saved for upgrade and restore capability");
|
|
14980
15957
|
let dnsAutoCreated = false;
|
|
15958
|
+
let dnsProvider;
|
|
14981
15959
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
14982
|
-
const {
|
|
14983
|
-
|
|
14984
|
-
|
|
14985
|
-
|
|
14986
|
-
|
|
14987
|
-
|
|
14988
|
-
|
|
14989
|
-
|
|
14990
|
-
|
|
14991
|
-
|
|
14992
|
-
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
|
|
14996
|
-
|
|
14997
|
-
|
|
14998
|
-
|
|
14999
|
-
|
|
15000
|
-
|
|
15001
|
-
|
|
15002
|
-
|
|
15003
|
-
|
|
15960
|
+
const {
|
|
15961
|
+
detectAvailableDNSProviders: detectAvailableDNSProviders2,
|
|
15962
|
+
getDNSCredentials: getDNSCredentials2,
|
|
15963
|
+
createDNSRecordsForProvider: createDNSRecordsForProvider2,
|
|
15964
|
+
getDNSProviderDisplayName: getDNSProviderDisplayName2,
|
|
15965
|
+
getDNSProviderTokenUrl: getDNSProviderTokenUrl2,
|
|
15966
|
+
buildEmailDNSRecords: buildEmailDNSRecords2
|
|
15967
|
+
} = await Promise.resolve().then(() => (init_dns(), dns_exports));
|
|
15968
|
+
const {
|
|
15969
|
+
promptDNSProvider: promptDNSProvider2,
|
|
15970
|
+
promptDNSConfirmation: promptDNSConfirmation2,
|
|
15971
|
+
promptContinueManualDNS: promptContinueManualDNS2
|
|
15972
|
+
} = await Promise.resolve().then(() => (init_prompts(), prompts_exports));
|
|
15973
|
+
const { previewDNSChanges: previewDNSChanges2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
15974
|
+
progress.start("Detecting DNS providers");
|
|
15975
|
+
const availableProviders = await detectAvailableDNSProviders2(
|
|
15976
|
+
outputs.domain,
|
|
15977
|
+
region
|
|
15978
|
+
);
|
|
15979
|
+
progress.stop();
|
|
15980
|
+
const selectedProvider = await promptDNSProvider2(
|
|
15981
|
+
outputs.domain,
|
|
15982
|
+
availableProviders
|
|
15983
|
+
);
|
|
15984
|
+
dnsProvider = selectedProvider;
|
|
15985
|
+
if (selectedProvider !== "manual") {
|
|
15986
|
+
progress.start(
|
|
15987
|
+
`Validating ${getDNSProviderDisplayName2(selectedProvider)} credentials`
|
|
15988
|
+
);
|
|
15989
|
+
const credentialResult2 = await getDNSCredentials2(
|
|
15990
|
+
selectedProvider,
|
|
15991
|
+
outputs.domain,
|
|
15992
|
+
region
|
|
15993
|
+
);
|
|
15994
|
+
progress.stop();
|
|
15995
|
+
if (credentialResult2.valid && credentialResult2.credentials) {
|
|
15996
|
+
const credentials = credentialResult2.credentials;
|
|
15997
|
+
if (credentials.provider === "route53") {
|
|
15998
|
+
try {
|
|
15999
|
+
progress.start("Checking existing DNS records");
|
|
16000
|
+
const dnsPreview = await previewDNSChanges2(
|
|
16001
|
+
credentials.hostedZoneId,
|
|
15004
16002
|
outputs.domain,
|
|
15005
16003
|
outputs.dkimTokens,
|
|
15006
16004
|
region,
|
|
15007
|
-
selectedCategories,
|
|
15008
16005
|
outputs.customTrackingDomain,
|
|
15009
16006
|
outputs.mailFromDomain
|
|
15010
16007
|
);
|
|
16008
|
+
progress.stop();
|
|
16009
|
+
const { shouldCreate, selectedCategories } = await promptDNSConfirmation2(dnsPreview);
|
|
16010
|
+
if (shouldCreate && selectedCategories.size > 0) {
|
|
16011
|
+
progress.start("Creating selected DNS records in Route53");
|
|
16012
|
+
const result = await createDNSRecordsForProvider2(
|
|
16013
|
+
credentials,
|
|
16014
|
+
{
|
|
16015
|
+
domain: outputs.domain,
|
|
16016
|
+
dkimTokens: outputs.dkimTokens,
|
|
16017
|
+
mailFromDomain: outputs.mailFromDomain,
|
|
16018
|
+
region
|
|
16019
|
+
},
|
|
16020
|
+
selectedCategories
|
|
16021
|
+
);
|
|
16022
|
+
if (result.success) {
|
|
16023
|
+
progress.succeed(
|
|
16024
|
+
`Created ${selectedCategories.size} DNS record group(s) in Route53`
|
|
16025
|
+
);
|
|
16026
|
+
dnsAutoCreated = true;
|
|
16027
|
+
} else {
|
|
16028
|
+
progress.fail("Failed to create some DNS records");
|
|
16029
|
+
if (result.errors) {
|
|
16030
|
+
for (const error of result.errors) {
|
|
16031
|
+
clack17.log.warn(error);
|
|
16032
|
+
}
|
|
16033
|
+
}
|
|
16034
|
+
}
|
|
16035
|
+
} else {
|
|
16036
|
+
clack17.log.info(
|
|
16037
|
+
"Skipping DNS record creation. You can add them manually."
|
|
16038
|
+
);
|
|
16039
|
+
}
|
|
16040
|
+
} catch (error) {
|
|
16041
|
+
progress.stop();
|
|
16042
|
+
clack17.log.warn(`Could not manage DNS records: ${error.message}`);
|
|
16043
|
+
}
|
|
16044
|
+
} else {
|
|
16045
|
+
const recordData = {
|
|
16046
|
+
domain: outputs.domain,
|
|
16047
|
+
dkimTokens: outputs.dkimTokens,
|
|
16048
|
+
mailFromDomain: outputs.mailFromDomain,
|
|
16049
|
+
region
|
|
16050
|
+
};
|
|
16051
|
+
const records = buildEmailDNSRecords2(recordData);
|
|
16052
|
+
clack17.log.info(pc18.bold("DNS records to create:"));
|
|
16053
|
+
for (const record of records) {
|
|
16054
|
+
clack17.log.info(
|
|
16055
|
+
pc18.dim(` ${record.type} ${record.name} \u2192 ${record.value}`)
|
|
16056
|
+
);
|
|
16057
|
+
}
|
|
16058
|
+
progress.start(
|
|
16059
|
+
`Creating DNS records in ${getDNSProviderDisplayName2(credentials.provider)}`
|
|
16060
|
+
);
|
|
16061
|
+
const result = await createDNSRecordsForProvider2(
|
|
16062
|
+
credentials,
|
|
16063
|
+
recordData
|
|
16064
|
+
);
|
|
16065
|
+
if (result.success) {
|
|
15011
16066
|
progress.succeed(
|
|
15012
|
-
`Created ${
|
|
16067
|
+
`Created ${result.recordsCreated} DNS records in ${getDNSProviderDisplayName2(credentials.provider)}`
|
|
15013
16068
|
);
|
|
15014
16069
|
dnsAutoCreated = true;
|
|
15015
16070
|
} else {
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
16071
|
+
progress.fail("Failed to create some DNS records");
|
|
16072
|
+
if (result.errors) {
|
|
16073
|
+
for (const error of result.errors) {
|
|
16074
|
+
clack17.log.warn(error);
|
|
16075
|
+
}
|
|
16076
|
+
}
|
|
15019
16077
|
}
|
|
15020
|
-
}
|
|
15021
|
-
|
|
15022
|
-
|
|
16078
|
+
}
|
|
16079
|
+
} else {
|
|
16080
|
+
clack17.log.warn(
|
|
16081
|
+
credentialResult2.error || "Could not validate credentials"
|
|
16082
|
+
);
|
|
16083
|
+
if (selectedProvider === "vercel" || selectedProvider === "cloudflare") {
|
|
16084
|
+
clack17.log.info(
|
|
16085
|
+
`Set the ${selectedProvider === "vercel" ? "VERCEL_TOKEN" : "CLOUDFLARE_API_TOKEN"} environment variable to enable automatic DNS management.`
|
|
16086
|
+
);
|
|
16087
|
+
clack17.log.info(
|
|
16088
|
+
`You can create a token at: ${pc18.cyan(getDNSProviderTokenUrl2(selectedProvider))}`
|
|
16089
|
+
);
|
|
16090
|
+
}
|
|
16091
|
+
const continueManual = await promptContinueManualDNS2();
|
|
16092
|
+
if (continueManual) {
|
|
16093
|
+
dnsProvider = "manual";
|
|
15023
16094
|
}
|
|
15024
16095
|
}
|
|
15025
16096
|
}
|
|
16097
|
+
if (dnsProvider && metadata.services.email) {
|
|
16098
|
+
metadata.services.email.dnsProvider = dnsProvider;
|
|
16099
|
+
await saveConnectionMetadata(metadata);
|
|
16100
|
+
}
|
|
15026
16101
|
}
|
|
15027
16102
|
const dnsRecords = [];
|
|
15028
16103
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0 && !dnsAutoCreated) {
|
|
@@ -15380,6 +16455,8 @@ import * as clack20 from "@clack/prompts";
|
|
|
15380
16455
|
import * as pulumi18 from "@pulumi/pulumi";
|
|
15381
16456
|
import pc21 from "picocolors";
|
|
15382
16457
|
init_events();
|
|
16458
|
+
init_create_records();
|
|
16459
|
+
init_credentials();
|
|
15383
16460
|
init_costs();
|
|
15384
16461
|
init_presets();
|
|
15385
16462
|
init_aws();
|
|
@@ -15820,35 +16897,54 @@ ${pc21.bold("Current Configuration:")}
|
|
|
15820
16897
|
"This ensures all tracking links use secure HTTPS connections."
|
|
15821
16898
|
)
|
|
15822
16899
|
);
|
|
15823
|
-
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
clack20.log.info(
|
|
15833
|
-
pc21.dim(
|
|
15834
|
-
"DNS records (SSL certificate validation + CloudFront) will be created automatically."
|
|
15835
|
-
)
|
|
15836
|
-
);
|
|
16900
|
+
let trackingDnsProvider = metadata.services.email?.dnsProvider;
|
|
16901
|
+
let canAutomateDNS = false;
|
|
16902
|
+
if (trackingDnsProvider) {
|
|
16903
|
+
canAutomateDNS = trackingDnsProvider !== "manual";
|
|
16904
|
+
if (canAutomateDNS) {
|
|
16905
|
+
progress.info(
|
|
16906
|
+
`Will use ${pc21.cyan(getDNSProviderDisplayName(trackingDnsProvider))} for DNS records ${pc21.green("\u2713")}`
|
|
16907
|
+
);
|
|
16908
|
+
}
|
|
15837
16909
|
} else {
|
|
15838
|
-
|
|
15839
|
-
|
|
15840
|
-
|
|
15841
|
-
|
|
15842
|
-
|
|
15843
|
-
"You'll need to manually create DNS records for SSL certificate validation and CloudFront."
|
|
16910
|
+
const availableProviders = await progress.execute(
|
|
16911
|
+
"Detecting available DNS providers",
|
|
16912
|
+
async () => await detectAvailableDNSProviders(
|
|
16913
|
+
trackingDomain || config2.domain,
|
|
16914
|
+
region
|
|
15844
16915
|
)
|
|
15845
16916
|
);
|
|
15846
|
-
|
|
15847
|
-
|
|
16917
|
+
const detectedProvider = availableProviders.find(
|
|
16918
|
+
(p) => p.detected && p.provider !== "manual"
|
|
15848
16919
|
);
|
|
16920
|
+
if (detectedProvider) {
|
|
16921
|
+
trackingDnsProvider = detectedProvider.provider;
|
|
16922
|
+
canAutomateDNS = true;
|
|
16923
|
+
progress.info(
|
|
16924
|
+
`Found ${pc21.cyan(getDNSProviderDisplayName(detectedProvider.provider))} ${pc21.green("\u2713")}`
|
|
16925
|
+
);
|
|
16926
|
+
clack20.log.info(
|
|
16927
|
+
pc21.dim(
|
|
16928
|
+
"DNS records (SSL certificate validation + CloudFront) will be created automatically."
|
|
16929
|
+
)
|
|
16930
|
+
);
|
|
16931
|
+
} else {
|
|
16932
|
+
canAutomateDNS = false;
|
|
16933
|
+
clack20.log.warn(
|
|
16934
|
+
`No automatic DNS provider detected for ${pc21.cyan(trackingDomain || config2.domain)}`
|
|
16935
|
+
);
|
|
16936
|
+
clack20.log.info(
|
|
16937
|
+
pc21.dim(
|
|
16938
|
+
"You'll need to manually create DNS records for SSL certificate validation and CloudFront."
|
|
16939
|
+
)
|
|
16940
|
+
);
|
|
16941
|
+
clack20.log.info(
|
|
16942
|
+
pc21.dim("DNS record details will be shown after deployment.")
|
|
16943
|
+
);
|
|
16944
|
+
}
|
|
15849
16945
|
}
|
|
15850
16946
|
const confirmHttps = await clack20.confirm({
|
|
15851
|
-
message:
|
|
16947
|
+
message: canAutomateDNS ? "Proceed with automatic HTTPS setup?" : "Proceed with manual HTTPS setup (requires DNS configuration)?",
|
|
15852
16948
|
initialValue: true
|
|
15853
16949
|
});
|
|
15854
16950
|
if (clack20.isCancel(confirmHttps) || !confirmHttps) {
|
|
@@ -16660,31 +17756,103 @@ ${pc21.bold("Cost Impact:")}`);
|
|
|
16660
17756
|
trackError("UPGRADE_FAILED", "email:upgrade", { step: "deploy" });
|
|
16661
17757
|
throw new Error(`Pulumi upgrade failed: ${error.message}`);
|
|
16662
17758
|
}
|
|
17759
|
+
let dnsAutoCreated = false;
|
|
16663
17760
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
16664
|
-
|
|
16665
|
-
|
|
16666
|
-
|
|
16667
|
-
|
|
16668
|
-
|
|
16669
|
-
|
|
16670
|
-
|
|
16671
|
-
|
|
17761
|
+
let dnsProvider = metadata.services.email?.dnsProvider;
|
|
17762
|
+
if (!dnsProvider) {
|
|
17763
|
+
const availableProviders = await progress.execute(
|
|
17764
|
+
"Detecting available DNS providers",
|
|
17765
|
+
async () => await detectAvailableDNSProviders(outputs.domain, region)
|
|
17766
|
+
);
|
|
17767
|
+
const detectedProvider = availableProviders.find(
|
|
17768
|
+
(p) => p.detected && p.provider !== "manual"
|
|
17769
|
+
);
|
|
17770
|
+
if (detectedProvider) {
|
|
17771
|
+
dnsProvider = await promptDNSProvider(
|
|
16672
17772
|
outputs.domain,
|
|
16673
|
-
|
|
16674
|
-
region,
|
|
16675
|
-
outputs.customTrackingDomain,
|
|
16676
|
-
mailFromDomain,
|
|
16677
|
-
outputs.cloudFrontDomain
|
|
16678
|
-
);
|
|
16679
|
-
progress.succeed("DNS records created in Route53");
|
|
16680
|
-
} catch (error) {
|
|
16681
|
-
progress.fail(
|
|
16682
|
-
`Failed to create DNS records automatically: ${error.message}`
|
|
17773
|
+
availableProviders
|
|
16683
17774
|
);
|
|
16684
|
-
|
|
16685
|
-
|
|
17775
|
+
if (dnsProvider && dnsProvider !== "manual" && metadata.services.email) {
|
|
17776
|
+
metadata.services.email.dnsProvider = dnsProvider;
|
|
17777
|
+
}
|
|
17778
|
+
}
|
|
17779
|
+
}
|
|
17780
|
+
if (dnsProvider && dnsProvider !== "manual") {
|
|
17781
|
+
const credResult = await progress.execute(
|
|
17782
|
+
`Validating ${getDNSProviderDisplayName(dnsProvider)} credentials`,
|
|
17783
|
+
async () => await getDNSCredentials(dnsProvider, outputs.domain, region)
|
|
17784
|
+
);
|
|
17785
|
+
if (credResult.valid && credResult.credentials) {
|
|
17786
|
+
const mailFromDomain = updatedConfig.mailFromDomain || `mail.${outputs.domain}`;
|
|
17787
|
+
const dnsData = {
|
|
17788
|
+
domain: outputs.domain,
|
|
17789
|
+
dkimTokens: outputs.dkimTokens,
|
|
17790
|
+
mailFromDomain,
|
|
17791
|
+
region
|
|
17792
|
+
};
|
|
17793
|
+
try {
|
|
17794
|
+
progress.start(
|
|
17795
|
+
`Creating DNS records in ${getDNSProviderDisplayName(dnsProvider)}`
|
|
17796
|
+
);
|
|
17797
|
+
const result = await createDNSRecordsForProvider(
|
|
17798
|
+
credResult.credentials,
|
|
17799
|
+
dnsData
|
|
17800
|
+
);
|
|
17801
|
+
if (result.success) {
|
|
17802
|
+
progress.succeed(
|
|
17803
|
+
`Created ${result.recordsCreated} DNS records in ${getDNSProviderDisplayName(dnsProvider)}`
|
|
17804
|
+
);
|
|
17805
|
+
dnsAutoCreated = true;
|
|
17806
|
+
} else {
|
|
17807
|
+
progress.fail(
|
|
17808
|
+
`Failed to create some DNS records: ${result.errors?.join(", ")}`
|
|
17809
|
+
);
|
|
17810
|
+
progress.info(
|
|
17811
|
+
"You can manually add the required DNS records shown below"
|
|
17812
|
+
);
|
|
17813
|
+
}
|
|
17814
|
+
} catch (error) {
|
|
17815
|
+
progress.fail(
|
|
17816
|
+
`Failed to create DNS records automatically: ${error.message}`
|
|
17817
|
+
);
|
|
17818
|
+
progress.info(
|
|
17819
|
+
"You can manually add the required DNS records shown below"
|
|
17820
|
+
);
|
|
17821
|
+
}
|
|
17822
|
+
} else {
|
|
17823
|
+
clack20.log.warn(
|
|
17824
|
+
credResult.error || `Unable to validate ${getDNSProviderDisplayName(dnsProvider)} credentials`
|
|
16686
17825
|
);
|
|
17826
|
+
if (dnsProvider === "vercel" || dnsProvider === "cloudflare") {
|
|
17827
|
+
clack20.log.info(
|
|
17828
|
+
`Set ${dnsProvider === "vercel" ? "VERCEL_TOKEN" : "CLOUDFLARE_API_TOKEN"} to enable automatic DNS management.`
|
|
17829
|
+
);
|
|
17830
|
+
clack20.log.info(
|
|
17831
|
+
`You can create a token at: ${pc21.cyan(getDNSProviderTokenUrl(dnsProvider))}`
|
|
17832
|
+
);
|
|
17833
|
+
}
|
|
17834
|
+
}
|
|
17835
|
+
}
|
|
17836
|
+
if (!dnsAutoCreated) {
|
|
17837
|
+
const mailFromDomain = updatedConfig.mailFromDomain || `mail.${outputs.domain}`;
|
|
17838
|
+
const dnsData = {
|
|
17839
|
+
domain: outputs.domain,
|
|
17840
|
+
dkimTokens: outputs.dkimTokens,
|
|
17841
|
+
mailFromDomain,
|
|
17842
|
+
region
|
|
17843
|
+
};
|
|
17844
|
+
const dnsRecords = buildEmailDNSRecords(dnsData);
|
|
17845
|
+
const displayRecords = formatDNSRecordsForDisplay(dnsRecords);
|
|
17846
|
+
console.log(
|
|
17847
|
+
`
|
|
17848
|
+
${pc21.bold("Add these DNS records to your DNS provider:")}
|
|
17849
|
+
`
|
|
17850
|
+
);
|
|
17851
|
+
for (const record of displayRecords) {
|
|
17852
|
+
console.log(` ${pc21.cyan(record.type)} ${record.name}`);
|
|
17853
|
+
console.log(` ${pc21.dim(record.value)}`);
|
|
16687
17854
|
}
|
|
17855
|
+
console.log("");
|
|
16688
17856
|
}
|
|
16689
17857
|
}
|
|
16690
17858
|
updateEmailConfig(metadata, updatedConfig);
|
|
@@ -17257,127 +18425,578 @@ ${pc23.dim("Service:")} ${pc23.cyan(serviceLabel)}`);
|
|
|
17257
18425
|
` ${pc23.green("+")} ${pc23.bold("SQS")} - Event queuing (Production+)`
|
|
17258
18426
|
);
|
|
17259
18427
|
console.log(
|
|
17260
|
-
` ${pc23.green("+")} ${pc23.bold("Lambda")} - Event processing (Production+)`
|
|
18428
|
+
` ${pc23.green("+")} ${pc23.bold("Lambda")} - Event processing (Production+)`
|
|
18429
|
+
);
|
|
18430
|
+
console.log(
|
|
18431
|
+
` ${pc23.green("+")} ${pc23.bold("DynamoDB")} - Email history (Production+)`
|
|
18432
|
+
);
|
|
18433
|
+
}
|
|
18434
|
+
console.log(
|
|
18435
|
+
` ${pc23.yellow("?")} ${pc23.bold("Route53")} - Auto DNS ${pc23.dim("(optional)")}`
|
|
18436
|
+
);
|
|
18437
|
+
console.log(
|
|
18438
|
+
` ${pc23.yellow("?")} ${pc23.bold("IAM OIDC")} - Vercel integration ${pc23.dim("(if using Vercel)")}`
|
|
18439
|
+
);
|
|
18440
|
+
}
|
|
18441
|
+
if (!service || service === "sms") {
|
|
18442
|
+
console.log(
|
|
18443
|
+
` ${pc23.green("+")} ${pc23.bold("SMS Voice")} - SMS sending & management`
|
|
18444
|
+
);
|
|
18445
|
+
console.log(` ${pc23.green("+")} ${pc23.bold("DynamoDB")} - Message history`);
|
|
18446
|
+
console.log(` ${pc23.green("+")} ${pc23.bold("Lambda")} - Event processing`);
|
|
18447
|
+
}
|
|
18448
|
+
if (!service || service === "cdn") {
|
|
18449
|
+
console.log(` ${pc23.green("+")} ${pc23.bold("S3")} - Asset storage`);
|
|
18450
|
+
console.log(
|
|
18451
|
+
` ${pc23.green("+")} ${pc23.bold("CloudFront")} - CDN distribution`
|
|
18452
|
+
);
|
|
18453
|
+
console.log(` ${pc23.green("+")} ${pc23.bold("ACM")} - SSL certificates`);
|
|
18454
|
+
console.log(
|
|
18455
|
+
` ${pc23.yellow("?")} ${pc23.bold("Route53")} - DNS management ${pc23.dim("(optional)")}`
|
|
18456
|
+
);
|
|
18457
|
+
}
|
|
18458
|
+
console.log(`
|
|
18459
|
+
${pc23.dim("Get full IAM policy JSON:")}`);
|
|
18460
|
+
console.log(` ${pc23.cyan("wraps permissions --json")}`);
|
|
18461
|
+
if (service) {
|
|
18462
|
+
console.log(`
|
|
18463
|
+
${pc23.dim("Get permissions for all services:")}`);
|
|
18464
|
+
console.log(` ${pc23.cyan("wraps permissions")}`);
|
|
18465
|
+
}
|
|
18466
|
+
console.log(`
|
|
18467
|
+
${pc23.dim("Documentation:")}`);
|
|
18468
|
+
console.log(
|
|
18469
|
+
` ${pc23.blue("https://wraps.dev/docs/guides/aws-setup/permissions")}
|
|
18470
|
+
`
|
|
18471
|
+
);
|
|
18472
|
+
}
|
|
18473
|
+
async function permissions(options) {
|
|
18474
|
+
const startTime = Date.now();
|
|
18475
|
+
trackCommand("permissions", {
|
|
18476
|
+
json: options.json,
|
|
18477
|
+
preset: options.preset,
|
|
18478
|
+
service: options.service
|
|
18479
|
+
});
|
|
18480
|
+
const policy = buildPolicy(options.service, options.preset);
|
|
18481
|
+
if (options.json) {
|
|
18482
|
+
console.log(JSON.stringify(policy, null, 2));
|
|
18483
|
+
} else {
|
|
18484
|
+
displaySummary(options.service, options.preset);
|
|
18485
|
+
console.log(pc23.bold("Quick Setup:\n"));
|
|
18486
|
+
console.log("1. Copy the IAM policy:");
|
|
18487
|
+
console.log(
|
|
18488
|
+
` ${pc23.cyan("wraps permissions --json > wraps-policy.json")}
|
|
18489
|
+
`
|
|
18490
|
+
);
|
|
18491
|
+
console.log("2. Create the policy in AWS Console:");
|
|
18492
|
+
console.log(" IAM > Policies > Create Policy > JSON\n");
|
|
18493
|
+
console.log("3. Attach to your IAM user/role\n");
|
|
18494
|
+
clack22.outro(
|
|
18495
|
+
pc23.green("Run with --json to get the full IAM policy document")
|
|
18496
|
+
);
|
|
18497
|
+
}
|
|
18498
|
+
trackCommand("permissions", {
|
|
18499
|
+
success: true,
|
|
18500
|
+
duration_ms: Date.now() - startTime
|
|
18501
|
+
});
|
|
18502
|
+
}
|
|
18503
|
+
|
|
18504
|
+
// src/commands/platform/connect.ts
|
|
18505
|
+
init_esm_shims();
|
|
18506
|
+
import {
|
|
18507
|
+
GetRoleCommand,
|
|
18508
|
+
IAMClient as IAMClient2,
|
|
18509
|
+
PutRolePolicyCommand
|
|
18510
|
+
} from "@aws-sdk/client-iam";
|
|
18511
|
+
import { confirm as confirm11, intro as intro21, isCancel as isCancel16, log as log21, outro as outro18, select as select12 } from "@clack/prompts";
|
|
18512
|
+
import * as pulumi19 from "@pulumi/pulumi";
|
|
18513
|
+
import pc24 from "picocolors";
|
|
18514
|
+
init_events();
|
|
18515
|
+
init_aws();
|
|
18516
|
+
init_fs();
|
|
18517
|
+
init_metadata();
|
|
18518
|
+
init_prompts();
|
|
18519
|
+
function buildConsolePolicyDocument(emailConfig, smsConfig) {
|
|
18520
|
+
const statements = [];
|
|
18521
|
+
statements.push({
|
|
18522
|
+
Effect: "Allow",
|
|
18523
|
+
Action: [
|
|
18524
|
+
"ses:GetAccount",
|
|
18525
|
+
"ses:GetSendStatistics",
|
|
18526
|
+
"ses:ListIdentities",
|
|
18527
|
+
"ses:GetIdentityVerificationAttributes",
|
|
18528
|
+
"ses:ListEmailIdentities",
|
|
18529
|
+
"ses:GetEmailIdentity",
|
|
18530
|
+
"ses:GetConfigurationSet",
|
|
18531
|
+
"ses:GetConfigurationSetEventDestinations",
|
|
18532
|
+
"cloudwatch:GetMetricData",
|
|
18533
|
+
"cloudwatch:GetMetricStatistics"
|
|
18534
|
+
],
|
|
18535
|
+
Resource: "*"
|
|
18536
|
+
});
|
|
18537
|
+
statements.push({
|
|
18538
|
+
Effect: "Allow",
|
|
18539
|
+
Action: [
|
|
18540
|
+
"ses:GetTemplate",
|
|
18541
|
+
"ses:ListTemplates",
|
|
18542
|
+
"ses:CreateTemplate",
|
|
18543
|
+
"ses:UpdateTemplate",
|
|
18544
|
+
"ses:DeleteTemplate",
|
|
18545
|
+
"ses:TestRenderTemplate"
|
|
18546
|
+
],
|
|
18547
|
+
Resource: "*"
|
|
18548
|
+
});
|
|
18549
|
+
const sendingEnabled = !emailConfig || emailConfig.sendingEnabled !== false;
|
|
18550
|
+
if (sendingEnabled) {
|
|
18551
|
+
statements.push({
|
|
18552
|
+
Effect: "Allow",
|
|
18553
|
+
Action: [
|
|
18554
|
+
"ses:SendEmail",
|
|
18555
|
+
"ses:SendRawEmail",
|
|
18556
|
+
"ses:SendTemplatedEmail",
|
|
18557
|
+
"ses:SendBulkTemplatedEmail",
|
|
18558
|
+
"ses:SendBulkEmail"
|
|
18559
|
+
],
|
|
18560
|
+
Resource: "*"
|
|
18561
|
+
});
|
|
18562
|
+
}
|
|
18563
|
+
const eventTracking = emailConfig?.eventTracking;
|
|
18564
|
+
if (eventTracking?.dynamoDBHistory) {
|
|
18565
|
+
statements.push({
|
|
18566
|
+
Effect: "Allow",
|
|
18567
|
+
Action: [
|
|
18568
|
+
"dynamodb:PutItem",
|
|
18569
|
+
"dynamodb:GetItem",
|
|
18570
|
+
"dynamodb:Query",
|
|
18571
|
+
"dynamodb:Scan",
|
|
18572
|
+
"dynamodb:BatchGetItem",
|
|
18573
|
+
"dynamodb:DescribeTable"
|
|
18574
|
+
],
|
|
18575
|
+
Resource: [
|
|
18576
|
+
"arn:aws:dynamodb:*:*:table/wraps-email-*",
|
|
18577
|
+
"arn:aws:dynamodb:*:*:table/wraps-email-*/index/*"
|
|
18578
|
+
]
|
|
18579
|
+
});
|
|
18580
|
+
}
|
|
18581
|
+
if (eventTracking?.enabled) {
|
|
18582
|
+
statements.push({
|
|
18583
|
+
Effect: "Allow",
|
|
18584
|
+
Action: ["events:PutEvents", "events:DescribeEventBus"],
|
|
18585
|
+
Resource: "arn:aws:events:*:*:event-bus/wraps-email-*"
|
|
18586
|
+
});
|
|
18587
|
+
}
|
|
18588
|
+
if (eventTracking?.enabled) {
|
|
18589
|
+
statements.push({
|
|
18590
|
+
Effect: "Allow",
|
|
18591
|
+
Action: [
|
|
18592
|
+
"sqs:SendMessage",
|
|
18593
|
+
"sqs:ReceiveMessage",
|
|
18594
|
+
"sqs:DeleteMessage",
|
|
18595
|
+
"sqs:GetQueueAttributes"
|
|
18596
|
+
],
|
|
18597
|
+
Resource: "arn:aws:sqs:*:*:wraps-email-*"
|
|
18598
|
+
});
|
|
18599
|
+
}
|
|
18600
|
+
const emailArchiving = emailConfig?.emailArchiving;
|
|
18601
|
+
if (emailArchiving?.enabled) {
|
|
18602
|
+
statements.push({
|
|
18603
|
+
Effect: "Allow",
|
|
18604
|
+
Action: [
|
|
18605
|
+
"ses:StartArchiveSearch",
|
|
18606
|
+
"ses:GetArchiveSearchResults",
|
|
18607
|
+
"ses:GetArchiveMessage",
|
|
18608
|
+
"ses:GetArchiveMessageContent",
|
|
18609
|
+
"ses:GetArchive",
|
|
18610
|
+
"ses:ListArchives",
|
|
18611
|
+
"ses:StartArchiveExport",
|
|
18612
|
+
"ses:GetArchiveExport"
|
|
18613
|
+
],
|
|
18614
|
+
Resource: "arn:aws:ses:*:*:mailmanager-archive/*"
|
|
18615
|
+
});
|
|
18616
|
+
}
|
|
18617
|
+
if (smsConfig) {
|
|
18618
|
+
statements.push({
|
|
18619
|
+
Effect: "Allow",
|
|
18620
|
+
Action: [
|
|
18621
|
+
"sms-voice:DescribeAccountAttributes",
|
|
18622
|
+
"sms-voice:DescribeSpendLimits",
|
|
18623
|
+
"sms-voice:DescribeConfigurationSets",
|
|
18624
|
+
"sms-voice:DescribeOptOutLists",
|
|
18625
|
+
"sms-voice:DescribeOptedOutNumbers",
|
|
18626
|
+
"sms-voice:DescribePools",
|
|
18627
|
+
"sms-voice:DescribePhoneNumbers",
|
|
18628
|
+
"sms-voice:DescribeProtectConfigurations",
|
|
18629
|
+
"sms-voice:DescribeRegistrations",
|
|
18630
|
+
"sms-voice:DescribeRegistrationAttachments",
|
|
18631
|
+
"sms-voice:DescribeRegistrationFieldDefinitions",
|
|
18632
|
+
"sms-voice:DescribeRegistrationFieldValues",
|
|
18633
|
+
"sms-voice:DescribeRegistrationSectionDefinitions",
|
|
18634
|
+
"sms-voice:DescribeRegistrationVersions"
|
|
18635
|
+
],
|
|
18636
|
+
Resource: "*"
|
|
18637
|
+
});
|
|
18638
|
+
const smsSendingEnabled = smsConfig.sendingEnabled !== false;
|
|
18639
|
+
if (smsSendingEnabled) {
|
|
18640
|
+
statements.push({
|
|
18641
|
+
Effect: "Allow",
|
|
18642
|
+
Action: ["sms-voice:SendTextMessage", "sms-voice:SendMediaMessage"],
|
|
18643
|
+
Resource: "*"
|
|
18644
|
+
});
|
|
18645
|
+
}
|
|
18646
|
+
const smsEventTracking = smsConfig.eventTracking;
|
|
18647
|
+
if (smsEventTracking?.dynamoDBHistory) {
|
|
18648
|
+
statements.push({
|
|
18649
|
+
Effect: "Allow",
|
|
18650
|
+
Action: [
|
|
18651
|
+
"dynamodb:GetItem",
|
|
18652
|
+
"dynamodb:Query",
|
|
18653
|
+
"dynamodb:Scan",
|
|
18654
|
+
"dynamodb:BatchGetItem",
|
|
18655
|
+
"dynamodb:DescribeTable"
|
|
18656
|
+
],
|
|
18657
|
+
Resource: [
|
|
18658
|
+
"arn:aws:dynamodb:*:*:table/wraps-sms-*",
|
|
18659
|
+
"arn:aws:dynamodb:*:*:table/wraps-sms-*/index/*"
|
|
18660
|
+
]
|
|
18661
|
+
});
|
|
18662
|
+
}
|
|
18663
|
+
if (smsEventTracking?.enabled) {
|
|
18664
|
+
statements.push({
|
|
18665
|
+
Effect: "Allow",
|
|
18666
|
+
Action: ["sns:GetTopicAttributes", "sns:ListSubscriptionsByTopic"],
|
|
18667
|
+
Resource: "arn:aws:sns:*:*:wraps-sms-*"
|
|
18668
|
+
});
|
|
18669
|
+
}
|
|
18670
|
+
}
|
|
18671
|
+
return {
|
|
18672
|
+
Version: "2012-10-17",
|
|
18673
|
+
Statement: statements
|
|
18674
|
+
};
|
|
18675
|
+
}
|
|
18676
|
+
async function connect3(options) {
|
|
18677
|
+
const startTime = Date.now();
|
|
18678
|
+
intro21(pc24.bold("Connect to Wraps Platform"));
|
|
18679
|
+
const progress = new DeploymentProgress();
|
|
18680
|
+
try {
|
|
18681
|
+
const wasAutoInstalled = await progress.execute(
|
|
18682
|
+
"Checking Pulumi CLI installation",
|
|
18683
|
+
async () => await ensurePulumiInstalled()
|
|
18684
|
+
);
|
|
18685
|
+
if (wasAutoInstalled) {
|
|
18686
|
+
progress.info("Pulumi CLI was automatically installed");
|
|
18687
|
+
}
|
|
18688
|
+
const identity = await progress.execute(
|
|
18689
|
+
"Validating AWS credentials",
|
|
18690
|
+
async () => validateAWSCredentials()
|
|
18691
|
+
);
|
|
18692
|
+
progress.info(`Connected to AWS account: ${pc24.cyan(identity.accountId)}`);
|
|
18693
|
+
let region = options.region;
|
|
18694
|
+
if (!region) {
|
|
18695
|
+
region = await getAWSRegion();
|
|
18696
|
+
}
|
|
18697
|
+
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
18698
|
+
if (!metadata) {
|
|
18699
|
+
progress.stop();
|
|
18700
|
+
log21.error(
|
|
18701
|
+
`No Wraps deployment found for account ${pc24.cyan(identity.accountId)} in region ${pc24.cyan(region)}`
|
|
18702
|
+
);
|
|
18703
|
+
console.log(
|
|
18704
|
+
`
|
|
18705
|
+
Run ${pc24.cyan("wraps email init")} to deploy infrastructure first.
|
|
18706
|
+
`
|
|
17261
18707
|
);
|
|
18708
|
+
process.exit(1);
|
|
18709
|
+
}
|
|
18710
|
+
const hasEmail = !!metadata.services.email?.config;
|
|
18711
|
+
const hasSms = !!metadata.services.sms?.config;
|
|
18712
|
+
if (!(hasEmail || hasSms)) {
|
|
18713
|
+
progress.stop();
|
|
18714
|
+
log21.error("No services deployed in this region.");
|
|
17262
18715
|
console.log(
|
|
17263
|
-
`
|
|
18716
|
+
`
|
|
18717
|
+
Run ${pc24.cyan("wraps email init")} or ${pc24.cyan("wraps sms init")} first.
|
|
18718
|
+
`
|
|
17264
18719
|
);
|
|
18720
|
+
process.exit(1);
|
|
18721
|
+
}
|
|
18722
|
+
progress.info(
|
|
18723
|
+
`Found services: ${[hasEmail && "email", hasSms && "sms"].filter(Boolean).join(", ")}`
|
|
18724
|
+
);
|
|
18725
|
+
let webhookSecret;
|
|
18726
|
+
let needsDeployment = false;
|
|
18727
|
+
if (hasEmail) {
|
|
18728
|
+
const emailConfig = metadata.services.email.config;
|
|
18729
|
+
const existingSecret = metadata.services.email.webhookSecret;
|
|
18730
|
+
if (!emailConfig.eventTracking?.enabled) {
|
|
18731
|
+
progress.stop();
|
|
18732
|
+
log21.warn(
|
|
18733
|
+
"Event tracking must be enabled to connect to the Wraps Platform."
|
|
18734
|
+
);
|
|
18735
|
+
log21.info(
|
|
18736
|
+
"Enabling event tracking will allow SES events to be streamed to the dashboard."
|
|
18737
|
+
);
|
|
18738
|
+
const enableEventTracking = await confirm11({
|
|
18739
|
+
message: "Enable event tracking now?",
|
|
18740
|
+
initialValue: true
|
|
18741
|
+
});
|
|
18742
|
+
if (isCancel16(enableEventTracking) || !enableEventTracking) {
|
|
18743
|
+
outro18("Platform connection cancelled.");
|
|
18744
|
+
process.exit(0);
|
|
18745
|
+
}
|
|
18746
|
+
metadata.services.email.config = {
|
|
18747
|
+
...emailConfig,
|
|
18748
|
+
eventTracking: {
|
|
18749
|
+
enabled: true,
|
|
18750
|
+
eventBridge: true,
|
|
18751
|
+
events: [
|
|
18752
|
+
"SEND",
|
|
18753
|
+
"DELIVERY",
|
|
18754
|
+
"OPEN",
|
|
18755
|
+
"CLICK",
|
|
18756
|
+
"BOUNCE",
|
|
18757
|
+
"COMPLAINT"
|
|
18758
|
+
],
|
|
18759
|
+
dynamoDBHistory: emailConfig.eventTracking?.dynamoDBHistory ?? false,
|
|
18760
|
+
archiveRetention: emailConfig.eventTracking?.archiveRetention ?? "90days"
|
|
18761
|
+
}
|
|
18762
|
+
};
|
|
18763
|
+
needsDeployment = true;
|
|
18764
|
+
}
|
|
18765
|
+
if (existingSecret) {
|
|
18766
|
+
progress.stop();
|
|
18767
|
+
log21.info(
|
|
18768
|
+
`Already connected to Wraps Platform (AWS Account: ${pc24.cyan(metadata.accountId)})`
|
|
18769
|
+
);
|
|
18770
|
+
const action = await select12({
|
|
18771
|
+
message: "What would you like to do?",
|
|
18772
|
+
options: [
|
|
18773
|
+
{
|
|
18774
|
+
value: "keep",
|
|
18775
|
+
label: "Keep current connection",
|
|
18776
|
+
hint: "Continue with existing webhook secret"
|
|
18777
|
+
},
|
|
18778
|
+
{
|
|
18779
|
+
value: "regenerate",
|
|
18780
|
+
label: "Regenerate webhook secret",
|
|
18781
|
+
hint: "Create new secret (requires update in dashboard)"
|
|
18782
|
+
},
|
|
18783
|
+
{
|
|
18784
|
+
value: "disconnect",
|
|
18785
|
+
label: "Disconnect from platform",
|
|
18786
|
+
hint: "Stop sending events to Wraps"
|
|
18787
|
+
}
|
|
18788
|
+
]
|
|
18789
|
+
});
|
|
18790
|
+
if (isCancel16(action)) {
|
|
18791
|
+
outro18("Operation cancelled");
|
|
18792
|
+
process.exit(0);
|
|
18793
|
+
}
|
|
18794
|
+
if (action === "keep") {
|
|
18795
|
+
webhookSecret = existingSecret;
|
|
18796
|
+
} else if (action === "disconnect") {
|
|
18797
|
+
const confirmDisconnect = await confirm11({
|
|
18798
|
+
message: "Are you sure? Events will no longer be sent to the Wraps Platform.",
|
|
18799
|
+
initialValue: false
|
|
18800
|
+
});
|
|
18801
|
+
if (isCancel16(confirmDisconnect) || !confirmDisconnect) {
|
|
18802
|
+
outro18("Disconnect cancelled");
|
|
18803
|
+
process.exit(0);
|
|
18804
|
+
}
|
|
18805
|
+
metadata.services.email.webhookSecret = void 0;
|
|
18806
|
+
needsDeployment = true;
|
|
18807
|
+
webhookSecret = void 0;
|
|
18808
|
+
} else {
|
|
18809
|
+
webhookSecret = generateWebhookSecret();
|
|
18810
|
+
metadata.services.email.webhookSecret = webhookSecret;
|
|
18811
|
+
needsDeployment = true;
|
|
18812
|
+
}
|
|
18813
|
+
} else {
|
|
18814
|
+
webhookSecret = generateWebhookSecret();
|
|
18815
|
+
metadata.services.email.webhookSecret = webhookSecret;
|
|
18816
|
+
needsDeployment = true;
|
|
18817
|
+
}
|
|
18818
|
+
}
|
|
18819
|
+
if (needsDeployment && hasEmail) {
|
|
18820
|
+
let vercelConfig;
|
|
18821
|
+
if (metadata.provider === "vercel" && !metadata.vercel) {
|
|
18822
|
+
progress.stop();
|
|
18823
|
+
vercelConfig = await promptVercelConfig();
|
|
18824
|
+
} else if (metadata.provider === "vercel") {
|
|
18825
|
+
vercelConfig = metadata.vercel;
|
|
18826
|
+
}
|
|
18827
|
+
const stackConfig = {
|
|
18828
|
+
provider: metadata.provider,
|
|
18829
|
+
region,
|
|
18830
|
+
vercel: vercelConfig,
|
|
18831
|
+
emailConfig: metadata.services.email.config,
|
|
18832
|
+
webhook: webhookSecret ? {
|
|
18833
|
+
awsAccountNumber: metadata.accountId,
|
|
18834
|
+
webhookSecret
|
|
18835
|
+
} : void 0
|
|
18836
|
+
};
|
|
18837
|
+
await progress.execute("Configuring event streaming", async () => {
|
|
18838
|
+
await ensurePulumiWorkDir();
|
|
18839
|
+
const stack = await pulumi19.automation.LocalWorkspace.createOrSelectStack(
|
|
18840
|
+
{
|
|
18841
|
+
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
18842
|
+
projectName: "wraps-email",
|
|
18843
|
+
program: async () => {
|
|
18844
|
+
const result = await deployEmailStack(stackConfig);
|
|
18845
|
+
return {
|
|
18846
|
+
roleArn: result.roleArn,
|
|
18847
|
+
configSetName: result.configSetName,
|
|
18848
|
+
tableName: result.tableName,
|
|
18849
|
+
region: result.region
|
|
18850
|
+
};
|
|
18851
|
+
}
|
|
18852
|
+
},
|
|
18853
|
+
{
|
|
18854
|
+
workDir: getPulumiWorkDir(),
|
|
18855
|
+
envVars: {
|
|
18856
|
+
PULUMI_CONFIG_PASSPHRASE: "",
|
|
18857
|
+
AWS_REGION: region
|
|
18858
|
+
},
|
|
18859
|
+
secretsProvider: "passphrase"
|
|
18860
|
+
}
|
|
18861
|
+
);
|
|
18862
|
+
await stack.setConfig("aws:region", { value: region });
|
|
18863
|
+
await stack.refresh({ onOutput: () => {
|
|
18864
|
+
} });
|
|
18865
|
+
await stack.up({ onOutput: () => {
|
|
18866
|
+
} });
|
|
18867
|
+
});
|
|
18868
|
+
progress.succeed("Event streaming configured");
|
|
18869
|
+
} else if (!needsDeployment && hasEmail && webhookSecret) {
|
|
18870
|
+
progress.succeed("Event streaming already configured");
|
|
18871
|
+
}
|
|
18872
|
+
const roleName = "wraps-console-access-role";
|
|
18873
|
+
const iam8 = new IAMClient2({ region: "us-east-1" });
|
|
18874
|
+
let roleExists4 = false;
|
|
18875
|
+
try {
|
|
18876
|
+
await iam8.send(new GetRoleCommand({ RoleName: roleName }));
|
|
18877
|
+
roleExists4 = true;
|
|
18878
|
+
} catch (error) {
|
|
18879
|
+
if (error && typeof error === "object" && "name" in error && error.name !== "NoSuchEntity") {
|
|
18880
|
+
throw error;
|
|
18881
|
+
}
|
|
18882
|
+
}
|
|
18883
|
+
if (roleExists4) {
|
|
18884
|
+
const emailConfig = metadata.services.email?.config;
|
|
18885
|
+
const smsConfig = metadata.services.sms?.config;
|
|
18886
|
+
const policy = buildConsolePolicyDocument(emailConfig, smsConfig);
|
|
18887
|
+
await progress.execute("Updating platform access role", async () => {
|
|
18888
|
+
await iam8.send(
|
|
18889
|
+
new PutRolePolicyCommand({
|
|
18890
|
+
RoleName: roleName,
|
|
18891
|
+
PolicyName: "wraps-console-access-policy",
|
|
18892
|
+
PolicyDocument: JSON.stringify(policy, null, 2)
|
|
18893
|
+
})
|
|
18894
|
+
);
|
|
18895
|
+
});
|
|
18896
|
+
progress.succeed("Platform access role updated");
|
|
18897
|
+
} else {
|
|
18898
|
+
progress.info(
|
|
18899
|
+
`IAM role ${pc24.cyan(roleName)} will be created when you add your AWS account in the dashboard`
|
|
18900
|
+
);
|
|
18901
|
+
}
|
|
18902
|
+
await saveConnectionMetadata(metadata);
|
|
18903
|
+
progress.stop();
|
|
18904
|
+
outro18(pc24.green("Platform connection complete!"));
|
|
18905
|
+
if (webhookSecret && needsDeployment) {
|
|
18906
|
+
console.log(`
|
|
18907
|
+
${pc24.bold("Webhook Secret")} ${pc24.dim("(save this!)")}`);
|
|
18908
|
+
console.log(pc24.dim("\u2500".repeat(60)));
|
|
18909
|
+
console.log(` ${pc24.cyan(webhookSecret)}`);
|
|
18910
|
+
console.log(pc24.dim("\u2500".repeat(60)));
|
|
18911
|
+
} else if (metadata.services.email?.webhookSecret && !needsDeployment) {
|
|
18912
|
+
console.log(`
|
|
18913
|
+
${pc24.bold("Existing Webhook Secret:")}`);
|
|
18914
|
+
console.log(pc24.dim("\u2500".repeat(60)));
|
|
18915
|
+
console.log(` ${pc24.cyan(metadata.services.email.webhookSecret)}`);
|
|
18916
|
+
console.log(pc24.dim("\u2500".repeat(60)));
|
|
17265
18917
|
}
|
|
17266
|
-
console.log(
|
|
17267
|
-
` ${pc23.yellow("?")} ${pc23.bold("Route53")} - Auto DNS ${pc23.dim("(optional)")}`
|
|
17268
|
-
);
|
|
17269
|
-
console.log(
|
|
17270
|
-
` ${pc23.yellow("?")} ${pc23.bold("IAM OIDC")} - Vercel integration ${pc23.dim("(if using Vercel)")}`
|
|
17271
|
-
);
|
|
17272
|
-
}
|
|
17273
|
-
if (!service || service === "sms") {
|
|
17274
|
-
console.log(
|
|
17275
|
-
` ${pc23.green("+")} ${pc23.bold("SMS Voice")} - SMS sending & management`
|
|
17276
|
-
);
|
|
17277
|
-
console.log(` ${pc23.green("+")} ${pc23.bold("DynamoDB")} - Message history`);
|
|
17278
|
-
console.log(` ${pc23.green("+")} ${pc23.bold("Lambda")} - Event processing`);
|
|
17279
|
-
}
|
|
17280
|
-
if (!service || service === "cdn") {
|
|
17281
|
-
console.log(` ${pc23.green("+")} ${pc23.bold("S3")} - Asset storage`);
|
|
17282
|
-
console.log(
|
|
17283
|
-
` ${pc23.green("+")} ${pc23.bold("CloudFront")} - CDN distribution`
|
|
17284
|
-
);
|
|
17285
|
-
console.log(` ${pc23.green("+")} ${pc23.bold("ACM")} - SSL certificates`);
|
|
17286
|
-
console.log(
|
|
17287
|
-
` ${pc23.yellow("?")} ${pc23.bold("Route53")} - DNS management ${pc23.dim("(optional)")}`
|
|
17288
|
-
);
|
|
17289
|
-
}
|
|
17290
|
-
console.log(`
|
|
17291
|
-
${pc23.dim("Get full IAM policy JSON:")}`);
|
|
17292
|
-
console.log(` ${pc23.cyan("wraps permissions --json")}`);
|
|
17293
|
-
if (service) {
|
|
17294
18918
|
console.log(`
|
|
17295
|
-
${
|
|
17296
|
-
console.log(` ${
|
|
17297
|
-
}
|
|
17298
|
-
|
|
17299
|
-
|
|
17300
|
-
|
|
17301
|
-
|
|
17302
|
-
`
|
|
17303
|
-
);
|
|
17304
|
-
}
|
|
17305
|
-
async function permissions(options) {
|
|
17306
|
-
const startTime = Date.now();
|
|
17307
|
-
trackCommand("permissions", {
|
|
17308
|
-
json: options.json,
|
|
17309
|
-
preset: options.preset,
|
|
17310
|
-
service: options.service
|
|
17311
|
-
});
|
|
17312
|
-
const policy = buildPolicy(options.service, options.preset);
|
|
17313
|
-
if (options.json) {
|
|
17314
|
-
console.log(JSON.stringify(policy, null, 2));
|
|
17315
|
-
} else {
|
|
17316
|
-
displaySummary(options.service, options.preset);
|
|
17317
|
-
console.log(pc23.bold("Quick Setup:\n"));
|
|
17318
|
-
console.log("1. Copy the IAM policy:");
|
|
18919
|
+
${pc24.bold("Next Steps:")}`);
|
|
18920
|
+
console.log(` 1. Go to ${pc24.cyan("https://app.wraps.dev/settings/aws")}`);
|
|
18921
|
+
console.log(` 2. Add your AWS account: ${pc24.cyan(identity.accountId)}`);
|
|
18922
|
+
if (webhookSecret) {
|
|
18923
|
+
console.log(" 3. Paste the webhook secret shown above");
|
|
18924
|
+
}
|
|
18925
|
+
console.log();
|
|
17319
18926
|
console.log(
|
|
17320
|
-
|
|
17321
|
-
|
|
17322
|
-
|
|
17323
|
-
console.log("2. Create the policy in AWS Console:");
|
|
17324
|
-
console.log(" IAM > Policies > Create Policy > JSON\n");
|
|
17325
|
-
console.log("3. Attach to your IAM user/role\n");
|
|
17326
|
-
clack22.outro(
|
|
17327
|
-
pc23.green("Run with --json to get the full IAM policy document")
|
|
18927
|
+
pc24.dim(
|
|
18928
|
+
"Events from your AWS infrastructure will stream to the dashboard."
|
|
18929
|
+
)
|
|
17328
18930
|
);
|
|
18931
|
+
console.log();
|
|
18932
|
+
const duration = Date.now() - startTime;
|
|
18933
|
+
trackCommand("platform:connect", {
|
|
18934
|
+
success: true,
|
|
18935
|
+
duration_ms: duration
|
|
18936
|
+
});
|
|
18937
|
+
} catch (error) {
|
|
18938
|
+
progress.stop();
|
|
18939
|
+
const duration = Date.now() - startTime;
|
|
18940
|
+
const errorCode = error instanceof Error ? error.name : "UNKNOWN_ERROR";
|
|
18941
|
+
trackError(errorCode, "platform:connect", {
|
|
18942
|
+
message: error instanceof Error ? error.message : String(error)
|
|
18943
|
+
});
|
|
18944
|
+
trackCommand("platform:connect", {
|
|
18945
|
+
success: false,
|
|
18946
|
+
duration_ms: duration
|
|
18947
|
+
});
|
|
18948
|
+
throw error;
|
|
17329
18949
|
}
|
|
17330
|
-
trackCommand("permissions", {
|
|
17331
|
-
success: true,
|
|
17332
|
-
duration_ms: Date.now() - startTime
|
|
17333
|
-
});
|
|
17334
18950
|
}
|
|
17335
18951
|
|
|
17336
18952
|
// src/commands/platform/index.ts
|
|
17337
18953
|
init_esm_shims();
|
|
17338
18954
|
import * as clack23 from "@clack/prompts";
|
|
17339
|
-
import
|
|
18955
|
+
import pc25 from "picocolors";
|
|
17340
18956
|
async function platform() {
|
|
17341
|
-
clack23.intro(
|
|
18957
|
+
clack23.intro(pc25.bold("Wraps Platform"));
|
|
17342
18958
|
console.log();
|
|
17343
18959
|
console.log(
|
|
17344
18960
|
" The Wraps Platform extends the free CLI with hosted features:"
|
|
17345
18961
|
);
|
|
17346
18962
|
console.log();
|
|
17347
|
-
console.log(` ${
|
|
17348
|
-
console.log(` ${
|
|
17349
|
-
console.log(` ${
|
|
17350
|
-
console.log(` ${
|
|
17351
|
-
console.log(` ${
|
|
17352
|
-
console.log(` ${
|
|
17353
|
-
console.log(` ${
|
|
18963
|
+
console.log(` ${pc25.bold("Features:")}`);
|
|
18964
|
+
console.log(` ${pc25.green("\u2713")} Visual email template editor`);
|
|
18965
|
+
console.log(` ${pc25.green("\u2713")} Broadcast campaigns & scheduling`);
|
|
18966
|
+
console.log(` ${pc25.green("\u2713")} Contact management & segments`);
|
|
18967
|
+
console.log(` ${pc25.green("\u2713")} Workflow automations`);
|
|
18968
|
+
console.log(` ${pc25.green("\u2713")} Analytics dashboard`);
|
|
18969
|
+
console.log(` ${pc25.green("\u2713")} Team collaboration`);
|
|
17354
18970
|
console.log();
|
|
17355
|
-
console.log(` ${
|
|
17356
|
-
console.log(` ${
|
|
17357
|
-
console.log(` ${
|
|
17358
|
-
console.log(` ${
|
|
17359
|
-
console.log(` ${
|
|
18971
|
+
console.log(` ${pc25.bold("Pricing:")}`);
|
|
18972
|
+
console.log(` ${pc25.cyan("Starter")} $10/mo 5,000 contacts`);
|
|
18973
|
+
console.log(` ${pc25.cyan("Growth")} $25/mo 25,000 contacts`);
|
|
18974
|
+
console.log(` ${pc25.cyan("Scale")} $50/mo 100,000 contacts`);
|
|
18975
|
+
console.log(` ${pc25.cyan("Enterprise")} Custom Unlimited contacts`);
|
|
17360
18976
|
console.log();
|
|
17361
18977
|
console.log(
|
|
17362
|
-
|
|
18978
|
+
pc25.dim(" + AWS costs at $0.10 per 1,000 emails (paid directly to AWS)")
|
|
17363
18979
|
);
|
|
17364
18980
|
console.log();
|
|
17365
18981
|
console.log(
|
|
17366
|
-
` ${
|
|
18982
|
+
` ${pc25.bold("Learn more:")} ${pc25.cyan("https://wraps.dev/platform")}`
|
|
17367
18983
|
);
|
|
17368
18984
|
console.log();
|
|
17369
|
-
console.log(
|
|
18985
|
+
console.log(pc25.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
17370
18986
|
console.log();
|
|
17371
|
-
console.log(` ${
|
|
18987
|
+
console.log(` ${pc25.bold("Platform Commands:")}`);
|
|
17372
18988
|
console.log();
|
|
17373
18989
|
console.log(
|
|
17374
|
-
` ${
|
|
18990
|
+
` ${pc25.cyan("wraps platform connect")} Connect infrastructure to Wraps Platform`
|
|
18991
|
+
);
|
|
18992
|
+
console.log(
|
|
18993
|
+
` ${pc25.cyan("wraps platform update-role")} Update IAM permissions for dashboard`
|
|
17375
18994
|
);
|
|
17376
18995
|
console.log();
|
|
17377
18996
|
console.log(
|
|
17378
|
-
|
|
18997
|
+
pc25.dim(" The connect command sets up event streaming and IAM permissions")
|
|
17379
18998
|
);
|
|
17380
|
-
console.log(
|
|
18999
|
+
console.log(pc25.dim(" in one step. Run it after deploying infrastructure."));
|
|
17381
19000
|
console.log();
|
|
17382
19001
|
}
|
|
17383
19002
|
|
|
@@ -17385,11 +19004,11 @@ async function platform() {
|
|
|
17385
19004
|
init_esm_shims();
|
|
17386
19005
|
init_aws();
|
|
17387
19006
|
init_metadata();
|
|
17388
|
-
import { GetRoleCommand, IAMClient as
|
|
17389
|
-
import { confirm as
|
|
17390
|
-
import
|
|
19007
|
+
import { GetRoleCommand as GetRoleCommand2, IAMClient as IAMClient3 } from "@aws-sdk/client-iam";
|
|
19008
|
+
import { confirm as confirm12, intro as intro23, isCancel as isCancel17, log as log22, outro as outro19 } from "@clack/prompts";
|
|
19009
|
+
import pc26 from "picocolors";
|
|
17391
19010
|
async function updateRole(options) {
|
|
17392
|
-
|
|
19011
|
+
intro23(pc26.bold("Update Platform Access Role"));
|
|
17393
19012
|
const progress = new DeploymentProgress();
|
|
17394
19013
|
const identity = await progress.execute(
|
|
17395
19014
|
"Validating AWS credentials",
|
|
@@ -17399,21 +19018,21 @@ async function updateRole(options) {
|
|
|
17399
19018
|
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
17400
19019
|
if (!metadata) {
|
|
17401
19020
|
progress.stop();
|
|
17402
|
-
|
|
17403
|
-
`No Wraps deployment found for account ${
|
|
19021
|
+
log22.error(
|
|
19022
|
+
`No Wraps deployment found for account ${pc26.cyan(identity.accountId)} in region ${pc26.cyan(region)}`
|
|
17404
19023
|
);
|
|
17405
19024
|
console.log(
|
|
17406
19025
|
`
|
|
17407
|
-
Run ${
|
|
19026
|
+
Run ${pc26.cyan("wraps email init")} to deploy infrastructure first.
|
|
17408
19027
|
`
|
|
17409
19028
|
);
|
|
17410
19029
|
process.exit(1);
|
|
17411
19030
|
}
|
|
17412
19031
|
const roleName = "wraps-console-access-role";
|
|
17413
|
-
const iam8 = new
|
|
19032
|
+
const iam8 = new IAMClient3({ region: "us-east-1" });
|
|
17414
19033
|
let roleExists4 = false;
|
|
17415
19034
|
try {
|
|
17416
|
-
await iam8.send(new
|
|
19035
|
+
await iam8.send(new GetRoleCommand2({ RoleName: roleName }));
|
|
17417
19036
|
roleExists4 = true;
|
|
17418
19037
|
} catch (error) {
|
|
17419
19038
|
if (error && typeof error === "object" && "name" in error && error.name !== "NoSuchEntity") {
|
|
@@ -17422,7 +19041,7 @@ Run ${pc25.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
17422
19041
|
}
|
|
17423
19042
|
if (!roleExists4) {
|
|
17424
19043
|
progress.stop();
|
|
17425
|
-
|
|
19044
|
+
log22.warn(`IAM role ${pc26.cyan(roleName)} does not exist`);
|
|
17426
19045
|
console.log(
|
|
17427
19046
|
"\nThis role is created when you connect AWS accounts through the Wraps Platform."
|
|
17428
19047
|
);
|
|
@@ -17431,21 +19050,21 @@ Run ${pc25.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
17431
19050
|
);
|
|
17432
19051
|
process.exit(0);
|
|
17433
19052
|
}
|
|
17434
|
-
progress.info(`Found IAM role: ${
|
|
19053
|
+
progress.info(`Found IAM role: ${pc26.cyan(roleName)}`);
|
|
17435
19054
|
if (!options.force) {
|
|
17436
19055
|
progress.stop();
|
|
17437
|
-
const shouldContinue = await
|
|
17438
|
-
message: `Update IAM role ${
|
|
19056
|
+
const shouldContinue = await confirm12({
|
|
19057
|
+
message: `Update IAM role ${pc26.cyan(roleName)} with latest permissions?`,
|
|
17439
19058
|
initialValue: true
|
|
17440
19059
|
});
|
|
17441
|
-
if (
|
|
17442
|
-
|
|
19060
|
+
if (isCancel17(shouldContinue) || !shouldContinue) {
|
|
19061
|
+
outro19("Update cancelled");
|
|
17443
19062
|
process.exit(0);
|
|
17444
19063
|
}
|
|
17445
19064
|
}
|
|
17446
19065
|
const emailConfig = metadata.services.email?.config;
|
|
17447
19066
|
const smsConfig = metadata.services.sms?.config;
|
|
17448
|
-
const policy =
|
|
19067
|
+
const policy = buildConsolePolicyDocument2(emailConfig, smsConfig);
|
|
17449
19068
|
const sendingEnabled = !emailConfig || emailConfig.sendingEnabled !== false;
|
|
17450
19069
|
const eventTracking = emailConfig?.eventTracking;
|
|
17451
19070
|
const emailArchiving = emailConfig?.emailArchiving;
|
|
@@ -17453,9 +19072,9 @@ Run ${pc25.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
17453
19072
|
const smsSendingEnabled = smsConfig && smsConfig.sendingEnabled !== false;
|
|
17454
19073
|
const smsEventTracking = smsConfig?.eventTracking;
|
|
17455
19074
|
await progress.execute("Updating IAM role permissions", async () => {
|
|
17456
|
-
const { PutRolePolicyCommand } = await import("@aws-sdk/client-iam");
|
|
19075
|
+
const { PutRolePolicyCommand: PutRolePolicyCommand2 } = await import("@aws-sdk/client-iam");
|
|
17457
19076
|
await iam8.send(
|
|
17458
|
-
new
|
|
19077
|
+
new PutRolePolicyCommand2({
|
|
17459
19078
|
RoleName: roleName,
|
|
17460
19079
|
PolicyName: "wraps-console-access-policy",
|
|
17461
19080
|
PolicyDocument: JSON.stringify(policy, null, 2)
|
|
@@ -17463,52 +19082,52 @@ Run ${pc25.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
17463
19082
|
);
|
|
17464
19083
|
});
|
|
17465
19084
|
progress.stop();
|
|
17466
|
-
|
|
19085
|
+
outro19(pc26.green("\u2713 Platform access role updated successfully"));
|
|
17467
19086
|
console.log(`
|
|
17468
|
-
${
|
|
19087
|
+
${pc26.bold("Updated Permissions:")}`);
|
|
17469
19088
|
console.log(`
|
|
17470
|
-
${
|
|
19089
|
+
${pc26.bold(pc26.cyan("Email:"))}`);
|
|
17471
19090
|
console.log(
|
|
17472
|
-
` ${
|
|
19091
|
+
` ${pc26.green("\u2713")} SES metrics and identity verification (always enabled)`
|
|
17473
19092
|
);
|
|
17474
|
-
console.log(` ${
|
|
19093
|
+
console.log(` ${pc26.green("\u2713")} SES template management (always enabled)`);
|
|
17475
19094
|
if (sendingEnabled) {
|
|
17476
|
-
console.log(` ${
|
|
19095
|
+
console.log(` ${pc26.green("\u2713")} Email sending via SES`);
|
|
17477
19096
|
}
|
|
17478
19097
|
if (eventTracking?.dynamoDBHistory) {
|
|
17479
19098
|
console.log(
|
|
17480
|
-
` ${
|
|
19099
|
+
` ${pc26.green("\u2713")} DynamoDB read access (including DescribeTable)`
|
|
17481
19100
|
);
|
|
17482
19101
|
}
|
|
17483
19102
|
if (eventTracking?.enabled) {
|
|
17484
|
-
console.log(` ${
|
|
19103
|
+
console.log(` ${pc26.green("\u2713")} EventBridge and SQS access`);
|
|
17485
19104
|
}
|
|
17486
19105
|
if (emailArchiving?.enabled) {
|
|
17487
|
-
console.log(` ${
|
|
19106
|
+
console.log(` ${pc26.green("\u2713")} Mail Manager Archive access`);
|
|
17488
19107
|
}
|
|
17489
19108
|
if (smsEnabled) {
|
|
17490
19109
|
console.log(`
|
|
17491
|
-
${
|
|
19110
|
+
${pc26.bold(pc26.cyan("SMS:"))}`);
|
|
17492
19111
|
console.log(
|
|
17493
|
-
` ${
|
|
19112
|
+
` ${pc26.green("\u2713")} SMS Voice V2 read access (phone numbers, config, registrations)`
|
|
17494
19113
|
);
|
|
17495
19114
|
if (smsSendingEnabled) {
|
|
17496
|
-
console.log(` ${
|
|
19115
|
+
console.log(` ${pc26.green("\u2713")} SMS sending via SMS Voice V2`);
|
|
17497
19116
|
}
|
|
17498
19117
|
if (smsEventTracking?.dynamoDBHistory) {
|
|
17499
|
-
console.log(` ${
|
|
19118
|
+
console.log(` ${pc26.green("\u2713")} DynamoDB read access for SMS history`);
|
|
17500
19119
|
}
|
|
17501
19120
|
if (smsEventTracking?.enabled) {
|
|
17502
|
-
console.log(` ${
|
|
19121
|
+
console.log(` ${pc26.green("\u2713")} SNS topic access for SMS events`);
|
|
17503
19122
|
}
|
|
17504
19123
|
}
|
|
17505
19124
|
console.log(
|
|
17506
19125
|
`
|
|
17507
|
-
${
|
|
19126
|
+
${pc26.dim("The Wraps Platform will now have updated permissions for feature detection.")}
|
|
17508
19127
|
`
|
|
17509
19128
|
);
|
|
17510
19129
|
}
|
|
17511
|
-
function
|
|
19130
|
+
function buildConsolePolicyDocument2(emailConfig, smsConfig) {
|
|
17512
19131
|
const statements = [];
|
|
17513
19132
|
statements.push({
|
|
17514
19133
|
Effect: "Allow",
|
|
@@ -17673,10 +19292,10 @@ function buildConsolePolicyDocument(emailConfig, smsConfig) {
|
|
|
17673
19292
|
// src/commands/shared/dashboard.ts
|
|
17674
19293
|
init_esm_shims();
|
|
17675
19294
|
import * as clack24 from "@clack/prompts";
|
|
17676
|
-
import * as
|
|
19295
|
+
import * as pulumi20 from "@pulumi/pulumi";
|
|
17677
19296
|
import getPort from "get-port";
|
|
17678
19297
|
import open from "open";
|
|
17679
|
-
import
|
|
19298
|
+
import pc27 from "picocolors";
|
|
17680
19299
|
|
|
17681
19300
|
// src/console/server.ts
|
|
17682
19301
|
init_esm_shims();
|
|
@@ -18765,13 +20384,13 @@ function createMetricsRouter(config2) {
|
|
|
18765
20384
|
const router = createRouter4();
|
|
18766
20385
|
router.get("/stream", async (req, res) => {
|
|
18767
20386
|
const connectionId = randomUUID().slice(0, 8);
|
|
18768
|
-
const
|
|
20387
|
+
const log35 = (msg, data) => {
|
|
18769
20388
|
console.log(JSON.stringify({ connectionId, msg, ...data }));
|
|
18770
20389
|
};
|
|
18771
20390
|
res.setHeader("Content-Type", "text/event-stream");
|
|
18772
20391
|
res.setHeader("Cache-Control", "no-cache");
|
|
18773
20392
|
res.setHeader("Connection", "keep-alive");
|
|
18774
|
-
|
|
20393
|
+
log35("SSE connected");
|
|
18775
20394
|
res.write('data: {"type":"connected"}\n\n');
|
|
18776
20395
|
const { startTime, endTime } = req.query;
|
|
18777
20396
|
const getTimeRange = () => ({
|
|
@@ -18781,7 +20400,7 @@ function createMetricsRouter(config2) {
|
|
|
18781
20400
|
const sendMetrics = async () => {
|
|
18782
20401
|
try {
|
|
18783
20402
|
const timeRange = getTimeRange();
|
|
18784
|
-
|
|
20403
|
+
log35("Fetching metrics", {
|
|
18785
20404
|
start: timeRange.start.toISOString(),
|
|
18786
20405
|
end: timeRange.end.toISOString()
|
|
18787
20406
|
});
|
|
@@ -18794,7 +20413,7 @@ function createMetricsRouter(config2) {
|
|
|
18794
20413
|
),
|
|
18795
20414
|
fetchSendQuota(config2.roleArn, config2.region)
|
|
18796
20415
|
]);
|
|
18797
|
-
|
|
20416
|
+
log35("Metrics fetched successfully");
|
|
18798
20417
|
const data = {
|
|
18799
20418
|
type: "metrics",
|
|
18800
20419
|
timestamp: Date.now(),
|
|
@@ -18824,7 +20443,7 @@ function createMetricsRouter(config2) {
|
|
|
18824
20443
|
const interval = setInterval(sendMetrics, 6e4);
|
|
18825
20444
|
req.on("close", () => {
|
|
18826
20445
|
clearInterval(interval);
|
|
18827
|
-
|
|
20446
|
+
log35("SSE disconnected");
|
|
18828
20447
|
});
|
|
18829
20448
|
});
|
|
18830
20449
|
router.get("/snapshot", async (_req, res) => {
|
|
@@ -20066,9 +21685,9 @@ function createUserRouter(config2) {
|
|
|
20066
21685
|
if (config2.roleArn) {
|
|
20067
21686
|
console.log("[User API] Attempting to fetch account alias via IAM");
|
|
20068
21687
|
const { assumeRole: assumeRole2 } = await Promise.resolve().then(() => (init_assume_role(), assume_role_exports));
|
|
20069
|
-
const { IAMClient:
|
|
21688
|
+
const { IAMClient: IAMClient4, ListAccountAliasesCommand } = await import("@aws-sdk/client-iam");
|
|
20070
21689
|
const credentials = await assumeRole2(config2.roleArn, region);
|
|
20071
|
-
const iamClient = new
|
|
21690
|
+
const iamClient = new IAMClient4({ region, credentials });
|
|
20072
21691
|
const response = await iamClient.send(
|
|
20073
21692
|
new ListAccountAliasesCommand({})
|
|
20074
21693
|
);
|
|
@@ -20196,7 +21815,7 @@ init_aws();
|
|
|
20196
21815
|
init_fs();
|
|
20197
21816
|
init_metadata();
|
|
20198
21817
|
async function dashboard(options) {
|
|
20199
|
-
clack24.intro(
|
|
21818
|
+
clack24.intro(pc27.bold("Wraps Dashboard"));
|
|
20200
21819
|
const progress = new DeploymentProgress();
|
|
20201
21820
|
const identity = await progress.execute(
|
|
20202
21821
|
"Validating AWS credentials",
|
|
@@ -20209,7 +21828,7 @@ async function dashboard(options) {
|
|
|
20209
21828
|
try {
|
|
20210
21829
|
await ensurePulumiWorkDir();
|
|
20211
21830
|
try {
|
|
20212
|
-
const emailStack = await
|
|
21831
|
+
const emailStack = await pulumi20.automation.LocalWorkspace.selectStack({
|
|
20213
21832
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
20214
21833
|
workDir: getPulumiWorkDir()
|
|
20215
21834
|
});
|
|
@@ -20217,7 +21836,7 @@ async function dashboard(options) {
|
|
|
20217
21836
|
} catch (_emailError) {
|
|
20218
21837
|
}
|
|
20219
21838
|
try {
|
|
20220
|
-
const smsStack = await
|
|
21839
|
+
const smsStack = await pulumi20.automation.LocalWorkspace.selectStack({
|
|
20221
21840
|
stackName: `wraps-sms-${identity.accountId}-${region}`,
|
|
20222
21841
|
workDir: getPulumiWorkDir()
|
|
20223
21842
|
});
|
|
@@ -20225,7 +21844,7 @@ async function dashboard(options) {
|
|
|
20225
21844
|
} catch (_smsError) {
|
|
20226
21845
|
}
|
|
20227
21846
|
try {
|
|
20228
|
-
const cdnStack = await
|
|
21847
|
+
const cdnStack = await pulumi20.automation.LocalWorkspace.selectStack({
|
|
20229
21848
|
stackName: `wraps-cdn-${identity.accountId}-${region}`,
|
|
20230
21849
|
workDir: getPulumiWorkDir()
|
|
20231
21850
|
});
|
|
@@ -20239,7 +21858,7 @@ async function dashboard(options) {
|
|
|
20239
21858
|
progress.stop();
|
|
20240
21859
|
clack24.log.error("No Wraps infrastructure found");
|
|
20241
21860
|
console.log(
|
|
20242
|
-
`\\nRun ${
|
|
21861
|
+
`\\nRun ${pc27.cyan("wraps email init")}, ${pc27.cyan("wraps sms init")}, or ${pc27.cyan("wraps storage init")} to deploy infrastructure first.\\n`
|
|
20243
21862
|
);
|
|
20244
21863
|
process.exit(1);
|
|
20245
21864
|
}
|
|
@@ -20282,7 +21901,7 @@ async function dashboard(options) {
|
|
|
20282
21901
|
progress.stop();
|
|
20283
21902
|
clack24.log.success("Starting dashboard server...");
|
|
20284
21903
|
console.log(
|
|
20285
|
-
`${
|
|
21904
|
+
`${pc27.dim("Using current AWS credentials (no role assumption)")}\\n`
|
|
20286
21905
|
);
|
|
20287
21906
|
const { url } = await startConsoleServer({
|
|
20288
21907
|
port,
|
|
@@ -20313,8 +21932,8 @@ async function dashboard(options) {
|
|
|
20313
21932
|
cdnCustomDomain,
|
|
20314
21933
|
cdnCertificateArn
|
|
20315
21934
|
});
|
|
20316
|
-
console.log(`\\n${
|
|
20317
|
-
console.log(`${
|
|
21935
|
+
console.log(`\\n${pc27.bold("Dashboard:")} ${pc27.cyan(url)}`);
|
|
21936
|
+
console.log(`${pc27.dim("Press Ctrl+C to stop")}\\n`);
|
|
20318
21937
|
getTelemetryClient().showFooterOnce();
|
|
20319
21938
|
if (!options.noOpen) {
|
|
20320
21939
|
await open(url);
|
|
@@ -20333,9 +21952,9 @@ init_esm_shims();
|
|
|
20333
21952
|
init_aws();
|
|
20334
21953
|
init_metadata();
|
|
20335
21954
|
import * as clack25 from "@clack/prompts";
|
|
20336
|
-
import
|
|
21955
|
+
import pc28 from "picocolors";
|
|
20337
21956
|
async function destroy(options) {
|
|
20338
|
-
clack25.intro(
|
|
21957
|
+
clack25.intro(pc28.bold("Wraps Infrastructure Teardown"));
|
|
20339
21958
|
const spinner6 = clack25.spinner();
|
|
20340
21959
|
spinner6.start("Validating AWS credentials");
|
|
20341
21960
|
let identity;
|
|
@@ -20356,14 +21975,14 @@ async function destroy(options) {
|
|
|
20356
21975
|
clack25.log.warn("No Wraps services found in this region");
|
|
20357
21976
|
console.log(
|
|
20358
21977
|
`
|
|
20359
|
-
Run ${
|
|
21978
|
+
Run ${pc28.cyan("wraps email init")} to deploy infrastructure.
|
|
20360
21979
|
`
|
|
20361
21980
|
);
|
|
20362
21981
|
process.exit(0);
|
|
20363
21982
|
}
|
|
20364
21983
|
if (deployedServices.length === 1) {
|
|
20365
21984
|
const service = deployedServices[0];
|
|
20366
|
-
clack25.log.info(`Found ${
|
|
21985
|
+
clack25.log.info(`Found ${pc28.cyan(service)} service deployed`);
|
|
20367
21986
|
if (service === "email") {
|
|
20368
21987
|
await emailDestroy(options);
|
|
20369
21988
|
return;
|
|
@@ -20392,7 +22011,7 @@ Run ${pc27.cyan("wraps email init")} to deploy infrastructure.
|
|
|
20392
22011
|
await emailDestroy(options);
|
|
20393
22012
|
}
|
|
20394
22013
|
if (serviceToDestroy === "all") {
|
|
20395
|
-
clack25.outro(
|
|
22014
|
+
clack25.outro(pc28.green("All Wraps infrastructure has been removed"));
|
|
20396
22015
|
}
|
|
20397
22016
|
}
|
|
20398
22017
|
|
|
@@ -20402,23 +22021,23 @@ init_events();
|
|
|
20402
22021
|
init_aws();
|
|
20403
22022
|
init_fs();
|
|
20404
22023
|
import * as clack26 from "@clack/prompts";
|
|
20405
|
-
import * as
|
|
20406
|
-
import
|
|
22024
|
+
import * as pulumi21 from "@pulumi/pulumi";
|
|
22025
|
+
import pc29 from "picocolors";
|
|
20407
22026
|
async function status(_options) {
|
|
20408
22027
|
const startTime = Date.now();
|
|
20409
22028
|
const progress = new DeploymentProgress();
|
|
20410
|
-
clack26.intro(
|
|
22029
|
+
clack26.intro(pc29.bold("Wraps Infrastructure Status"));
|
|
20411
22030
|
const identity = await progress.execute(
|
|
20412
22031
|
"Loading infrastructure status",
|
|
20413
22032
|
async () => validateAWSCredentials()
|
|
20414
22033
|
);
|
|
20415
|
-
progress.info(`AWS Account: ${
|
|
22034
|
+
progress.info(`AWS Account: ${pc29.cyan(identity.accountId)}`);
|
|
20416
22035
|
const region = await getAWSRegion();
|
|
20417
|
-
progress.info(`Region: ${
|
|
22036
|
+
progress.info(`Region: ${pc29.cyan(region)}`);
|
|
20418
22037
|
const services = [];
|
|
20419
22038
|
try {
|
|
20420
22039
|
await ensurePulumiWorkDir();
|
|
20421
|
-
const emailStack = await
|
|
22040
|
+
const emailStack = await pulumi21.automation.LocalWorkspace.selectStack({
|
|
20422
22041
|
stackName: `wraps-${identity.accountId}-${region}`,
|
|
20423
22042
|
workDir: getPulumiWorkDir()
|
|
20424
22043
|
});
|
|
@@ -20437,7 +22056,7 @@ async function status(_options) {
|
|
|
20437
22056
|
services.push({ name: "Email", status: "not_deployed" });
|
|
20438
22057
|
}
|
|
20439
22058
|
try {
|
|
20440
|
-
const smsStack = await
|
|
22059
|
+
const smsStack = await pulumi21.automation.LocalWorkspace.selectStack({
|
|
20441
22060
|
stackName: `wraps-sms-${identity.accountId}-${region}`,
|
|
20442
22061
|
workDir: getPulumiWorkDir()
|
|
20443
22062
|
});
|
|
@@ -20460,32 +22079,32 @@ async function status(_options) {
|
|
|
20460
22079
|
clack26.note(
|
|
20461
22080
|
services.map((s) => {
|
|
20462
22081
|
if (s.status === "deployed") {
|
|
20463
|
-
const details = s.details ?
|
|
20464
|
-
return ` ${
|
|
22082
|
+
const details = s.details ? pc29.dim(` (${s.details})`) : "";
|
|
22083
|
+
return ` ${pc29.green("\u2713")} ${s.name}${details}`;
|
|
20465
22084
|
}
|
|
20466
|
-
return ` ${
|
|
22085
|
+
return ` ${pc29.dim("\u25CB")} ${s.name} ${pc29.dim("(not deployed)")}`;
|
|
20467
22086
|
}).join("\n"),
|
|
20468
22087
|
"Services"
|
|
20469
22088
|
);
|
|
20470
22089
|
const hasDeployedServices = services.some((s) => s.status === "deployed");
|
|
20471
22090
|
if (hasDeployedServices) {
|
|
20472
22091
|
console.log(`
|
|
20473
|
-
${
|
|
22092
|
+
${pc29.bold("Details:")}`);
|
|
20474
22093
|
if (services.find((s) => s.name === "Email")?.status === "deployed") {
|
|
20475
|
-
console.log(` ${
|
|
22094
|
+
console.log(` ${pc29.dim("Email:")} ${pc29.cyan("wraps email status")}`);
|
|
20476
22095
|
}
|
|
20477
22096
|
if (services.find((s) => s.name === "SMS")?.status === "deployed") {
|
|
20478
|
-
console.log(` ${
|
|
22097
|
+
console.log(` ${pc29.dim("SMS:")} ${pc29.cyan("wraps sms status")}`);
|
|
20479
22098
|
}
|
|
20480
22099
|
} else {
|
|
20481
22100
|
console.log(`
|
|
20482
|
-
${
|
|
20483
|
-
console.log(` ${
|
|
20484
|
-
console.log(` ${
|
|
22101
|
+
${pc29.bold("Get started:")}`);
|
|
22102
|
+
console.log(` ${pc29.dim("Deploy email:")} ${pc29.cyan("wraps email init")}`);
|
|
22103
|
+
console.log(` ${pc29.dim("Deploy SMS:")} ${pc29.cyan("wraps sms init")}`);
|
|
20485
22104
|
}
|
|
20486
22105
|
console.log(`
|
|
20487
|
-
${
|
|
20488
|
-
console.log(`${
|
|
22106
|
+
${pc29.bold("Dashboard:")} ${pc29.blue("https://app.wraps.dev")}`);
|
|
22107
|
+
console.log(`${pc29.bold("Docs:")} ${pc29.blue("https://wraps.dev/docs")}
|
|
20489
22108
|
`);
|
|
20490
22109
|
trackCommand("status", {
|
|
20491
22110
|
success: true,
|
|
@@ -20497,20 +22116,20 @@ ${pc28.bold("Dashboard:")} ${pc28.blue("https://app.wraps.dev")}`);
|
|
|
20497
22116
|
// src/commands/sms/destroy.ts
|
|
20498
22117
|
init_esm_shims();
|
|
20499
22118
|
import * as clack27 from "@clack/prompts";
|
|
20500
|
-
import * as
|
|
20501
|
-
import
|
|
22119
|
+
import * as pulumi23 from "@pulumi/pulumi";
|
|
22120
|
+
import pc30 from "picocolors";
|
|
20502
22121
|
|
|
20503
22122
|
// src/infrastructure/sms-stack.ts
|
|
20504
22123
|
init_esm_shims();
|
|
20505
22124
|
import * as aws15 from "@pulumi/aws";
|
|
20506
|
-
import * as
|
|
22125
|
+
import * as pulumi22 from "@pulumi/pulumi";
|
|
20507
22126
|
async function roleExists3(roleName) {
|
|
20508
22127
|
try {
|
|
20509
|
-
const { IAMClient:
|
|
20510
|
-
const iam8 = new
|
|
22128
|
+
const { IAMClient: IAMClient4, GetRoleCommand: GetRoleCommand3 } = await import("@aws-sdk/client-iam");
|
|
22129
|
+
const iam8 = new IAMClient4({
|
|
20511
22130
|
region: process.env.AWS_REGION || "us-east-1"
|
|
20512
22131
|
});
|
|
20513
|
-
await iam8.send(new
|
|
22132
|
+
await iam8.send(new GetRoleCommand3({ RoleName: roleName }));
|
|
20514
22133
|
return true;
|
|
20515
22134
|
} catch (error) {
|
|
20516
22135
|
if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity") {
|
|
@@ -20537,7 +22156,7 @@ async function tableExists2(tableName) {
|
|
|
20537
22156
|
async function createSMSIAMRole(config2) {
|
|
20538
22157
|
let assumeRolePolicy;
|
|
20539
22158
|
if (config2.provider === "vercel" && config2.oidcProvider) {
|
|
20540
|
-
assumeRolePolicy =
|
|
22159
|
+
assumeRolePolicy = pulumi22.interpolate`{
|
|
20541
22160
|
"Version": "2012-10-17",
|
|
20542
22161
|
"Statement": [
|
|
20543
22162
|
{
|
|
@@ -20565,7 +22184,7 @@ async function createSMSIAMRole(config2) {
|
|
|
20565
22184
|
]
|
|
20566
22185
|
}`;
|
|
20567
22186
|
} else if (config2.provider === "aws") {
|
|
20568
|
-
assumeRolePolicy =
|
|
22187
|
+
assumeRolePolicy = pulumi22.output(`{
|
|
20569
22188
|
"Version": "2012-10-17",
|
|
20570
22189
|
"Statement": [{
|
|
20571
22190
|
"Effect": "Allow",
|
|
@@ -20920,7 +22539,7 @@ async function createSMSSNSResources(config2) {
|
|
|
20920
22539
|
});
|
|
20921
22540
|
new aws15.sqs.QueuePolicy("wraps-sms-events-queue-policy", {
|
|
20922
22541
|
queueUrl: config2.queueUrl,
|
|
20923
|
-
policy:
|
|
22542
|
+
policy: pulumi22.all([config2.queueArn, topic.arn]).apply(
|
|
20924
22543
|
([queueArn, topicArn2]) => JSON.stringify({
|
|
20925
22544
|
Version: "2012-10-17",
|
|
20926
22545
|
Statement: [
|
|
@@ -21019,7 +22638,7 @@ async function deploySMSLambdaFunction(config2) {
|
|
|
21019
22638
|
});
|
|
21020
22639
|
new aws15.iam.RolePolicy("wraps-sms-lambda-policy", {
|
|
21021
22640
|
role: lambdaRole.name,
|
|
21022
|
-
policy:
|
|
22641
|
+
policy: pulumi22.all([config2.tableName, config2.queueArn]).apply(
|
|
21023
22642
|
([tableName, queueArn]) => JSON.stringify({
|
|
21024
22643
|
Version: "2012-10-17",
|
|
21025
22644
|
Statement: [
|
|
@@ -21056,7 +22675,7 @@ async function deploySMSLambdaFunction(config2) {
|
|
|
21056
22675
|
runtime: "nodejs20.x",
|
|
21057
22676
|
handler: "index.handler",
|
|
21058
22677
|
role: lambdaRole.arn,
|
|
21059
|
-
code: new
|
|
22678
|
+
code: new pulumi22.asset.FileArchive(codeDir),
|
|
21060
22679
|
timeout: 300,
|
|
21061
22680
|
// 5 minutes
|
|
21062
22681
|
memorySize: 512,
|
|
@@ -21296,18 +22915,18 @@ async function createSMSProtectConfigurationWithSDK(configurationSetName, region
|
|
|
21296
22915
|
const existing = await client.send(
|
|
21297
22916
|
new DescribeProtectConfigurationsCommand({})
|
|
21298
22917
|
);
|
|
21299
|
-
for (const
|
|
21300
|
-
if (!(
|
|
22918
|
+
for (const pc41 of existing.ProtectConfigurations || []) {
|
|
22919
|
+
if (!(pc41.ProtectConfigurationArn && pc41.ProtectConfigurationId)) {
|
|
21301
22920
|
continue;
|
|
21302
22921
|
}
|
|
21303
22922
|
const tagsResponse = await client.send(
|
|
21304
22923
|
new ListTagsForResourceCommand({
|
|
21305
|
-
ResourceArn:
|
|
22924
|
+
ResourceArn: pc41.ProtectConfigurationArn
|
|
21306
22925
|
})
|
|
21307
22926
|
);
|
|
21308
22927
|
const nameTag = tagsResponse.Tags?.find((t) => t.Key === "Name");
|
|
21309
22928
|
if (nameTag?.Value === protectConfigName) {
|
|
21310
|
-
existingProtectConfigId =
|
|
22929
|
+
existingProtectConfigId = pc41.ProtectConfigurationId;
|
|
21311
22930
|
break;
|
|
21312
22931
|
}
|
|
21313
22932
|
}
|
|
@@ -21394,13 +23013,13 @@ async function deleteSMSProtectConfigurationWithSDK(region) {
|
|
|
21394
23013
|
new DescribeProtectConfigurationsCommand({})
|
|
21395
23014
|
);
|
|
21396
23015
|
if (existing.ProtectConfigurations) {
|
|
21397
|
-
for (const
|
|
21398
|
-
if (!(
|
|
23016
|
+
for (const pc41 of existing.ProtectConfigurations) {
|
|
23017
|
+
if (!(pc41.ProtectConfigurationArn && pc41.ProtectConfigurationId)) {
|
|
21399
23018
|
continue;
|
|
21400
23019
|
}
|
|
21401
23020
|
const tagsResponse = await client.send(
|
|
21402
23021
|
new ListTagsForResourceCommand({
|
|
21403
|
-
ResourceArn:
|
|
23022
|
+
ResourceArn: pc41.ProtectConfigurationArn
|
|
21404
23023
|
})
|
|
21405
23024
|
);
|
|
21406
23025
|
const isWrapsManaged = tagsResponse.Tags?.some(
|
|
@@ -21409,7 +23028,7 @@ async function deleteSMSProtectConfigurationWithSDK(region) {
|
|
|
21409
23028
|
if (isWrapsManaged) {
|
|
21410
23029
|
await client.send(
|
|
21411
23030
|
new DeleteProtectConfigurationCommand({
|
|
21412
|
-
ProtectConfigurationId:
|
|
23031
|
+
ProtectConfigurationId: pc41.ProtectConfigurationId
|
|
21413
23032
|
})
|
|
21414
23033
|
);
|
|
21415
23034
|
}
|
|
@@ -21428,7 +23047,7 @@ init_metadata();
|
|
|
21428
23047
|
async function smsDestroy(options) {
|
|
21429
23048
|
const startTime = Date.now();
|
|
21430
23049
|
clack27.intro(
|
|
21431
|
-
|
|
23050
|
+
pc30.bold(
|
|
21432
23051
|
options.preview ? "SMS Infrastructure Destruction Preview" : "SMS Infrastructure Teardown"
|
|
21433
23052
|
)
|
|
21434
23053
|
);
|
|
@@ -21448,7 +23067,7 @@ async function smsDestroy(options) {
|
|
|
21448
23067
|
}
|
|
21449
23068
|
if (!(options.force || options.preview)) {
|
|
21450
23069
|
const confirmed = await clack27.confirm({
|
|
21451
|
-
message:
|
|
23070
|
+
message: pc30.red(
|
|
21452
23071
|
"Are you sure you want to destroy all SMS infrastructure?"
|
|
21453
23072
|
),
|
|
21454
23073
|
initialValue: false
|
|
@@ -21467,7 +23086,7 @@ async function smsDestroy(options) {
|
|
|
21467
23086
|
const stackName = storedStackName || `wraps-sms-${identity.accountId}-${region}`;
|
|
21468
23087
|
let stack;
|
|
21469
23088
|
try {
|
|
21470
|
-
stack = await
|
|
23089
|
+
stack = await pulumi23.automation.LocalWorkspace.selectStack({
|
|
21471
23090
|
stackName,
|
|
21472
23091
|
workDir: getPulumiWorkDir()
|
|
21473
23092
|
});
|
|
@@ -21487,7 +23106,7 @@ async function smsDestroy(options) {
|
|
|
21487
23106
|
commandName: "wraps sms destroy"
|
|
21488
23107
|
});
|
|
21489
23108
|
clack27.outro(
|
|
21490
|
-
|
|
23109
|
+
pc30.green("Preview complete. Run without --preview to destroy.")
|
|
21491
23110
|
);
|
|
21492
23111
|
trackServiceRemoved("sms", {
|
|
21493
23112
|
preview: true,
|
|
@@ -21524,7 +23143,7 @@ async function smsDestroy(options) {
|
|
|
21524
23143
|
const stackName = storedStackName || `wraps-sms-${identity.accountId}-${region}`;
|
|
21525
23144
|
let stack;
|
|
21526
23145
|
try {
|
|
21527
|
-
stack = await
|
|
23146
|
+
stack = await pulumi23.automation.LocalWorkspace.selectStack({
|
|
21528
23147
|
stackName,
|
|
21529
23148
|
workDir: getPulumiWorkDir()
|
|
21530
23149
|
});
|
|
@@ -21560,16 +23179,16 @@ async function smsDestroy(options) {
|
|
|
21560
23179
|
await saveConnectionMetadata(metadata);
|
|
21561
23180
|
}
|
|
21562
23181
|
progress.stop();
|
|
21563
|
-
clack27.outro(
|
|
23182
|
+
clack27.outro(pc30.green("SMS infrastructure has been removed"));
|
|
21564
23183
|
console.log(`
|
|
21565
|
-
${
|
|
21566
|
-
console.log(` ${
|
|
21567
|
-
console.log(` ${
|
|
21568
|
-
console.log(` ${
|
|
21569
|
-
console.log(` ${
|
|
23184
|
+
${pc30.bold("Cleaned up:")}`);
|
|
23185
|
+
console.log(` ${pc30.green("\u2713")} Phone number released`);
|
|
23186
|
+
console.log(` ${pc30.green("\u2713")} Configuration set deleted`);
|
|
23187
|
+
console.log(` ${pc30.green("\u2713")} Event processing infrastructure removed`);
|
|
23188
|
+
console.log(` ${pc30.green("\u2713")} IAM role deleted`);
|
|
21570
23189
|
console.log(
|
|
21571
23190
|
`
|
|
21572
|
-
Run ${
|
|
23191
|
+
Run ${pc30.cyan("wraps sms init")} to deploy infrastructure again.
|
|
21573
23192
|
`
|
|
21574
23193
|
);
|
|
21575
23194
|
trackServiceRemoved("sms", {
|
|
@@ -21581,8 +23200,8 @@ Run ${pc29.cyan("wraps sms init")} to deploy infrastructure again.
|
|
|
21581
23200
|
// src/commands/sms/init.ts
|
|
21582
23201
|
init_esm_shims();
|
|
21583
23202
|
import * as clack28 from "@clack/prompts";
|
|
21584
|
-
import * as
|
|
21585
|
-
import
|
|
23203
|
+
import * as pulumi24 from "@pulumi/pulumi";
|
|
23204
|
+
import pc31 from "picocolors";
|
|
21586
23205
|
init_events();
|
|
21587
23206
|
init_aws();
|
|
21588
23207
|
init_errors();
|
|
@@ -22106,7 +23725,7 @@ async function promptEstimatedSMSVolume() {
|
|
|
22106
23725
|
}
|
|
22107
23726
|
async function init3(options) {
|
|
22108
23727
|
const startTime = Date.now();
|
|
22109
|
-
clack28.intro(
|
|
23728
|
+
clack28.intro(pc31.bold("Wraps SMS Infrastructure Setup"));
|
|
22110
23729
|
const progress = new DeploymentProgress();
|
|
22111
23730
|
const wasAutoInstalled = await progress.execute(
|
|
22112
23731
|
"Checking Pulumi CLI installation",
|
|
@@ -22119,7 +23738,7 @@ async function init3(options) {
|
|
|
22119
23738
|
"Validating AWS credentials",
|
|
22120
23739
|
async () => validateAWSCredentials()
|
|
22121
23740
|
);
|
|
22122
|
-
progress.info(`Connected to AWS account: ${
|
|
23741
|
+
progress.info(`Connected to AWS account: ${pc31.cyan(identity.accountId)}`);
|
|
22123
23742
|
const provider = options.provider || await promptProvider();
|
|
22124
23743
|
const region = options.region || await promptRegion(await getAWSRegion());
|
|
22125
23744
|
let vercelConfig;
|
|
@@ -22132,9 +23751,9 @@ async function init3(options) {
|
|
|
22132
23751
|
);
|
|
22133
23752
|
if (existingConnection?.services?.sms) {
|
|
22134
23753
|
clack28.log.warn(
|
|
22135
|
-
`SMS already configured for account ${
|
|
23754
|
+
`SMS already configured for account ${pc31.cyan(identity.accountId)} in region ${pc31.cyan(region)}`
|
|
22136
23755
|
);
|
|
22137
|
-
clack28.log.info(`Use ${
|
|
23756
|
+
clack28.log.info(`Use ${pc31.cyan("wraps sms status")} to view current setup`);
|
|
22138
23757
|
process.exit(0);
|
|
22139
23758
|
}
|
|
22140
23759
|
let preset = options.preset;
|
|
@@ -22176,7 +23795,7 @@ async function init3(options) {
|
|
|
22176
23795
|
}
|
|
22177
23796
|
progress.info(
|
|
22178
23797
|
`
|
|
22179
|
-
${
|
|
23798
|
+
${pc31.bold("Fraud Protection")} - Block SMS to countries where you don't do business`
|
|
22180
23799
|
);
|
|
22181
23800
|
const allowedCountries = await promptAllowedCountries();
|
|
22182
23801
|
let aitFiltering = false;
|
|
@@ -22198,13 +23817,13 @@ ${pc30.bold("Fraud Protection")} - Block SMS to countries where you don't do bus
|
|
|
22198
23817
|
};
|
|
22199
23818
|
const estimatedVolume = await promptEstimatedSMSVolume();
|
|
22200
23819
|
progress.info(`
|
|
22201
|
-
${
|
|
23820
|
+
${pc31.bold("Cost Estimate:")}`);
|
|
22202
23821
|
const costSummary = getSMSCostSummary(smsConfig, estimatedVolume);
|
|
22203
23822
|
clack28.log.info(costSummary);
|
|
22204
23823
|
const warnings = validateSMSConfig(smsConfig);
|
|
22205
23824
|
if (warnings.length > 0) {
|
|
22206
23825
|
progress.info(`
|
|
22207
|
-
${
|
|
23826
|
+
${pc31.yellow(pc31.bold("Important Notes:"))}`);
|
|
22208
23827
|
for (const warning of warnings) {
|
|
22209
23828
|
clack28.log.warn(warning);
|
|
22210
23829
|
}
|
|
@@ -22228,7 +23847,7 @@ ${pc30.yellow(pc30.bold("Important Notes:"))}`);
|
|
|
22228
23847
|
"Deploying SMS infrastructure (this may take 2-3 minutes)",
|
|
22229
23848
|
async () => {
|
|
22230
23849
|
await ensurePulumiWorkDir();
|
|
22231
|
-
const stack = await
|
|
23850
|
+
const stack = await pulumi24.automation.LocalWorkspace.createOrSelectStack(
|
|
22232
23851
|
{
|
|
22233
23852
|
stackName: `wraps-sms-${identity.accountId}-${region}`,
|
|
22234
23853
|
projectName: "wraps-sms",
|
|
@@ -22334,47 +23953,47 @@ ${pc30.yellow(pc30.bold("Important Notes:"))}`);
|
|
|
22334
23953
|
await saveConnectionMetadata(metadata);
|
|
22335
23954
|
progress.info("Connection metadata saved");
|
|
22336
23955
|
console.log("\n");
|
|
22337
|
-
clack28.log.success(
|
|
23956
|
+
clack28.log.success(pc31.green(pc31.bold("SMS infrastructure deployed!")));
|
|
22338
23957
|
console.log("\n");
|
|
22339
23958
|
clack28.note(
|
|
22340
23959
|
[
|
|
22341
|
-
`${
|
|
22342
|
-
`${
|
|
22343
|
-
`${
|
|
22344
|
-
`${
|
|
22345
|
-
outputs.tableName ? `${
|
|
23960
|
+
`${pc31.bold("Phone Number:")} ${pc31.cyan(outputs.phoneNumber || "Provisioning...")}`,
|
|
23961
|
+
`${pc31.bold("Phone Type:")} ${pc31.cyan(smsConfig.phoneNumberType || "simulator")}`,
|
|
23962
|
+
`${pc31.bold("Config Set:")} ${pc31.cyan(outputs.configSetName || "wraps-sms-config")}`,
|
|
23963
|
+
`${pc31.bold("Region:")} ${pc31.cyan(outputs.region)}`,
|
|
23964
|
+
outputs.tableName ? `${pc31.bold("History Table:")} ${pc31.cyan(outputs.tableName)}` : "",
|
|
22346
23965
|
"",
|
|
22347
|
-
|
|
22348
|
-
|
|
23966
|
+
pc31.dim("IAM Role:"),
|
|
23967
|
+
pc31.dim(` ${outputs.roleArn}`)
|
|
22349
23968
|
].filter(Boolean).join("\n"),
|
|
22350
23969
|
"SMS Infrastructure"
|
|
22351
23970
|
);
|
|
22352
23971
|
const nextSteps = [];
|
|
22353
23972
|
if (smsConfig.phoneNumberType === "toll-free") {
|
|
22354
23973
|
nextSteps.push(
|
|
22355
|
-
`${
|
|
23974
|
+
`${pc31.cyan("wraps sms register")} - Submit toll-free registration (required before sending)`
|
|
22356
23975
|
);
|
|
22357
23976
|
}
|
|
22358
23977
|
nextSteps.push(
|
|
22359
|
-
`${
|
|
23978
|
+
`${pc31.cyan("wraps sms test --to +1234567890")} - Send a test message`
|
|
22360
23979
|
);
|
|
22361
|
-
nextSteps.push(`${
|
|
23980
|
+
nextSteps.push(`${pc31.cyan("wraps sms status")} - View SMS configuration`);
|
|
22362
23981
|
console.log("\n");
|
|
22363
|
-
clack28.log.info(
|
|
23982
|
+
clack28.log.info(pc31.bold("Next steps:"));
|
|
22364
23983
|
for (const step of nextSteps) {
|
|
22365
23984
|
console.log(` ${step}`);
|
|
22366
23985
|
}
|
|
22367
23986
|
console.log("\n");
|
|
22368
|
-
clack28.log.info(
|
|
22369
|
-
console.log(
|
|
23987
|
+
clack28.log.info(pc31.bold("SDK Usage:"));
|
|
23988
|
+
console.log(pc31.dim(" npm install @wraps.dev/sms"));
|
|
22370
23989
|
console.log("");
|
|
22371
|
-
console.log(
|
|
22372
|
-
console.log(
|
|
22373
|
-
console.log(
|
|
22374
|
-
console.log(
|
|
22375
|
-
console.log(
|
|
22376
|
-
console.log(
|
|
22377
|
-
clack28.outro(
|
|
23990
|
+
console.log(pc31.dim(" import { Wraps } from '@wraps.dev/sms';"));
|
|
23991
|
+
console.log(pc31.dim(" const wraps = new Wraps();"));
|
|
23992
|
+
console.log(pc31.dim(" await wraps.sms.send({"));
|
|
23993
|
+
console.log(pc31.dim(" to: '+14155551234',"));
|
|
23994
|
+
console.log(pc31.dim(" message: 'Your code is 123456',"));
|
|
23995
|
+
console.log(pc31.dim(" });"));
|
|
23996
|
+
clack28.outro(pc31.green("Setup complete!"));
|
|
22378
23997
|
const duration = Date.now() - startTime;
|
|
22379
23998
|
const enabledFeatures = [];
|
|
22380
23999
|
if (smsConfig.tracking?.enabled) {
|
|
@@ -22408,7 +24027,7 @@ init_esm_shims();
|
|
|
22408
24027
|
init_aws();
|
|
22409
24028
|
init_metadata();
|
|
22410
24029
|
import * as clack29 from "@clack/prompts";
|
|
22411
|
-
import
|
|
24030
|
+
import pc32 from "picocolors";
|
|
22412
24031
|
async function getPhoneNumberDetails(region) {
|
|
22413
24032
|
const { PinpointSMSVoiceV2Client: PinpointSMSVoiceV2Client5, DescribePhoneNumbersCommand: DescribePhoneNumbersCommand2 } = await import("@aws-sdk/client-pinpoint-sms-voice-v2");
|
|
22414
24033
|
const client = new PinpointSMSVoiceV2Client5({ region });
|
|
@@ -22446,7 +24065,7 @@ async function getRegistrationStatus(region, registrationId) {
|
|
|
22446
24065
|
}
|
|
22447
24066
|
}
|
|
22448
24067
|
async function smsRegister(options) {
|
|
22449
|
-
clack29.intro(
|
|
24068
|
+
clack29.intro(pc32.bold("Wraps SMS - Toll-Free Registration"));
|
|
22450
24069
|
const progress = new DeploymentProgress();
|
|
22451
24070
|
const identity = await progress.execute(
|
|
22452
24071
|
"Validating AWS credentials",
|
|
@@ -22459,7 +24078,7 @@ async function smsRegister(options) {
|
|
|
22459
24078
|
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
22460
24079
|
if (!metadata?.services?.sms) {
|
|
22461
24080
|
clack29.log.error("No SMS infrastructure found.");
|
|
22462
|
-
clack29.log.info(`Run ${
|
|
24081
|
+
clack29.log.info(`Run ${pc32.cyan("wraps sms init")} first.`);
|
|
22463
24082
|
process.exit(1);
|
|
22464
24083
|
}
|
|
22465
24084
|
const phoneDetails = await progress.execute(
|
|
@@ -22472,11 +24091,11 @@ async function smsRegister(options) {
|
|
|
22472
24091
|
}
|
|
22473
24092
|
console.log("");
|
|
22474
24093
|
console.log(
|
|
22475
|
-
`${
|
|
24094
|
+
`${pc32.bold("Phone Number:")} ${pc32.cyan(phoneDetails.phoneNumber)}`
|
|
22476
24095
|
);
|
|
22477
|
-
console.log(`${
|
|
24096
|
+
console.log(`${pc32.bold("Type:")} ${pc32.cyan(phoneDetails.type)}`);
|
|
22478
24097
|
console.log(
|
|
22479
|
-
`${
|
|
24098
|
+
`${pc32.bold("Status:")} ${phoneDetails.status === "ACTIVE" ? pc32.green(phoneDetails.status) : pc32.yellow(phoneDetails.status)}`
|
|
22480
24099
|
);
|
|
22481
24100
|
if (phoneDetails.registrationId) {
|
|
22482
24101
|
const regStatus = await getRegistrationStatus(
|
|
@@ -22484,7 +24103,7 @@ async function smsRegister(options) {
|
|
|
22484
24103
|
phoneDetails.registrationId
|
|
22485
24104
|
);
|
|
22486
24105
|
if (regStatus) {
|
|
22487
|
-
console.log(`${
|
|
24106
|
+
console.log(`${pc32.bold("Registration:")} ${pc32.cyan(regStatus)}`);
|
|
22488
24107
|
}
|
|
22489
24108
|
}
|
|
22490
24109
|
console.log("");
|
|
@@ -22503,21 +24122,21 @@ async function smsRegister(options) {
|
|
|
22503
24122
|
clack29.log.info(`Your ${phoneDetails.type} number should be ready to use.`);
|
|
22504
24123
|
process.exit(0);
|
|
22505
24124
|
}
|
|
22506
|
-
console.log(
|
|
24125
|
+
console.log(pc32.bold("Toll-Free Registration Required"));
|
|
22507
24126
|
console.log("");
|
|
22508
24127
|
console.log(
|
|
22509
|
-
|
|
24128
|
+
pc32.dim("To send SMS at scale, you must register your toll-free number.")
|
|
22510
24129
|
);
|
|
22511
|
-
console.log(
|
|
24130
|
+
console.log(pc32.dim("This process typically takes 1-15 business days."));
|
|
22512
24131
|
console.log("");
|
|
22513
|
-
console.log(
|
|
22514
|
-
console.log(` ${
|
|
24132
|
+
console.log(pc32.bold("You'll need to provide:"));
|
|
24133
|
+
console.log(` ${pc32.dim("\u2022")} Business name and address`);
|
|
22515
24134
|
console.log(
|
|
22516
|
-
` ${
|
|
24135
|
+
` ${pc32.dim("\u2022")} Use case description (what messages you're sending)`
|
|
22517
24136
|
);
|
|
22518
|
-
console.log(` ${
|
|
22519
|
-
console.log(` ${
|
|
22520
|
-
console.log(` ${
|
|
24137
|
+
console.log(` ${pc32.dim("\u2022")} Sample messages (2-3 examples)`);
|
|
24138
|
+
console.log(` ${pc32.dim("\u2022")} How users opt-in to receive messages`);
|
|
24139
|
+
console.log(` ${pc32.dim("\u2022")} Expected monthly message volume`);
|
|
22521
24140
|
console.log("");
|
|
22522
24141
|
const openConsole = await clack29.confirm({
|
|
22523
24142
|
message: "Open AWS Console to start registration?",
|
|
@@ -22542,29 +24161,29 @@ async function smsRegister(options) {
|
|
|
22542
24161
|
} catch {
|
|
22543
24162
|
clack29.log.info("Open this URL in your browser:");
|
|
22544
24163
|
console.log(`
|
|
22545
|
-
${
|
|
24164
|
+
${pc32.cyan(consoleUrl)}
|
|
22546
24165
|
`);
|
|
22547
24166
|
}
|
|
22548
24167
|
}
|
|
22549
24168
|
console.log("");
|
|
22550
|
-
console.log(
|
|
24169
|
+
console.log(pc32.bold("Next Steps:"));
|
|
22551
24170
|
console.log(
|
|
22552
|
-
` 1. Click ${
|
|
24171
|
+
` 1. Click ${pc32.cyan("Create registration")} in the AWS Console`
|
|
22553
24172
|
);
|
|
22554
|
-
console.log(` 2. Select ${
|
|
24173
|
+
console.log(` 2. Select ${pc32.cyan("Toll-free number registration")}`);
|
|
22555
24174
|
console.log(" 3. Fill out the business information form");
|
|
22556
24175
|
console.log(" 4. Submit and wait for approval (1-15 business days)");
|
|
22557
24176
|
console.log("");
|
|
22558
24177
|
console.log(
|
|
22559
|
-
|
|
24178
|
+
pc32.dim("Once approved, run `wraps sms sync` to complete setup.")
|
|
22560
24179
|
);
|
|
22561
24180
|
} else {
|
|
22562
24181
|
const consoleUrl = `https://${region}.console.aws.amazon.com/sms-voice/home?region=${region}#/registrations`;
|
|
22563
24182
|
console.log("");
|
|
22564
24183
|
console.log("When you're ready, go to:");
|
|
22565
|
-
console.log(` ${
|
|
24184
|
+
console.log(` ${pc32.cyan(consoleUrl)}`);
|
|
22566
24185
|
}
|
|
22567
|
-
clack29.outro(
|
|
24186
|
+
clack29.outro(pc32.dim("Good luck with your registration!"));
|
|
22568
24187
|
}
|
|
22569
24188
|
|
|
22570
24189
|
// src/commands/sms/status.ts
|
|
@@ -22574,51 +24193,51 @@ init_aws();
|
|
|
22574
24193
|
init_fs();
|
|
22575
24194
|
init_metadata();
|
|
22576
24195
|
import * as clack30 from "@clack/prompts";
|
|
22577
|
-
import * as
|
|
22578
|
-
import
|
|
24196
|
+
import * as pulumi25 from "@pulumi/pulumi";
|
|
24197
|
+
import pc33 from "picocolors";
|
|
22579
24198
|
function displaySMSStatus(options) {
|
|
22580
24199
|
const lines = [];
|
|
22581
|
-
lines.push(
|
|
24200
|
+
lines.push(pc33.bold(pc33.green("SMS Infrastructure Active")));
|
|
22582
24201
|
lines.push("");
|
|
22583
|
-
lines.push(
|
|
24202
|
+
lines.push(pc33.bold("Phone Number"));
|
|
22584
24203
|
if (options.phoneNumber) {
|
|
22585
|
-
lines.push(` Number: ${
|
|
24204
|
+
lines.push(` Number: ${pc33.cyan(options.phoneNumber)}`);
|
|
22586
24205
|
} else {
|
|
22587
|
-
lines.push(` Number: ${
|
|
24206
|
+
lines.push(` Number: ${pc33.yellow("Provisioning...")}`);
|
|
22588
24207
|
}
|
|
22589
|
-
lines.push(` Type: ${
|
|
24208
|
+
lines.push(` Type: ${pc33.cyan(options.phoneNumberType || "simulator")}`);
|
|
22590
24209
|
lines.push("");
|
|
22591
|
-
lines.push(
|
|
22592
|
-
lines.push(` Region: ${
|
|
24210
|
+
lines.push(pc33.bold("Configuration"));
|
|
24211
|
+
lines.push(` Region: ${pc33.cyan(options.region)}`);
|
|
22593
24212
|
if (options.preset) {
|
|
22594
|
-
lines.push(` Preset: ${
|
|
24213
|
+
lines.push(` Preset: ${pc33.cyan(options.preset)}`);
|
|
22595
24214
|
}
|
|
22596
24215
|
if (options.configSetName) {
|
|
22597
|
-
lines.push(` Config Set: ${
|
|
24216
|
+
lines.push(` Config Set: ${pc33.cyan(options.configSetName)}`);
|
|
22598
24217
|
}
|
|
22599
24218
|
lines.push("");
|
|
22600
|
-
lines.push(
|
|
24219
|
+
lines.push(pc33.bold("Features"));
|
|
22601
24220
|
lines.push(
|
|
22602
|
-
` Event Tracking: ${options.eventTracking ?
|
|
24221
|
+
` Event Tracking: ${options.eventTracking ? pc33.green("Enabled") : pc33.dim("Disabled")}`
|
|
22603
24222
|
);
|
|
22604
24223
|
if (options.tableName) {
|
|
22605
|
-
lines.push(` Message History: ${
|
|
22606
|
-
lines.push(` Table: ${
|
|
24224
|
+
lines.push(` Message History: ${pc33.green("Enabled")}`);
|
|
24225
|
+
lines.push(` Table: ${pc33.dim(options.tableName)}`);
|
|
22607
24226
|
}
|
|
22608
24227
|
if (options.queueUrl) {
|
|
22609
|
-
lines.push(` Event Queue: ${
|
|
24228
|
+
lines.push(` Event Queue: ${pc33.green("Enabled")}`);
|
|
22610
24229
|
}
|
|
22611
24230
|
lines.push("");
|
|
22612
24231
|
if (options.roleArn) {
|
|
22613
|
-
lines.push(
|
|
22614
|
-
lines.push(` ${
|
|
24232
|
+
lines.push(pc33.bold("IAM Role"));
|
|
24233
|
+
lines.push(` ${pc33.dim(options.roleArn)}`);
|
|
22615
24234
|
}
|
|
22616
24235
|
clack30.note(lines.join("\n"), "SMS Status");
|
|
22617
24236
|
}
|
|
22618
24237
|
async function smsStatus(_options) {
|
|
22619
24238
|
const startTime = Date.now();
|
|
22620
24239
|
const progress = new DeploymentProgress();
|
|
22621
|
-
clack30.intro(
|
|
24240
|
+
clack30.intro(pc33.bold("Wraps SMS Status"));
|
|
22622
24241
|
const identity = await progress.execute(
|
|
22623
24242
|
"Loading SMS infrastructure status",
|
|
22624
24243
|
async () => validateAWSCredentials()
|
|
@@ -22630,7 +24249,7 @@ async function smsStatus(_options) {
|
|
|
22630
24249
|
clack30.log.error("No SMS infrastructure found");
|
|
22631
24250
|
console.log(
|
|
22632
24251
|
`
|
|
22633
|
-
Run ${
|
|
24252
|
+
Run ${pc33.cyan("wraps sms init")} to deploy SMS infrastructure.
|
|
22634
24253
|
`
|
|
22635
24254
|
);
|
|
22636
24255
|
process.exit(1);
|
|
@@ -22639,7 +24258,7 @@ Run ${pc32.cyan("wraps sms init")} to deploy SMS infrastructure.
|
|
|
22639
24258
|
try {
|
|
22640
24259
|
await ensurePulumiWorkDir();
|
|
22641
24260
|
const stackName = metadata.services.sms.pulumiStackName || `wraps-sms-${identity.accountId}-${region}`;
|
|
22642
|
-
const stack = await
|
|
24261
|
+
const stack = await pulumi25.automation.LocalWorkspace.selectStack({
|
|
22643
24262
|
stackName,
|
|
22644
24263
|
workDir: getPulumiWorkDir()
|
|
22645
24264
|
});
|
|
@@ -22661,25 +24280,25 @@ Run ${pc32.cyan("wraps sms init")} to deploy SMS infrastructure.
|
|
|
22661
24280
|
preset: metadata.services.sms.preset
|
|
22662
24281
|
});
|
|
22663
24282
|
console.log("");
|
|
22664
|
-
clack30.log.info(
|
|
24283
|
+
clack30.log.info(pc33.bold("Commands:"));
|
|
22665
24284
|
console.log(
|
|
22666
|
-
` ${
|
|
24285
|
+
` ${pc33.cyan("wraps sms test --to +1234567890")} - Send a test message`
|
|
22667
24286
|
);
|
|
22668
|
-
console.log(` ${
|
|
24287
|
+
console.log(` ${pc33.cyan("wraps sms destroy")} - Remove SMS infrastructure`);
|
|
22669
24288
|
trackCommand("sms:status", {
|
|
22670
24289
|
success: true,
|
|
22671
24290
|
phone_type: smsConfig?.phoneNumberType,
|
|
22672
24291
|
event_tracking: smsConfig?.eventTracking?.enabled,
|
|
22673
24292
|
duration_ms: Date.now() - startTime
|
|
22674
24293
|
});
|
|
22675
|
-
clack30.outro(
|
|
24294
|
+
clack30.outro(pc33.dim("SMS infrastructure is ready"));
|
|
22676
24295
|
}
|
|
22677
24296
|
|
|
22678
24297
|
// src/commands/sms/sync.ts
|
|
22679
24298
|
init_esm_shims();
|
|
22680
24299
|
import * as clack31 from "@clack/prompts";
|
|
22681
|
-
import * as
|
|
22682
|
-
import
|
|
24300
|
+
import * as pulumi26 from "@pulumi/pulumi";
|
|
24301
|
+
import pc34 from "picocolors";
|
|
22683
24302
|
init_events();
|
|
22684
24303
|
init_aws();
|
|
22685
24304
|
init_errors();
|
|
@@ -22687,7 +24306,7 @@ init_fs();
|
|
|
22687
24306
|
init_metadata();
|
|
22688
24307
|
async function smsSync(options) {
|
|
22689
24308
|
const startTime = Date.now();
|
|
22690
|
-
clack31.intro(
|
|
24309
|
+
clack31.intro(pc34.bold("Wraps SMS Infrastructure Sync"));
|
|
22691
24310
|
const progress = new DeploymentProgress();
|
|
22692
24311
|
const identity = await progress.execute(
|
|
22693
24312
|
"Validating AWS credentials",
|
|
@@ -22701,7 +24320,7 @@ async function smsSync(options) {
|
|
|
22701
24320
|
clack31.log.error("No SMS infrastructure found to sync");
|
|
22702
24321
|
console.log(
|
|
22703
24322
|
`
|
|
22704
|
-
Run ${
|
|
24323
|
+
Run ${pc34.cyan("wraps sms init")} to deploy SMS infrastructure first.
|
|
22705
24324
|
`
|
|
22706
24325
|
);
|
|
22707
24326
|
process.exit(1);
|
|
@@ -22710,10 +24329,10 @@ Run ${pc33.cyan("wraps sms init")} to deploy SMS infrastructure first.
|
|
|
22710
24329
|
const storedStackName = smsService.pulumiStackName;
|
|
22711
24330
|
progress.info("Found existing SMS configuration");
|
|
22712
24331
|
progress.info(
|
|
22713
|
-
`Phone type: ${
|
|
24332
|
+
`Phone type: ${pc34.cyan(smsConfig.phoneNumberType || "simulator")}`
|
|
22714
24333
|
);
|
|
22715
24334
|
progress.info(
|
|
22716
|
-
`Event tracking: ${
|
|
24335
|
+
`Event tracking: ${pc34.cyan(smsConfig.eventTracking?.enabled ? "enabled" : "disabled")}`
|
|
22717
24336
|
);
|
|
22718
24337
|
if (!options.yes) {
|
|
22719
24338
|
const confirmed = await clack31.confirm({
|
|
@@ -22736,7 +24355,7 @@ Run ${pc33.cyan("wraps sms init")} to deploy SMS infrastructure first.
|
|
|
22736
24355
|
outputs = await progress.execute("Syncing SMS infrastructure", async () => {
|
|
22737
24356
|
await ensurePulumiWorkDir();
|
|
22738
24357
|
const stackName = storedStackName || `wraps-sms-${identity.accountId}-${region}`;
|
|
22739
|
-
const stack = await
|
|
24358
|
+
const stack = await pulumi26.automation.LocalWorkspace.createOrSelectStack(
|
|
22740
24359
|
{
|
|
22741
24360
|
stackName,
|
|
22742
24361
|
projectName: "wraps-sms",
|
|
@@ -22835,7 +24454,7 @@ Run ${pc33.cyan("wraps sms init")} to deploy SMS infrastructure first.
|
|
|
22835
24454
|
}
|
|
22836
24455
|
progress.stop();
|
|
22837
24456
|
console.log("\n");
|
|
22838
|
-
clack31.log.success(
|
|
24457
|
+
clack31.log.success(pc34.green("SMS infrastructure synced successfully!"));
|
|
22839
24458
|
const changes = [];
|
|
22840
24459
|
if (outputs.lambdaFunctions?.length) {
|
|
22841
24460
|
changes.push("Lambda functions updated");
|
|
@@ -22843,13 +24462,13 @@ Run ${pc33.cyan("wraps sms init")} to deploy SMS infrastructure first.
|
|
|
22843
24462
|
changes.push("SDK resources verified");
|
|
22844
24463
|
console.log("");
|
|
22845
24464
|
for (const change of changes) {
|
|
22846
|
-
console.log(` ${
|
|
24465
|
+
console.log(` ${pc34.green("\u2713")} ${change}`);
|
|
22847
24466
|
}
|
|
22848
24467
|
trackCommand("sms:sync", {
|
|
22849
24468
|
success: true,
|
|
22850
24469
|
duration_ms: Date.now() - startTime
|
|
22851
24470
|
});
|
|
22852
|
-
clack31.outro(
|
|
24471
|
+
clack31.outro(pc34.green("Sync complete!"));
|
|
22853
24472
|
}
|
|
22854
24473
|
|
|
22855
24474
|
// src/commands/sms/test.ts
|
|
@@ -22862,7 +24481,7 @@ import {
|
|
|
22862
24481
|
SendTextMessageCommand
|
|
22863
24482
|
} from "@aws-sdk/client-pinpoint-sms-voice-v2";
|
|
22864
24483
|
import * as clack32 from "@clack/prompts";
|
|
22865
|
-
import
|
|
24484
|
+
import pc35 from "picocolors";
|
|
22866
24485
|
function isValidPhoneNumber(phone) {
|
|
22867
24486
|
return /^\+[1-9]\d{1,14}$/.test(phone);
|
|
22868
24487
|
}
|
|
@@ -22879,7 +24498,7 @@ var SIMULATOR_DESTINATIONS = [
|
|
|
22879
24498
|
async function smsTest(options) {
|
|
22880
24499
|
const startTime = Date.now();
|
|
22881
24500
|
const progress = new DeploymentProgress();
|
|
22882
|
-
clack32.intro(
|
|
24501
|
+
clack32.intro(pc35.bold("Wraps SMS Test"));
|
|
22883
24502
|
const identity = await progress.execute(
|
|
22884
24503
|
"Validating AWS credentials",
|
|
22885
24504
|
async () => validateAWSCredentials()
|
|
@@ -22891,7 +24510,7 @@ async function smsTest(options) {
|
|
|
22891
24510
|
clack32.log.error("No SMS infrastructure found");
|
|
22892
24511
|
console.log(
|
|
22893
24512
|
`
|
|
22894
|
-
Run ${
|
|
24513
|
+
Run ${pc35.cyan("wraps sms init")} to deploy SMS infrastructure.
|
|
22895
24514
|
`
|
|
22896
24515
|
);
|
|
22897
24516
|
process.exit(1);
|
|
@@ -22998,16 +24617,16 @@ Run ${pc34.cyan("wraps sms init")} to deploy SMS infrastructure.
|
|
|
22998
24617
|
);
|
|
22999
24618
|
progress.stop();
|
|
23000
24619
|
console.log("\n");
|
|
23001
|
-
clack32.log.success(
|
|
24620
|
+
clack32.log.success(pc35.green("Test SMS sent successfully!"));
|
|
23002
24621
|
console.log("");
|
|
23003
24622
|
clack32.note(
|
|
23004
24623
|
[
|
|
23005
|
-
`${
|
|
23006
|
-
`${
|
|
23007
|
-
`${
|
|
23008
|
-
`${
|
|
24624
|
+
`${pc35.bold("Message ID:")} ${pc35.cyan(messageId || "unknown")}`,
|
|
24625
|
+
`${pc35.bold("To:")} ${pc35.cyan(toNumber)}`,
|
|
24626
|
+
`${pc35.bold("Message:")} ${message}`,
|
|
24627
|
+
`${pc35.bold("Type:")} ${pc35.cyan(smsConfig?.phoneNumberType || "simulator")}`,
|
|
23009
24628
|
"",
|
|
23010
|
-
|
|
24629
|
+
pc35.dim(
|
|
23011
24630
|
smsConfig?.phoneNumberType === "simulator" ? "Note: Simulator messages are not actually delivered" : "Check your phone for the message!"
|
|
23012
24631
|
)
|
|
23013
24632
|
].join("\n"),
|
|
@@ -23016,7 +24635,7 @@ Run ${pc34.cyan("wraps sms init")} to deploy SMS infrastructure.
|
|
|
23016
24635
|
if (smsConfig?.eventTracking?.enabled) {
|
|
23017
24636
|
console.log("");
|
|
23018
24637
|
clack32.log.info(
|
|
23019
|
-
|
|
24638
|
+
pc35.dim("Event tracking is enabled. Check DynamoDB for delivery status.")
|
|
23020
24639
|
);
|
|
23021
24640
|
}
|
|
23022
24641
|
trackCommand("sms:test", {
|
|
@@ -23025,7 +24644,7 @@ Run ${pc34.cyan("wraps sms init")} to deploy SMS infrastructure.
|
|
|
23025
24644
|
message_length: message?.length,
|
|
23026
24645
|
duration_ms: Date.now() - startTime
|
|
23027
24646
|
});
|
|
23028
|
-
clack32.outro(
|
|
24647
|
+
clack32.outro(pc35.green("Test complete!"));
|
|
23029
24648
|
} catch (error) {
|
|
23030
24649
|
progress.stop();
|
|
23031
24650
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -23039,7 +24658,7 @@ Run ${pc34.cyan("wraps sms init")} to deploy SMS infrastructure.
|
|
|
23039
24658
|
clack32.log.error("Toll-free number registration is not complete");
|
|
23040
24659
|
console.log(
|
|
23041
24660
|
`
|
|
23042
|
-
Run ${
|
|
24661
|
+
Run ${pc35.cyan("wraps sms register")} to complete registration.
|
|
23043
24662
|
`
|
|
23044
24663
|
);
|
|
23045
24664
|
} else if (errorMessage.includes("opt-out")) {
|
|
@@ -23057,8 +24676,8 @@ Run ${pc34.cyan("wraps sms register")} to complete registration.
|
|
|
23057
24676
|
// src/commands/sms/upgrade.ts
|
|
23058
24677
|
init_esm_shims();
|
|
23059
24678
|
import * as clack33 from "@clack/prompts";
|
|
23060
|
-
import * as
|
|
23061
|
-
import
|
|
24679
|
+
import * as pulumi27 from "@pulumi/pulumi";
|
|
24680
|
+
import pc36 from "picocolors";
|
|
23062
24681
|
init_events();
|
|
23063
24682
|
init_aws();
|
|
23064
24683
|
init_errors();
|
|
@@ -23068,7 +24687,7 @@ init_prompts();
|
|
|
23068
24687
|
async function smsUpgrade(options) {
|
|
23069
24688
|
const startTime = Date.now();
|
|
23070
24689
|
let upgradeAction = "";
|
|
23071
|
-
clack33.intro(
|
|
24690
|
+
clack33.intro(pc36.bold("Wraps SMS Upgrade - Enhance Your SMS Infrastructure"));
|
|
23072
24691
|
const progress = new DeploymentProgress();
|
|
23073
24692
|
const wasAutoInstalled = await progress.execute(
|
|
23074
24693
|
"Checking Pulumi CLI installation",
|
|
@@ -23081,7 +24700,7 @@ async function smsUpgrade(options) {
|
|
|
23081
24700
|
"Validating AWS credentials",
|
|
23082
24701
|
async () => validateAWSCredentials()
|
|
23083
24702
|
);
|
|
23084
|
-
progress.info(`Connected to AWS account: ${
|
|
24703
|
+
progress.info(`Connected to AWS account: ${pc36.cyan(identity.accountId)}`);
|
|
23085
24704
|
let region = options.region;
|
|
23086
24705
|
if (!region) {
|
|
23087
24706
|
const defaultRegion = await getAWSRegion();
|
|
@@ -23090,34 +24709,34 @@ async function smsUpgrade(options) {
|
|
|
23090
24709
|
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
23091
24710
|
if (!metadata) {
|
|
23092
24711
|
clack33.log.error(
|
|
23093
|
-
`No Wraps connection found for account ${
|
|
24712
|
+
`No Wraps connection found for account ${pc36.cyan(identity.accountId)} in region ${pc36.cyan(region)}`
|
|
23094
24713
|
);
|
|
23095
24714
|
clack33.log.info(
|
|
23096
|
-
`Use ${
|
|
24715
|
+
`Use ${pc36.cyan("wraps sms init")} to create new infrastructure.`
|
|
23097
24716
|
);
|
|
23098
24717
|
process.exit(1);
|
|
23099
24718
|
}
|
|
23100
24719
|
if (!metadata.services.sms) {
|
|
23101
24720
|
clack33.log.error("No SMS infrastructure found");
|
|
23102
24721
|
clack33.log.info(
|
|
23103
|
-
`Use ${
|
|
24722
|
+
`Use ${pc36.cyan("wraps sms init")} to deploy SMS infrastructure.`
|
|
23104
24723
|
);
|
|
23105
24724
|
process.exit(1);
|
|
23106
24725
|
}
|
|
23107
24726
|
progress.info(`Found existing connection created: ${metadata.timestamp}`);
|
|
23108
24727
|
console.log(`
|
|
23109
|
-
${
|
|
24728
|
+
${pc36.bold("Current Configuration:")}
|
|
23110
24729
|
`);
|
|
23111
24730
|
if (metadata.services.sms.preset) {
|
|
23112
|
-
console.log(` Preset: ${
|
|
24731
|
+
console.log(` Preset: ${pc36.cyan(metadata.services.sms.preset)}`);
|
|
23113
24732
|
} else {
|
|
23114
|
-
console.log(` Preset: ${
|
|
24733
|
+
console.log(` Preset: ${pc36.cyan("custom")}`);
|
|
23115
24734
|
}
|
|
23116
24735
|
const config2 = metadata.services.sms.config;
|
|
23117
24736
|
if (!config2) {
|
|
23118
24737
|
clack33.log.error("No SMS configuration found in metadata");
|
|
23119
24738
|
clack33.log.info(
|
|
23120
|
-
`Use ${
|
|
24739
|
+
`Use ${pc36.cyan("wraps sms init")} to create new infrastructure.`
|
|
23121
24740
|
);
|
|
23122
24741
|
process.exit(1);
|
|
23123
24742
|
}
|
|
@@ -23129,45 +24748,45 @@ ${pc35.bold("Current Configuration:")}
|
|
|
23129
24748
|
"short-code": "Short code ($995+/mo, 100+ MPS)"
|
|
23130
24749
|
};
|
|
23131
24750
|
console.log(
|
|
23132
|
-
` Phone Type: ${
|
|
24751
|
+
` Phone Type: ${pc36.cyan(phoneTypeLabels2[config2.phoneNumberType] || config2.phoneNumberType)}`
|
|
23133
24752
|
);
|
|
23134
24753
|
}
|
|
23135
24754
|
if (config2.tracking?.enabled) {
|
|
23136
|
-
console.log(` ${
|
|
24755
|
+
console.log(` ${pc36.green("\u2713")} Delivery Tracking`);
|
|
23137
24756
|
if (config2.tracking.linkTracking) {
|
|
23138
|
-
console.log(` ${
|
|
24757
|
+
console.log(` ${pc36.dim("\u2514\u2500")} Link click tracking enabled`);
|
|
23139
24758
|
}
|
|
23140
24759
|
}
|
|
23141
24760
|
if (config2.eventTracking?.enabled) {
|
|
23142
|
-
console.log(` ${
|
|
24761
|
+
console.log(` ${pc36.green("\u2713")} Event Tracking (SNS)`);
|
|
23143
24762
|
if (config2.eventTracking.dynamoDBHistory) {
|
|
23144
24763
|
console.log(
|
|
23145
|
-
` ${
|
|
24764
|
+
` ${pc36.dim("\u2514\u2500")} Message History: ${pc36.cyan(config2.eventTracking.archiveRetention || "90days")}`
|
|
23146
24765
|
);
|
|
23147
24766
|
}
|
|
23148
24767
|
}
|
|
23149
24768
|
if (config2.messageArchiving?.enabled) {
|
|
23150
24769
|
console.log(
|
|
23151
|
-
` ${
|
|
24770
|
+
` ${pc36.green("\u2713")} Message Archiving (${config2.messageArchiving.retention})`
|
|
23152
24771
|
);
|
|
23153
24772
|
}
|
|
23154
24773
|
if (config2.optOutManagement) {
|
|
23155
|
-
console.log(` ${
|
|
24774
|
+
console.log(` ${pc36.green("\u2713")} Opt-out Management`);
|
|
23156
24775
|
}
|
|
23157
24776
|
if (config2.protectConfiguration?.enabled) {
|
|
23158
24777
|
const countries = config2.protectConfiguration.allowedCountries?.join(", ") || "US";
|
|
23159
|
-
console.log(` ${
|
|
23160
|
-
console.log(` ${
|
|
24778
|
+
console.log(` ${pc36.green("\u2713")} Fraud Protection`);
|
|
24779
|
+
console.log(` ${pc36.dim("\u2514\u2500")} Allowed countries: ${pc36.cyan(countries)}`);
|
|
23161
24780
|
if (config2.protectConfiguration.aitFiltering) {
|
|
23162
|
-
console.log(` ${
|
|
24781
|
+
console.log(` ${pc36.dim("\u2514\u2500")} AIT filtering: ${pc36.cyan("enabled")}`);
|
|
23163
24782
|
}
|
|
23164
24783
|
} else {
|
|
23165
|
-
console.log(` ${
|
|
24784
|
+
console.log(` ${pc36.dim("\u25CB")} Fraud Protection (not configured)`);
|
|
23166
24785
|
}
|
|
23167
24786
|
const currentCostData = calculateSMSCosts(config2, 1e4);
|
|
23168
24787
|
console.log(
|
|
23169
24788
|
`
|
|
23170
|
-
Estimated Cost: ${
|
|
24789
|
+
Estimated Cost: ${pc36.cyan(`~${formatCost3(currentCostData.total.monthly)}/mo`)}`
|
|
23171
24790
|
);
|
|
23172
24791
|
console.log("");
|
|
23173
24792
|
const phoneTypeLabels = {
|
|
@@ -23274,22 +24893,22 @@ ${pc35.bold("Current Configuration:")}
|
|
|
23274
24893
|
if (selectedType === "toll-free") {
|
|
23275
24894
|
console.log(
|
|
23276
24895
|
`
|
|
23277
|
-
${
|
|
24896
|
+
${pc36.yellow("\u26A0")} ${pc36.bold("Toll-free Registration Required")}
|
|
23278
24897
|
`
|
|
23279
24898
|
);
|
|
23280
24899
|
console.log(
|
|
23281
|
-
|
|
24900
|
+
pc36.dim("Toll-free numbers require carrier registration before")
|
|
23282
24901
|
);
|
|
23283
24902
|
console.log(
|
|
23284
|
-
|
|
24903
|
+
pc36.dim("they can send messages at scale. After deployment:\n")
|
|
23285
24904
|
);
|
|
23286
24905
|
console.log(
|
|
23287
|
-
` 1. Run ${
|
|
24906
|
+
` 1. Run ${pc36.cyan("wraps sms register")} to start registration`
|
|
23288
24907
|
);
|
|
23289
24908
|
console.log(" 2. Submit your business use case information");
|
|
23290
24909
|
console.log(" 3. Wait for carrier verification (1-5 business days)");
|
|
23291
24910
|
console.log(
|
|
23292
|
-
|
|
24911
|
+
pc36.dim("\nUntil verified, sending is limited to low volume.\n")
|
|
23293
24912
|
);
|
|
23294
24913
|
const confirmTollFree = await clack33.confirm({
|
|
23295
24914
|
message: "Continue with toll-free number request?",
|
|
@@ -23303,10 +24922,10 @@ ${pc35.yellow("\u26A0")} ${pc35.bold("Toll-free Registration Required")}
|
|
|
23303
24922
|
if (selectedType === "10dlc") {
|
|
23304
24923
|
console.log(
|
|
23305
24924
|
`
|
|
23306
|
-
${
|
|
24925
|
+
${pc36.yellow("\u26A0")} ${pc36.bold("10DLC Campaign Registration Required")}
|
|
23307
24926
|
`
|
|
23308
24927
|
);
|
|
23309
|
-
console.log(
|
|
24928
|
+
console.log(pc36.dim("10DLC requires brand and campaign registration:"));
|
|
23310
24929
|
console.log(" \u2022 Brand registration: one-time $4 fee");
|
|
23311
24930
|
console.log(" \u2022 Campaign registration: $15/mo per campaign");
|
|
23312
24931
|
console.log(" \u2022 Verification takes 1-7 business days");
|
|
@@ -23539,12 +25158,12 @@ ${pc35.yellow("\u26A0")} ${pc35.bold("10DLC Campaign Registration Required")}
|
|
|
23539
25158
|
const enableLinkTracking = !config2.tracking?.linkTracking;
|
|
23540
25159
|
if (enableLinkTracking) {
|
|
23541
25160
|
clack33.log.info(
|
|
23542
|
-
|
|
25161
|
+
pc36.dim(
|
|
23543
25162
|
"Link tracking will track clicks on URLs in your SMS messages."
|
|
23544
25163
|
)
|
|
23545
25164
|
);
|
|
23546
25165
|
clack33.log.info(
|
|
23547
|
-
|
|
25166
|
+
pc36.dim("URLs will be rewritten to go through a tracking endpoint.")
|
|
23548
25167
|
);
|
|
23549
25168
|
}
|
|
23550
25169
|
const confirmed = await clack33.confirm({
|
|
@@ -23757,18 +25376,18 @@ ${pc35.yellow("\u26A0")} ${pc35.bold("10DLC Campaign Registration Required")}
|
|
|
23757
25376
|
const newCostData = calculateSMSCosts(updatedConfig, 1e4);
|
|
23758
25377
|
const costDiff = newCostData.total.monthly - currentCostData.total.monthly;
|
|
23759
25378
|
console.log(`
|
|
23760
|
-
${
|
|
25379
|
+
${pc36.bold("Cost Impact:")}`);
|
|
23761
25380
|
console.log(
|
|
23762
|
-
` Current: ${
|
|
25381
|
+
` Current: ${pc36.cyan(`${formatCost3(currentCostData.total.monthly)}/mo`)}`
|
|
23763
25382
|
);
|
|
23764
25383
|
console.log(
|
|
23765
|
-
` New: ${
|
|
25384
|
+
` New: ${pc36.cyan(`${formatCost3(newCostData.total.monthly)}/mo`)}`
|
|
23766
25385
|
);
|
|
23767
25386
|
if (costDiff > 0) {
|
|
23768
|
-
console.log(` Change: ${
|
|
25387
|
+
console.log(` Change: ${pc36.yellow(`+${formatCost3(costDiff)}/mo`)}`);
|
|
23769
25388
|
} else if (costDiff < 0) {
|
|
23770
25389
|
console.log(
|
|
23771
|
-
` Change: ${
|
|
25390
|
+
` Change: ${pc36.green(`-${formatCost3(Math.abs(costDiff))}/mo`)}`
|
|
23772
25391
|
);
|
|
23773
25392
|
}
|
|
23774
25393
|
console.log("");
|
|
@@ -23800,7 +25419,7 @@ ${pc35.bold("Cost Impact:")}`);
|
|
|
23800
25419
|
"Updating SMS infrastructure (this may take 2-3 minutes)",
|
|
23801
25420
|
async () => {
|
|
23802
25421
|
await ensurePulumiWorkDir();
|
|
23803
|
-
const stack = await
|
|
25422
|
+
const stack = await pulumi27.automation.LocalWorkspace.createOrSelectStack(
|
|
23804
25423
|
{
|
|
23805
25424
|
stackName: metadata.services.sms?.pulumiStackName || `wraps-sms-${identity.accountId}-${region}`,
|
|
23806
25425
|
projectName: "wraps-sms",
|
|
@@ -23901,43 +25520,43 @@ ${pc35.bold("Cost Impact:")}`);
|
|
|
23901
25520
|
await saveConnectionMetadata(metadata);
|
|
23902
25521
|
progress.info("Connection metadata updated");
|
|
23903
25522
|
console.log("\n");
|
|
23904
|
-
clack33.log.success(
|
|
25523
|
+
clack33.log.success(pc36.green(pc36.bold("SMS infrastructure upgraded!")));
|
|
23905
25524
|
console.log("\n");
|
|
23906
25525
|
clack33.note(
|
|
23907
25526
|
[
|
|
23908
|
-
`${
|
|
23909
|
-
`${
|
|
23910
|
-
`${
|
|
23911
|
-
`${
|
|
23912
|
-
outputs.tableName ? `${
|
|
25527
|
+
`${pc36.bold("Phone Number:")} ${pc36.cyan(outputs.phoneNumber || "Provisioning...")}`,
|
|
25528
|
+
`${pc36.bold("Phone Type:")} ${pc36.cyan(updatedConfig.phoneNumberType || "simulator")}`,
|
|
25529
|
+
`${pc36.bold("Config Set:")} ${pc36.cyan(outputs.configSetName || "wraps-sms-config")}`,
|
|
25530
|
+
`${pc36.bold("Region:")} ${pc36.cyan(outputs.region)}`,
|
|
25531
|
+
outputs.tableName ? `${pc36.bold("History Table:")} ${pc36.cyan(outputs.tableName)}` : "",
|
|
23913
25532
|
"",
|
|
23914
|
-
|
|
23915
|
-
|
|
25533
|
+
pc36.dim("IAM Role:"),
|
|
25534
|
+
pc36.dim(` ${outputs.roleArn}`)
|
|
23916
25535
|
].filter(Boolean).join("\n"),
|
|
23917
25536
|
"SMS Infrastructure"
|
|
23918
25537
|
);
|
|
23919
25538
|
console.log(`
|
|
23920
|
-
${
|
|
25539
|
+
${pc36.green("\u2713")} ${pc36.bold("Upgrade complete!")}
|
|
23921
25540
|
`);
|
|
23922
25541
|
if (upgradeAction === "phone-number") {
|
|
23923
25542
|
console.log(
|
|
23924
|
-
`Upgraded to ${
|
|
25543
|
+
`Upgraded to ${pc36.cyan(updatedConfig.phoneNumberType)} number (${pc36.green(`${formatCost3(newCostData.total.monthly)}/mo`)})
|
|
23925
25544
|
`
|
|
23926
25545
|
);
|
|
23927
25546
|
if (updatedConfig.phoneNumberType === "toll-free") {
|
|
23928
|
-
console.log(`${
|
|
25547
|
+
console.log(`${pc36.bold("Next Steps:")}`);
|
|
23929
25548
|
console.log(
|
|
23930
|
-
` 1. Run ${
|
|
25549
|
+
` 1. Run ${pc36.cyan("wraps sms register")} to start toll-free registration`
|
|
23931
25550
|
);
|
|
23932
25551
|
console.log(" 2. Submit your business information and use case");
|
|
23933
25552
|
console.log(" 3. Wait for carrier verification (1-5 business days)");
|
|
23934
25553
|
console.log("");
|
|
23935
25554
|
console.log(
|
|
23936
|
-
|
|
25555
|
+
pc36.dim("Until verified, your number can only send limited messages.")
|
|
23937
25556
|
);
|
|
23938
25557
|
console.log("");
|
|
23939
25558
|
} else if (updatedConfig.phoneNumberType === "10dlc") {
|
|
23940
|
-
console.log(`${
|
|
25559
|
+
console.log(`${pc36.bold("Next Steps:")}`);
|
|
23941
25560
|
console.log(" 1. Register your brand in the AWS Console");
|
|
23942
25561
|
console.log(" 2. Create a 10DLC campaign for your use case");
|
|
23943
25562
|
console.log(" 3. Wait for campaign approval (1-7 business days)");
|
|
@@ -23945,16 +25564,16 @@ ${pc35.green("\u2713")} ${pc35.bold("Upgrade complete!")}
|
|
|
23945
25564
|
}
|
|
23946
25565
|
} else if (upgradeAction === "preset" && newPreset) {
|
|
23947
25566
|
console.log(
|
|
23948
|
-
`Upgraded to ${
|
|
25567
|
+
`Upgraded to ${pc36.cyan(newPreset)} preset (${pc36.green(`${formatCost3(newCostData.total.monthly)}/mo`)})
|
|
23949
25568
|
`
|
|
23950
25569
|
);
|
|
23951
25570
|
} else {
|
|
23952
25571
|
console.log(
|
|
23953
|
-
`Updated configuration (${
|
|
25572
|
+
`Updated configuration (${pc36.green(`${formatCost3(newCostData.total.monthly)}/mo`)})
|
|
23954
25573
|
`
|
|
23955
25574
|
);
|
|
23956
25575
|
}
|
|
23957
|
-
console.log(
|
|
25576
|
+
console.log(pc36.dim(getSMSCostSummary(updatedConfig, 1e4)));
|
|
23958
25577
|
const enabledFeatures = [];
|
|
23959
25578
|
if (updatedConfig.tracking?.enabled) {
|
|
23960
25579
|
enabledFeatures.push("tracking");
|
|
@@ -23978,7 +25597,7 @@ ${pc35.green("\u2713")} ${pc35.bold("Upgrade complete!")}
|
|
|
23978
25597
|
action: typeof upgradeAction === "string" ? upgradeAction : void 0,
|
|
23979
25598
|
duration_ms: Date.now() - startTime
|
|
23980
25599
|
});
|
|
23981
|
-
clack33.outro(
|
|
25600
|
+
clack33.outro(pc36.green("Upgrade complete!"));
|
|
23982
25601
|
}
|
|
23983
25602
|
|
|
23984
25603
|
// src/commands/sms/verify-number.ts
|
|
@@ -23995,14 +25614,14 @@ import {
|
|
|
23995
25614
|
VerifyDestinationNumberCommand
|
|
23996
25615
|
} from "@aws-sdk/client-pinpoint-sms-voice-v2";
|
|
23997
25616
|
import * as clack34 from "@clack/prompts";
|
|
23998
|
-
import
|
|
25617
|
+
import pc37 from "picocolors";
|
|
23999
25618
|
function isValidPhoneNumber2(phone) {
|
|
24000
25619
|
return /^\+[1-9]\d{1,14}$/.test(phone);
|
|
24001
25620
|
}
|
|
24002
25621
|
async function smsVerifyNumber(options) {
|
|
24003
25622
|
const startTime = Date.now();
|
|
24004
25623
|
const progress = new DeploymentProgress();
|
|
24005
|
-
clack34.intro(
|
|
25624
|
+
clack34.intro(pc37.bold("Wraps SMS - Verify Destination Number"));
|
|
24006
25625
|
const identity = await progress.execute(
|
|
24007
25626
|
"Validating AWS credentials",
|
|
24008
25627
|
async () => validateAWSCredentials()
|
|
@@ -24014,7 +25633,7 @@ async function smsVerifyNumber(options) {
|
|
|
24014
25633
|
clack34.log.error("No SMS infrastructure found");
|
|
24015
25634
|
console.log(
|
|
24016
25635
|
`
|
|
24017
|
-
Run ${
|
|
25636
|
+
Run ${pc37.cyan("wraps sms init")} to deploy SMS infrastructure.
|
|
24018
25637
|
`
|
|
24019
25638
|
);
|
|
24020
25639
|
process.exit(1);
|
|
@@ -24031,16 +25650,16 @@ Run ${pc36.cyan("wraps sms init")} to deploy SMS infrastructure.
|
|
|
24031
25650
|
clack34.log.info("No verified destination numbers found");
|
|
24032
25651
|
console.log(
|
|
24033
25652
|
`
|
|
24034
|
-
Run ${
|
|
25653
|
+
Run ${pc37.cyan("wraps sms verify-number")} to verify a number.
|
|
24035
25654
|
`
|
|
24036
25655
|
);
|
|
24037
25656
|
} else {
|
|
24038
25657
|
console.log("\n");
|
|
24039
|
-
clack34.log.info(
|
|
25658
|
+
clack34.log.info(pc37.bold("Verified Destination Numbers:"));
|
|
24040
25659
|
console.log("");
|
|
24041
25660
|
for (const num of response.VerifiedDestinationNumbers) {
|
|
24042
|
-
const status2 = num.Status === "VERIFIED" ?
|
|
24043
|
-
console.log(` ${
|
|
25661
|
+
const status2 = num.Status === "VERIFIED" ? pc37.green("\u2713 Verified") : pc37.yellow("\u29D6 Pending");
|
|
25662
|
+
console.log(` ${pc37.cyan(num.DestinationPhoneNumber)} - ${status2}`);
|
|
24044
25663
|
}
|
|
24045
25664
|
console.log("");
|
|
24046
25665
|
}
|
|
@@ -24049,7 +25668,7 @@ Run ${pc36.cyan("wraps sms verify-number")} to verify a number.
|
|
|
24049
25668
|
count: response.VerifiedDestinationNumbers?.length || 0,
|
|
24050
25669
|
duration_ms: Date.now() - startTime
|
|
24051
25670
|
});
|
|
24052
|
-
clack34.outro(
|
|
25671
|
+
clack34.outro(pc37.green("Done!"));
|
|
24053
25672
|
return;
|
|
24054
25673
|
} catch (error) {
|
|
24055
25674
|
progress.stop();
|
|
@@ -24068,7 +25687,7 @@ Run ${pc36.cyan("wraps sms verify-number")} to verify a number.
|
|
|
24068
25687
|
clack34.log.error("Phone number is required for deletion");
|
|
24069
25688
|
console.log(
|
|
24070
25689
|
`
|
|
24071
|
-
Usage: ${
|
|
25690
|
+
Usage: ${pc37.cyan("wraps sms verify-number --delete --phone-number +14155551234")}
|
|
24072
25691
|
`
|
|
24073
25692
|
);
|
|
24074
25693
|
process.exit(1);
|
|
@@ -24093,12 +25712,12 @@ Usage: ${pc36.cyan("wraps sms verify-number --delete --phone-number +14155551234
|
|
|
24093
25712
|
);
|
|
24094
25713
|
});
|
|
24095
25714
|
progress.stop();
|
|
24096
|
-
clack34.log.success(`Removed ${
|
|
25715
|
+
clack34.log.success(`Removed ${pc37.cyan(phoneNumber2)} from verified list`);
|
|
24097
25716
|
trackCommand("sms:verify-number:delete", {
|
|
24098
25717
|
success: true,
|
|
24099
25718
|
duration_ms: Date.now() - startTime
|
|
24100
25719
|
});
|
|
24101
|
-
clack34.outro(
|
|
25720
|
+
clack34.outro(pc37.green("Done!"));
|
|
24102
25721
|
return;
|
|
24103
25722
|
} catch (error) {
|
|
24104
25723
|
progress.stop();
|
|
@@ -24163,17 +25782,17 @@ Usage: ${pc36.cyan("wraps sms verify-number --delete --phone-number +14155551234
|
|
|
24163
25782
|
progress.stop();
|
|
24164
25783
|
console.log("\n");
|
|
24165
25784
|
clack34.log.success(
|
|
24166
|
-
|
|
25785
|
+
pc37.green(`Phone number ${pc37.cyan(phoneNumber)} verified!`)
|
|
24167
25786
|
);
|
|
24168
25787
|
console.log("");
|
|
24169
25788
|
console.log(
|
|
24170
|
-
`You can now send test messages to this number with ${
|
|
25789
|
+
`You can now send test messages to this number with ${pc37.cyan("wraps sms test")}`
|
|
24171
25790
|
);
|
|
24172
25791
|
trackCommand("sms:verify-number:confirm", {
|
|
24173
25792
|
success: true,
|
|
24174
25793
|
duration_ms: Date.now() - startTime
|
|
24175
25794
|
});
|
|
24176
|
-
clack34.outro(
|
|
25795
|
+
clack34.outro(pc37.green("Verification complete!"));
|
|
24177
25796
|
return;
|
|
24178
25797
|
} catch (error) {
|
|
24179
25798
|
progress.stop();
|
|
@@ -24182,7 +25801,7 @@ Usage: ${pc36.cyan("wraps sms verify-number --delete --phone-number +14155551234
|
|
|
24182
25801
|
clack34.log.error("Invalid verification code. Please try again.");
|
|
24183
25802
|
console.log(
|
|
24184
25803
|
`
|
|
24185
|
-
Run ${
|
|
25804
|
+
Run ${pc37.cyan(`wraps sms verify-number --phone-number ${phoneNumber} --resend`)} to get a new code.
|
|
24186
25805
|
`
|
|
24187
25806
|
);
|
|
24188
25807
|
} else {
|
|
@@ -24218,17 +25837,17 @@ Run ${pc36.cyan(`wraps sms verify-number --phone-number ${phoneNumber} --resend`
|
|
|
24218
25837
|
);
|
|
24219
25838
|
});
|
|
24220
25839
|
progress.stop();
|
|
24221
|
-
clack34.log.success(`Verification code resent to ${
|
|
25840
|
+
clack34.log.success(`Verification code resent to ${pc37.cyan(phoneNumber)}`);
|
|
24222
25841
|
console.log("");
|
|
24223
25842
|
console.log(
|
|
24224
25843
|
`Once you receive the code, run:
|
|
24225
|
-
${
|
|
25844
|
+
${pc37.cyan(`wraps sms verify-number --phone-number ${phoneNumber} --code YOUR_CODE`)}`
|
|
24226
25845
|
);
|
|
24227
25846
|
trackCommand("sms:verify-number:resend", {
|
|
24228
25847
|
success: true,
|
|
24229
25848
|
duration_ms: Date.now() - startTime
|
|
24230
25849
|
});
|
|
24231
|
-
clack34.outro(
|
|
25850
|
+
clack34.outro(pc37.green("Code sent!"));
|
|
24232
25851
|
return;
|
|
24233
25852
|
} catch (error) {
|
|
24234
25853
|
progress.stop();
|
|
@@ -24250,9 +25869,9 @@ Run ${pc36.cyan(`wraps sms verify-number --phone-number ${phoneNumber} --resend`
|
|
|
24250
25869
|
if (existingNumber?.Status === "VERIFIED") {
|
|
24251
25870
|
progress.stop();
|
|
24252
25871
|
clack34.log.info(
|
|
24253
|
-
`Number ${
|
|
25872
|
+
`Number ${pc37.cyan(phoneNumber)} is already verified and ready to use!`
|
|
24254
25873
|
);
|
|
24255
|
-
clack34.outro(
|
|
25874
|
+
clack34.outro(pc37.green("Done!"));
|
|
24256
25875
|
return;
|
|
24257
25876
|
}
|
|
24258
25877
|
if (existingNumber?.Status === "PENDING") {
|
|
@@ -24269,14 +25888,14 @@ Run ${pc36.cyan(`wraps sms verify-number --phone-number ${phoneNumber} --resend`
|
|
|
24269
25888
|
);
|
|
24270
25889
|
progress.stop();
|
|
24271
25890
|
clack34.log.info(
|
|
24272
|
-
`Verification already in progress. New code sent to ${
|
|
25891
|
+
`Verification already in progress. New code sent to ${pc37.cyan(phoneNumber)}`
|
|
24273
25892
|
);
|
|
24274
25893
|
console.log("");
|
|
24275
25894
|
console.log(
|
|
24276
25895
|
`Once you receive the code, run:
|
|
24277
|
-
${
|
|
25896
|
+
${pc37.cyan(`wraps sms verify-number --phone-number ${phoneNumber} --code YOUR_CODE`)}`
|
|
24278
25897
|
);
|
|
24279
|
-
clack34.outro(
|
|
25898
|
+
clack34.outro(pc37.green("Code sent!"));
|
|
24280
25899
|
return;
|
|
24281
25900
|
}
|
|
24282
25901
|
const createResponse = await progress.execute(
|
|
@@ -24298,7 +25917,7 @@ Run ${pc36.cyan(`wraps sms verify-number --phone-number ${phoneNumber} --resend`
|
|
|
24298
25917
|
progress.stop();
|
|
24299
25918
|
console.log("\n");
|
|
24300
25919
|
clack34.log.success(
|
|
24301
|
-
`Verification code sent to ${
|
|
25920
|
+
`Verification code sent to ${pc37.cyan(phoneNumber)} via SMS`
|
|
24302
25921
|
);
|
|
24303
25922
|
console.log("");
|
|
24304
25923
|
clack34.note(
|
|
@@ -24306,9 +25925,9 @@ Run ${pc36.cyan(`wraps sms verify-number --phone-number ${phoneNumber} --resend`
|
|
|
24306
25925
|
"1. Check your phone for the verification code",
|
|
24307
25926
|
"",
|
|
24308
25927
|
"2. Complete verification with:",
|
|
24309
|
-
` ${
|
|
25928
|
+
` ${pc37.cyan(`wraps sms verify-number --phone-number ${phoneNumber} --code YOUR_CODE`)}`,
|
|
24310
25929
|
"",
|
|
24311
|
-
|
|
25930
|
+
pc37.dim("The code expires in 24 hours")
|
|
24312
25931
|
].join("\n"),
|
|
24313
25932
|
"Next Steps"
|
|
24314
25933
|
);
|
|
@@ -24316,7 +25935,7 @@ Run ${pc36.cyan(`wraps sms verify-number --phone-number ${phoneNumber} --resend`
|
|
|
24316
25935
|
success: true,
|
|
24317
25936
|
duration_ms: Date.now() - startTime
|
|
24318
25937
|
});
|
|
24319
|
-
clack34.outro(
|
|
25938
|
+
clack34.outro(pc37.green("Verification started!"));
|
|
24320
25939
|
} catch (error) {
|
|
24321
25940
|
progress.stop();
|
|
24322
25941
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -24324,7 +25943,7 @@ Run ${pc36.cyan(`wraps sms verify-number --phone-number ${phoneNumber} --resend`
|
|
|
24324
25943
|
clack34.log.error("This number is already being verified");
|
|
24325
25944
|
console.log(
|
|
24326
25945
|
`
|
|
24327
|
-
Run ${
|
|
25946
|
+
Run ${pc37.cyan(`wraps sms verify-number --phone-number ${phoneNumber} --resend`)} to get a new code.
|
|
24328
25947
|
`
|
|
24329
25948
|
);
|
|
24330
25949
|
} else {
|
|
@@ -24340,17 +25959,17 @@ Run ${pc36.cyan(`wraps sms verify-number --phone-number ${phoneNumber} --resend`
|
|
|
24340
25959
|
// src/commands/support.ts
|
|
24341
25960
|
init_esm_shims();
|
|
24342
25961
|
import * as clack35 from "@clack/prompts";
|
|
24343
|
-
import
|
|
25962
|
+
import pc38 from "picocolors";
|
|
24344
25963
|
async function support() {
|
|
24345
|
-
clack35.intro(
|
|
25964
|
+
clack35.intro(pc38.bold("Get Help with Wraps"));
|
|
24346
25965
|
console.log();
|
|
24347
|
-
console.log(` ${
|
|
25966
|
+
console.log(` ${pc38.bold("Email:")} ${pc38.cyan("hey@wraps.sh")}`);
|
|
24348
25967
|
console.log(
|
|
24349
|
-
` ${
|
|
25968
|
+
` ${pc38.bold("GitHub:")} ${pc38.cyan("https://github.com/wraps-dev/wraps/issues")}`
|
|
24350
25969
|
);
|
|
24351
|
-
console.log(` ${
|
|
25970
|
+
console.log(` ${pc38.bold("Docs:")} ${pc38.cyan("https://wraps.dev/docs")}`);
|
|
24352
25971
|
console.log();
|
|
24353
|
-
console.log(
|
|
25972
|
+
console.log(pc38.dim(" Response time: Usually within 24 hours"));
|
|
24354
25973
|
console.log();
|
|
24355
25974
|
}
|
|
24356
25975
|
|
|
@@ -24358,55 +25977,55 @@ async function support() {
|
|
|
24358
25977
|
init_esm_shims();
|
|
24359
25978
|
init_client();
|
|
24360
25979
|
import * as clack36 from "@clack/prompts";
|
|
24361
|
-
import
|
|
25980
|
+
import pc39 from "picocolors";
|
|
24362
25981
|
async function telemetryEnable() {
|
|
24363
25982
|
const client = getTelemetryClient();
|
|
24364
25983
|
client.enable();
|
|
24365
|
-
clack36.log.success(
|
|
24366
|
-
console.log(` Config: ${
|
|
25984
|
+
clack36.log.success(pc39.green("Telemetry enabled"));
|
|
25985
|
+
console.log(` Config: ${pc39.dim(client.getConfigPath())}`);
|
|
24367
25986
|
console.log(`
|
|
24368
|
-
${
|
|
25987
|
+
${pc39.dim("Thank you for helping improve Wraps!")}
|
|
24369
25988
|
`);
|
|
24370
25989
|
}
|
|
24371
25990
|
async function telemetryDisable() {
|
|
24372
25991
|
const client = getTelemetryClient();
|
|
24373
25992
|
client.disable();
|
|
24374
|
-
clack36.log.success(
|
|
24375
|
-
console.log(` Config: ${
|
|
25993
|
+
clack36.log.success(pc39.green("Telemetry disabled"));
|
|
25994
|
+
console.log(` Config: ${pc39.dim(client.getConfigPath())}`);
|
|
24376
25995
|
console.log(
|
|
24377
25996
|
`
|
|
24378
|
-
${
|
|
25997
|
+
${pc39.dim("You can re-enable with:")} wraps telemetry enable
|
|
24379
25998
|
`
|
|
24380
25999
|
);
|
|
24381
26000
|
}
|
|
24382
26001
|
async function telemetryStatus() {
|
|
24383
26002
|
const client = getTelemetryClient();
|
|
24384
|
-
clack36.intro(
|
|
24385
|
-
const status2 = client.isEnabled() ?
|
|
26003
|
+
clack36.intro(pc39.bold("Telemetry Status"));
|
|
26004
|
+
const status2 = client.isEnabled() ? pc39.green("Enabled") : pc39.red("Disabled");
|
|
24386
26005
|
console.log();
|
|
24387
|
-
console.log(` ${
|
|
24388
|
-
console.log(` ${
|
|
26006
|
+
console.log(` ${pc39.bold("Status:")} ${status2}`);
|
|
26007
|
+
console.log(` ${pc39.bold("Config file:")} ${pc39.dim(client.getConfigPath())}`);
|
|
24389
26008
|
if (client.isEnabled()) {
|
|
24390
26009
|
console.log();
|
|
24391
|
-
console.log(
|
|
24392
|
-
console.log(` ${
|
|
26010
|
+
console.log(pc39.bold(" How to opt-out:"));
|
|
26011
|
+
console.log(` ${pc39.cyan("wraps telemetry disable")}`);
|
|
24393
26012
|
console.log(
|
|
24394
|
-
` ${
|
|
26013
|
+
` ${pc39.dim("Or set:")} ${pc39.cyan("WRAPS_TELEMETRY_DISABLED=1")}`
|
|
24395
26014
|
);
|
|
24396
|
-
console.log(` ${
|
|
26015
|
+
console.log(` ${pc39.dim("Or set:")} ${pc39.cyan("DO_NOT_TRACK=1")}`);
|
|
24397
26016
|
} else {
|
|
24398
26017
|
console.log();
|
|
24399
|
-
console.log(
|
|
24400
|
-
console.log(` ${
|
|
26018
|
+
console.log(pc39.bold(" How to opt-in:"));
|
|
26019
|
+
console.log(` ${pc39.cyan("wraps telemetry enable")}`);
|
|
24401
26020
|
}
|
|
24402
26021
|
console.log();
|
|
24403
|
-
console.log(
|
|
26022
|
+
console.log(pc39.bold(" Debug mode:"));
|
|
24404
26023
|
console.log(
|
|
24405
|
-
` ${
|
|
26024
|
+
` ${pc39.dim("See what would be sent:")} ${pc39.cyan("WRAPS_TELEMETRY_DEBUG=1 wraps <command>")}`
|
|
24406
26025
|
);
|
|
24407
26026
|
console.log();
|
|
24408
26027
|
console.log(
|
|
24409
|
-
` ${
|
|
26028
|
+
` ${pc39.dim("Learn more:")} ${pc39.cyan("https://wraps.dev/docs/telemetry")}`
|
|
24410
26029
|
);
|
|
24411
26030
|
console.log();
|
|
24412
26031
|
}
|
|
@@ -24491,135 +26110,138 @@ function showVersion() {
|
|
|
24491
26110
|
process.exit(0);
|
|
24492
26111
|
}
|
|
24493
26112
|
function showHelp() {
|
|
24494
|
-
clack37.intro(
|
|
26113
|
+
clack37.intro(pc40.bold(`WRAPS CLI v${VERSION}`));
|
|
24495
26114
|
console.log("Deploy AWS infrastructure to your account\n");
|
|
24496
26115
|
console.log("Usage: wraps [service] <command> [options]\n");
|
|
24497
26116
|
console.log("Services:");
|
|
24498
|
-
console.log(` ${
|
|
26117
|
+
console.log(` ${pc40.cyan("email")} Email infrastructure (AWS SES)`);
|
|
24499
26118
|
console.log(
|
|
24500
|
-
` ${
|
|
26119
|
+
` ${pc40.cyan("sms")} SMS infrastructure (AWS End User Messaging)`
|
|
24501
26120
|
);
|
|
24502
26121
|
console.log(
|
|
24503
|
-
` ${
|
|
26122
|
+
` ${pc40.cyan("cdn")} CDN infrastructure (AWS S3 + CloudFront)
|
|
24504
26123
|
`
|
|
24505
26124
|
);
|
|
24506
26125
|
console.log("Email Commands:");
|
|
24507
26126
|
console.log(
|
|
24508
|
-
` ${
|
|
26127
|
+
` ${pc40.cyan("email init")} Deploy new email infrastructure`
|
|
24509
26128
|
);
|
|
24510
26129
|
console.log(
|
|
24511
|
-
` ${
|
|
26130
|
+
` ${pc40.cyan("email check")} Check email deliverability for a domain`
|
|
24512
26131
|
);
|
|
24513
26132
|
console.log(
|
|
24514
|
-
` ${
|
|
26133
|
+
` ${pc40.cyan("email connect")} Connect to existing AWS SES`
|
|
24515
26134
|
);
|
|
24516
26135
|
console.log(
|
|
24517
|
-
` ${
|
|
26136
|
+
` ${pc40.cyan("email status")} Show email infrastructure details`
|
|
24518
26137
|
);
|
|
24519
|
-
console.log(` ${
|
|
26138
|
+
console.log(` ${pc40.cyan("email verify")} Verify domain DNS records`);
|
|
24520
26139
|
console.log(
|
|
24521
|
-
` ${
|
|
26140
|
+
` ${pc40.cyan("email sync")} Apply CLI updates to infrastructure`
|
|
24522
26141
|
);
|
|
24523
|
-
console.log(` ${
|
|
26142
|
+
console.log(` ${pc40.cyan("email upgrade")} Add features`);
|
|
24524
26143
|
console.log(
|
|
24525
|
-
` ${
|
|
26144
|
+
` ${pc40.cyan("email restore")} Restore original configuration`
|
|
24526
26145
|
);
|
|
24527
26146
|
console.log(
|
|
24528
|
-
` ${
|
|
26147
|
+
` ${pc40.cyan("email destroy")} Remove email infrastructure`
|
|
24529
26148
|
);
|
|
24530
|
-
console.log(` ${
|
|
24531
|
-
console.log(` ${
|
|
24532
|
-
console.log(` ${
|
|
26149
|
+
console.log(` ${pc40.cyan("email domains add")} Add a domain to SES`);
|
|
26150
|
+
console.log(` ${pc40.cyan("email domains list")} List all domains`);
|
|
26151
|
+
console.log(` ${pc40.cyan("email domains remove")} Remove a domain
|
|
24533
26152
|
`);
|
|
24534
26153
|
console.log("SMS Commands:");
|
|
24535
|
-
console.log(` ${
|
|
26154
|
+
console.log(` ${pc40.cyan("sms init")} Deploy SMS infrastructure`);
|
|
24536
26155
|
console.log(
|
|
24537
|
-
` ${
|
|
26156
|
+
` ${pc40.cyan("sms status")} Show SMS infrastructure details`
|
|
24538
26157
|
);
|
|
24539
|
-
console.log(` ${
|
|
26158
|
+
console.log(` ${pc40.cyan("sms test")} Send a test SMS message`);
|
|
24540
26159
|
console.log(
|
|
24541
|
-
` ${
|
|
26160
|
+
` ${pc40.cyan("sms verify-number")} Verify a destination phone number`
|
|
24542
26161
|
);
|
|
24543
26162
|
console.log(
|
|
24544
|
-
` ${
|
|
26163
|
+
` ${pc40.cyan("sms sync")} Sync infrastructure (update Lambda, etc.)`
|
|
24545
26164
|
);
|
|
24546
|
-
console.log(` ${
|
|
24547
|
-
console.log(` ${
|
|
26165
|
+
console.log(` ${pc40.cyan("sms upgrade")} Upgrade SMS features`);
|
|
26166
|
+
console.log(` ${pc40.cyan("sms register")} Register toll-free number`);
|
|
24548
26167
|
console.log(
|
|
24549
|
-
` ${
|
|
26168
|
+
` ${pc40.cyan("sms destroy")} Remove SMS infrastructure
|
|
24550
26169
|
`
|
|
24551
26170
|
);
|
|
24552
26171
|
console.log("CDN Commands:");
|
|
24553
26172
|
console.log(
|
|
24554
|
-
` ${
|
|
26173
|
+
` ${pc40.cyan("cdn init")} Deploy CDN infrastructure (S3 + CloudFront)`
|
|
24555
26174
|
);
|
|
24556
26175
|
console.log(
|
|
24557
|
-
` ${
|
|
26176
|
+
` ${pc40.cyan("cdn status")} Show CDN infrastructure details`
|
|
24558
26177
|
);
|
|
24559
26178
|
console.log(
|
|
24560
|
-
` ${
|
|
26179
|
+
` ${pc40.cyan("cdn verify")} Check DNS and certificate status`
|
|
24561
26180
|
);
|
|
24562
26181
|
console.log(
|
|
24563
|
-
` ${
|
|
26182
|
+
` ${pc40.cyan("cdn upgrade")} Add custom domain after cert validation`
|
|
24564
26183
|
);
|
|
24565
26184
|
console.log(
|
|
24566
|
-
` ${
|
|
26185
|
+
` ${pc40.cyan("cdn sync")} Sync infrastructure with current config`
|
|
24567
26186
|
);
|
|
24568
26187
|
console.log(
|
|
24569
|
-
` ${
|
|
26188
|
+
` ${pc40.cyan("cdn destroy")} Remove CDN infrastructure
|
|
24570
26189
|
`
|
|
24571
26190
|
);
|
|
24572
26191
|
console.log("Local Development:");
|
|
24573
26192
|
console.log(
|
|
24574
|
-
` ${
|
|
26193
|
+
` ${pc40.cyan("console")} Start local web console
|
|
24575
26194
|
`
|
|
24576
26195
|
);
|
|
24577
26196
|
console.log("Platform:");
|
|
24578
26197
|
console.log(
|
|
24579
|
-
` ${
|
|
26198
|
+
` ${pc40.cyan("platform")} Show platform info and pricing`
|
|
26199
|
+
);
|
|
26200
|
+
console.log(
|
|
26201
|
+
` ${pc40.cyan("platform connect")} Connect to Wraps Platform (events + IAM)`
|
|
24580
26202
|
);
|
|
24581
26203
|
console.log(
|
|
24582
|
-
` ${
|
|
26204
|
+
` ${pc40.cyan("platform update-role")} Update platform IAM permissions
|
|
24583
26205
|
`
|
|
24584
26206
|
);
|
|
24585
26207
|
console.log("AWS Setup:");
|
|
24586
26208
|
console.log(
|
|
24587
|
-
` ${
|
|
26209
|
+
` ${pc40.cyan("aws setup")} Interactive AWS setup wizard`
|
|
24588
26210
|
);
|
|
24589
26211
|
console.log(
|
|
24590
|
-
` ${
|
|
26212
|
+
` ${pc40.cyan("aws doctor")} Diagnose AWS configuration issues
|
|
24591
26213
|
`
|
|
24592
26214
|
);
|
|
24593
26215
|
console.log("Global Commands:");
|
|
24594
|
-
console.log(` ${
|
|
24595
|
-
console.log(` ${
|
|
24596
|
-
console.log(` ${
|
|
24597
|
-
console.log(` ${
|
|
26216
|
+
console.log(` ${pc40.cyan("status")} Show overview of all services`);
|
|
26217
|
+
console.log(` ${pc40.cyan("destroy")} Remove deployed infrastructure`);
|
|
26218
|
+
console.log(` ${pc40.cyan("permissions")} Show required AWS IAM permissions`);
|
|
26219
|
+
console.log(` ${pc40.cyan("completion")} Generate shell completion script`);
|
|
24598
26220
|
console.log(
|
|
24599
|
-
` ${
|
|
26221
|
+
` ${pc40.cyan("telemetry")} Manage anonymous telemetry settings`
|
|
24600
26222
|
);
|
|
24601
|
-
console.log(` ${
|
|
26223
|
+
console.log(` ${pc40.cyan("news")} Show recent Wraps updates`);
|
|
24602
26224
|
console.log(
|
|
24603
|
-
` ${
|
|
26225
|
+
` ${pc40.cyan("support")} Get help and support contact info
|
|
24604
26226
|
`
|
|
24605
26227
|
);
|
|
24606
26228
|
console.log("Options:");
|
|
24607
26229
|
console.log(
|
|
24608
|
-
` ${
|
|
26230
|
+
` ${pc40.dim("-p, --provider")} Hosting provider (vercel, aws, railway, other)`
|
|
24609
26231
|
);
|
|
24610
|
-
console.log(` ${
|
|
24611
|
-
console.log(` ${
|
|
24612
|
-
console.log(` ${
|
|
24613
|
-
console.log(` ${
|
|
24614
|
-
console.log(` ${
|
|
24615
|
-
console.log(` ${
|
|
26232
|
+
console.log(` ${pc40.dim("-r, --region")} AWS region`);
|
|
26233
|
+
console.log(` ${pc40.dim("-d, --domain")} Domain name`);
|
|
26234
|
+
console.log(` ${pc40.dim("--account")} AWS account ID or alias`);
|
|
26235
|
+
console.log(` ${pc40.dim("--preset")} Configuration preset`);
|
|
26236
|
+
console.log(` ${pc40.dim("-y, --yes")} Skip confirmation prompts`);
|
|
26237
|
+
console.log(` ${pc40.dim("-f, --force")} Force destructive operations`);
|
|
24616
26238
|
console.log(
|
|
24617
|
-
` ${
|
|
26239
|
+
` ${pc40.dim("--preview")} Preview changes without deploying`
|
|
24618
26240
|
);
|
|
24619
|
-
console.log(` ${
|
|
26241
|
+
console.log(` ${pc40.dim("-v, --version")} Show version number
|
|
24620
26242
|
`);
|
|
24621
26243
|
console.log(
|
|
24622
|
-
`Run ${
|
|
26244
|
+
`Run ${pc40.cyan("wraps <service> <command> --help")} for more information.
|
|
24623
26245
|
`
|
|
24624
26246
|
);
|
|
24625
26247
|
process.exit(0);
|
|
@@ -24765,7 +26387,7 @@ var flags = args.parse(process.argv);
|
|
|
24765
26387
|
var [primaryCommand, subCommand] = args.sub;
|
|
24766
26388
|
if (!primaryCommand) {
|
|
24767
26389
|
async function interactiveMenu() {
|
|
24768
|
-
clack37.intro(
|
|
26390
|
+
clack37.intro(pc40.bold(`WRAPS CLI v${VERSION}`));
|
|
24769
26391
|
console.log(" Deploy AWS infrastructure to your account.\n");
|
|
24770
26392
|
const action = await clack37.select({
|
|
24771
26393
|
message: "What would you like to do?",
|
|
@@ -24879,20 +26501,20 @@ async function run() {
|
|
|
24879
26501
|
const telemetry = getTelemetryClient();
|
|
24880
26502
|
if (telemetry.shouldShowNotification()) {
|
|
24881
26503
|
console.log();
|
|
24882
|
-
clack37.log.info(
|
|
26504
|
+
clack37.log.info(pc40.bold("Anonymous Telemetry"));
|
|
24883
26505
|
console.log(
|
|
24884
|
-
` Wraps collects ${
|
|
26506
|
+
` Wraps collects ${pc40.cyan("anonymous usage data")} to improve the CLI.`
|
|
24885
26507
|
);
|
|
24886
26508
|
console.log(
|
|
24887
|
-
` We ${
|
|
26509
|
+
` We ${pc40.bold("never")} collect: domains, AWS credentials, email content, or PII.`
|
|
24888
26510
|
);
|
|
24889
26511
|
console.log(
|
|
24890
|
-
` We ${
|
|
26512
|
+
` We ${pc40.bold("only")} collect: command names, success/failure, CLI version, OS.`
|
|
24891
26513
|
);
|
|
24892
26514
|
console.log();
|
|
24893
|
-
console.log(` Opt-out anytime: ${
|
|
24894
|
-
console.log(` Or set: ${
|
|
24895
|
-
console.log(` Learn more: ${
|
|
26515
|
+
console.log(` Opt-out anytime: ${pc40.cyan("wraps telemetry disable")}`);
|
|
26516
|
+
console.log(` Or set: ${pc40.cyan("WRAPS_TELEMETRY_DISABLED=1")}`);
|
|
26517
|
+
console.log(` Learn more: ${pc40.cyan("https://wraps.dev/docs")}`);
|
|
24896
26518
|
console.log();
|
|
24897
26519
|
telemetry.markNotificationShown();
|
|
24898
26520
|
}
|
|
@@ -24962,7 +26584,7 @@ async function run() {
|
|
|
24962
26584
|
clack37.log.error("--domain flag is required");
|
|
24963
26585
|
console.log(
|
|
24964
26586
|
`
|
|
24965
|
-
Usage: ${
|
|
26587
|
+
Usage: ${pc40.cyan("wraps email verify --domain yourapp.com")}
|
|
24966
26588
|
`
|
|
24967
26589
|
);
|
|
24968
26590
|
process.exit(1);
|
|
@@ -24978,7 +26600,7 @@ Usage: ${pc39.cyan("wraps email verify --domain yourapp.com")}
|
|
|
24978
26600
|
clack37.log.error("--domain flag is required");
|
|
24979
26601
|
console.log(
|
|
24980
26602
|
`
|
|
24981
|
-
Usage: ${
|
|
26603
|
+
Usage: ${pc40.cyan("wraps email domains add --domain yourapp.com")}
|
|
24982
26604
|
`
|
|
24983
26605
|
);
|
|
24984
26606
|
process.exit(1);
|
|
@@ -24994,7 +26616,7 @@ Usage: ${pc39.cyan("wraps email domains add --domain yourapp.com")}
|
|
|
24994
26616
|
clack37.log.error("--domain flag is required");
|
|
24995
26617
|
console.log(
|
|
24996
26618
|
`
|
|
24997
|
-
Usage: ${
|
|
26619
|
+
Usage: ${pc40.cyan("wraps email domains verify --domain yourapp.com")}
|
|
24998
26620
|
`
|
|
24999
26621
|
);
|
|
25000
26622
|
process.exit(1);
|
|
@@ -25007,7 +26629,7 @@ Usage: ${pc39.cyan("wraps email domains verify --domain yourapp.com")}
|
|
|
25007
26629
|
clack37.log.error("--domain flag is required");
|
|
25008
26630
|
console.log(
|
|
25009
26631
|
`
|
|
25010
|
-
Usage: ${
|
|
26632
|
+
Usage: ${pc40.cyan("wraps email domains get-dkim --domain yourapp.com")}
|
|
25011
26633
|
`
|
|
25012
26634
|
);
|
|
25013
26635
|
process.exit(1);
|
|
@@ -25020,7 +26642,7 @@ Usage: ${pc39.cyan("wraps email domains get-dkim --domain yourapp.com")}
|
|
|
25020
26642
|
clack37.log.error("--domain flag is required");
|
|
25021
26643
|
console.log(
|
|
25022
26644
|
`
|
|
25023
|
-
Usage: ${
|
|
26645
|
+
Usage: ${pc40.cyan("wraps email domains remove --domain yourapp.com --force")}
|
|
25024
26646
|
`
|
|
25025
26647
|
);
|
|
25026
26648
|
process.exit(1);
|
|
@@ -25037,7 +26659,7 @@ Usage: ${pc39.cyan("wraps email domains remove --domain yourapp.com --force")}
|
|
|
25037
26659
|
);
|
|
25038
26660
|
console.log(
|
|
25039
26661
|
`
|
|
25040
|
-
Available commands: ${
|
|
26662
|
+
Available commands: ${pc40.cyan("add")}, ${pc40.cyan("list")}, ${pc40.cyan("verify")}, ${pc40.cyan("get-dkim")}, ${pc40.cyan("remove")}
|
|
25041
26663
|
`
|
|
25042
26664
|
);
|
|
25043
26665
|
process.exit(1);
|
|
@@ -25055,7 +26677,7 @@ Available commands: ${pc39.cyan("add")}, ${pc39.cyan("list")}, ${pc39.cyan("veri
|
|
|
25055
26677
|
clack37.log.error(`Unknown email command: ${subCommand}`);
|
|
25056
26678
|
console.log(
|
|
25057
26679
|
`
|
|
25058
|
-
Run ${
|
|
26680
|
+
Run ${pc40.cyan("wraps --help")} for available commands.
|
|
25059
26681
|
`
|
|
25060
26682
|
);
|
|
25061
26683
|
process.exit(1);
|
|
@@ -25126,7 +26748,7 @@ Run ${pc39.cyan("wraps --help")} for available commands.
|
|
|
25126
26748
|
clack37.log.error(`Unknown sms command: ${subCommand}`);
|
|
25127
26749
|
console.log(
|
|
25128
26750
|
`
|
|
25129
|
-
Run ${
|
|
26751
|
+
Run ${pc40.cyan("wraps --help")} for available commands.
|
|
25130
26752
|
`
|
|
25131
26753
|
);
|
|
25132
26754
|
process.exit(1);
|
|
@@ -25185,7 +26807,7 @@ Run ${pc39.cyan("wraps --help")} for available commands.
|
|
|
25185
26807
|
clack37.log.error(`Unknown cdn command: ${subCommand}`);
|
|
25186
26808
|
console.log(
|
|
25187
26809
|
`
|
|
25188
|
-
Run ${
|
|
26810
|
+
Run ${pc40.cyan("wraps --help")} for available commands.
|
|
25189
26811
|
`
|
|
25190
26812
|
);
|
|
25191
26813
|
process.exit(1);
|
|
@@ -25210,6 +26832,13 @@ Run ${pc39.cyan("wraps --help")} for available commands.
|
|
|
25210
26832
|
return;
|
|
25211
26833
|
}
|
|
25212
26834
|
switch (subCommand) {
|
|
26835
|
+
case "connect":
|
|
26836
|
+
await connect3({
|
|
26837
|
+
region: flags.region,
|
|
26838
|
+
force: flags.force,
|
|
26839
|
+
yes: flags.yes
|
|
26840
|
+
});
|
|
26841
|
+
break;
|
|
25213
26842
|
case "update-role":
|
|
25214
26843
|
await updateRole({
|
|
25215
26844
|
region: flags.region,
|
|
@@ -25218,11 +26847,13 @@ Run ${pc39.cyan("wraps --help")} for available commands.
|
|
|
25218
26847
|
break;
|
|
25219
26848
|
default:
|
|
25220
26849
|
clack37.log.error(`Unknown platform command: ${subCommand}`);
|
|
25221
|
-
console.log(`
|
|
25222
|
-
Available commands: ${pc39.cyan("update-role")}
|
|
25223
|
-
`);
|
|
25224
26850
|
console.log(
|
|
25225
|
-
`
|
|
26851
|
+
`
|
|
26852
|
+
Available commands: ${pc40.cyan("connect")}, ${pc40.cyan("update-role")}
|
|
26853
|
+
`
|
|
26854
|
+
);
|
|
26855
|
+
console.log(
|
|
26856
|
+
`Run ${pc40.cyan("wraps platform")} for more information.
|
|
25226
26857
|
`
|
|
25227
26858
|
);
|
|
25228
26859
|
process.exit(1);
|
|
@@ -25249,10 +26880,10 @@ Available commands: ${pc39.cyan("update-role")}
|
|
|
25249
26880
|
clack37.log.error(`Unknown aws command: ${subCommand}`);
|
|
25250
26881
|
console.log(
|
|
25251
26882
|
`
|
|
25252
|
-
Available commands: ${
|
|
26883
|
+
Available commands: ${pc40.cyan("setup")}, ${pc40.cyan("doctor")}
|
|
25253
26884
|
`
|
|
25254
26885
|
);
|
|
25255
|
-
console.log(`Run ${
|
|
26886
|
+
console.log(`Run ${pc40.cyan("wraps --help")} for more information.
|
|
25256
26887
|
`);
|
|
25257
26888
|
process.exit(1);
|
|
25258
26889
|
}
|
|
@@ -25315,7 +26946,7 @@ Available commands: ${pc39.cyan("setup")}, ${pc39.cyan("doctor")}
|
|
|
25315
26946
|
clack37.log.error(`Unknown telemetry command: ${subCommand}`);
|
|
25316
26947
|
console.log(
|
|
25317
26948
|
`
|
|
25318
|
-
Available commands: ${
|
|
26949
|
+
Available commands: ${pc40.cyan("enable")}, ${pc40.cyan("disable")}, ${pc40.cyan("status")}
|
|
25319
26950
|
`
|
|
25320
26951
|
);
|
|
25321
26952
|
process.exit(1);
|
|
@@ -25338,7 +26969,7 @@ Please specify a command for ${primaryCommand} service.
|
|
|
25338
26969
|
clack37.log.error(`Unknown command: ${primaryCommand}`);
|
|
25339
26970
|
console.log(
|
|
25340
26971
|
`
|
|
25341
|
-
Run ${
|
|
26972
|
+
Run ${pc40.cyan("wraps --help")} for available commands.
|
|
25342
26973
|
`
|
|
25343
26974
|
);
|
|
25344
26975
|
process.exit(1);
|