@wraps.dev/cli 2.12.3 → 2.13.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 +295 -282
- package/dist/cli.js +702 -192
- package/dist/cli.js.map +1 -1
- package/dist/lambda/event-processor/.bundled +1 -1
- package/dist/lambda/inbound-processor/.bundled +1 -1
- package/dist/lambda/sms-event-processor/.bundled +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2068,6 +2068,17 @@ function calculateAlertingCost(config2) {
|
|
|
2068
2068
|
description: `Reputation alerts (${numAlarms} CloudWatch alarms)`
|
|
2069
2069
|
};
|
|
2070
2070
|
}
|
|
2071
|
+
function calculateUserWebhookCost(config2, emailsPerMonth) {
|
|
2072
|
+
if (!config2.userWebhook?.enabled) {
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
const estimatedInvocations = emailsPerMonth * 2;
|
|
2076
|
+
const monthlyCost = estimatedInvocations / 1e6 * AWS_PRICING.EVENTBRIDGE_API_DEST_PER_MILLION;
|
|
2077
|
+
return {
|
|
2078
|
+
monthly: monthlyCost,
|
|
2079
|
+
description: "User webhook (API Destination invocations)"
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2071
2082
|
function calculateCosts(config2, emailsPerMonth = 1e4) {
|
|
2072
2083
|
const tracking = calculateTrackingCost(config2);
|
|
2073
2084
|
const reputationMetrics = calculateReputationMetricsCost(config2);
|
|
@@ -2078,8 +2089,9 @@ function calculateCosts(config2, emailsPerMonth = 1e4) {
|
|
|
2078
2089
|
const waf = calculateWafCost(config2, emailsPerMonth);
|
|
2079
2090
|
const smtpCredentials = calculateSMTPCredentialsCost(config2);
|
|
2080
2091
|
const alerts = calculateAlertingCost(config2);
|
|
2092
|
+
const userWebhook = calculateUserWebhookCost(config2, emailsPerMonth);
|
|
2081
2093
|
const sesEmailCost = Math.max(0, emailsPerMonth - FREE_TIER.SES_EMAILS) * AWS_PRICING.SES_PER_EMAIL;
|
|
2082
|
-
const totalMonthlyCost = sesEmailCost + (tracking?.monthly || 0) + (reputationMetrics?.monthly || 0) + (eventTracking?.monthly || 0) + (dynamoDBHistory?.monthly || 0) + (emailArchiving?.monthly || 0) + (dedicatedIp?.monthly || 0) + (waf?.monthly || 0) + (smtpCredentials?.monthly || 0) + (alerts?.monthly || 0);
|
|
2094
|
+
const totalMonthlyCost = sesEmailCost + (tracking?.monthly || 0) + (reputationMetrics?.monthly || 0) + (eventTracking?.monthly || 0) + (dynamoDBHistory?.monthly || 0) + (emailArchiving?.monthly || 0) + (dedicatedIp?.monthly || 0) + (waf?.monthly || 0) + (smtpCredentials?.monthly || 0) + (alerts?.monthly || 0) + (userWebhook?.monthly || 0);
|
|
2083
2095
|
return {
|
|
2084
2096
|
tracking,
|
|
2085
2097
|
reputationMetrics,
|
|
@@ -2090,6 +2102,7 @@ function calculateCosts(config2, emailsPerMonth = 1e4) {
|
|
|
2090
2102
|
waf,
|
|
2091
2103
|
smtpCredentials,
|
|
2092
2104
|
alerts,
|
|
2105
|
+
userWebhook,
|
|
2093
2106
|
total: {
|
|
2094
2107
|
monthly: totalMonthlyCost,
|
|
2095
2108
|
perEmail: AWS_PRICING.SES_PER_EMAIL,
|
|
@@ -2160,6 +2173,11 @@ function getCostSummary(config2, emailsPerMonth = 1e4) {
|
|
|
2160
2173
|
` - ${costs.alerts.description}: ${formatCost(costs.alerts.monthly)}`
|
|
2161
2174
|
);
|
|
2162
2175
|
}
|
|
2176
|
+
if (costs.userWebhook) {
|
|
2177
|
+
lines.push(
|
|
2178
|
+
` - ${costs.userWebhook.description}: ${formatCost(costs.userWebhook.monthly)}`
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2163
2181
|
return lines.join("\n");
|
|
2164
2182
|
}
|
|
2165
2183
|
var AWS_PRICING, FREE_TIER;
|
|
@@ -2209,8 +2227,11 @@ var init_costs = __esm({
|
|
|
2209
2227
|
// $5.00 per Web ACL per month
|
|
2210
2228
|
WAF_RULE_PER_MONTH: 1,
|
|
2211
2229
|
// $1.00 per rule per month
|
|
2212
|
-
WAF_REQUESTS_PER_MILLION: 0.6
|
|
2230
|
+
WAF_REQUESTS_PER_MILLION: 0.6,
|
|
2213
2231
|
// $0.60 per million requests
|
|
2232
|
+
// EventBridge API Destination pricing
|
|
2233
|
+
EVENTBRIDGE_API_DEST_PER_MILLION: 0.2
|
|
2234
|
+
// $0.20 per million API Destination invocations
|
|
2214
2235
|
};
|
|
2215
2236
|
FREE_TIER = {
|
|
2216
2237
|
// SES: 3,000 emails/month for first 12 months (new AWS accounts only)
|
|
@@ -5068,10 +5089,10 @@ var init_constants = __esm({
|
|
|
5068
5089
|
async function roleExists(roleName) {
|
|
5069
5090
|
try {
|
|
5070
5091
|
const { IAMClient: IAMClient4, GetRoleCommand: GetRoleCommand3 } = await import("@aws-sdk/client-iam");
|
|
5071
|
-
const
|
|
5092
|
+
const iam10 = new IAMClient4({
|
|
5072
5093
|
region: getDefaultRegion()
|
|
5073
5094
|
});
|
|
5074
|
-
await
|
|
5095
|
+
await iam10.send(new GetRoleCommand3({ RoleName: roleName }));
|
|
5075
5096
|
return true;
|
|
5076
5097
|
} catch (error) {
|
|
5077
5098
|
if (error instanceof Error && (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity")) {
|
|
@@ -5350,6 +5371,104 @@ var init_dist = __esm({
|
|
|
5350
5371
|
}
|
|
5351
5372
|
});
|
|
5352
5373
|
|
|
5374
|
+
// src/infrastructure/resources/eventbridge-user-webhook.ts
|
|
5375
|
+
var eventbridge_user_webhook_exports = {};
|
|
5376
|
+
__export(eventbridge_user_webhook_exports, {
|
|
5377
|
+
createUserWebhookResources: () => createUserWebhookResources
|
|
5378
|
+
});
|
|
5379
|
+
import * as aws6 from "@pulumi/aws";
|
|
5380
|
+
function createUserWebhookResources(config2) {
|
|
5381
|
+
const connection = new aws6.cloudwatch.EventConnection(
|
|
5382
|
+
"wraps-user-webhook-connection",
|
|
5383
|
+
{
|
|
5384
|
+
name: "wraps-user-webhook-connection",
|
|
5385
|
+
description: "Connection for user webhook endpoint",
|
|
5386
|
+
authorizationType: "API_KEY",
|
|
5387
|
+
authParameters: {
|
|
5388
|
+
apiKey: {
|
|
5389
|
+
key: "X-Wraps-Signature",
|
|
5390
|
+
value: config2.secret
|
|
5391
|
+
}
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
);
|
|
5395
|
+
const apiDestination = new aws6.cloudwatch.EventApiDestination(
|
|
5396
|
+
"wraps-user-webhook-destination",
|
|
5397
|
+
{
|
|
5398
|
+
name: "wraps-user-webhook-destination",
|
|
5399
|
+
description: "Send SES events to user webhook endpoint",
|
|
5400
|
+
connectionArn: connection.arn,
|
|
5401
|
+
httpMethod: "POST",
|
|
5402
|
+
invocationEndpoint: config2.url,
|
|
5403
|
+
invocationRateLimitPerSecond: 300
|
|
5404
|
+
}
|
|
5405
|
+
);
|
|
5406
|
+
const role = new aws6.iam.Role("wraps-user-webhook-role", {
|
|
5407
|
+
name: "wraps-user-webhook-role",
|
|
5408
|
+
assumeRolePolicy: JSON.stringify({
|
|
5409
|
+
Version: "2012-10-17",
|
|
5410
|
+
Statement: [
|
|
5411
|
+
{
|
|
5412
|
+
Effect: "Allow",
|
|
5413
|
+
Principal: {
|
|
5414
|
+
Service: "events.amazonaws.com"
|
|
5415
|
+
},
|
|
5416
|
+
Action: "sts:AssumeRole"
|
|
5417
|
+
}
|
|
5418
|
+
]
|
|
5419
|
+
}),
|
|
5420
|
+
tags: {
|
|
5421
|
+
ManagedBy: "wraps-cli",
|
|
5422
|
+
Service: "email"
|
|
5423
|
+
}
|
|
5424
|
+
});
|
|
5425
|
+
new aws6.iam.RolePolicy("wraps-user-webhook-policy", {
|
|
5426
|
+
role: role.name,
|
|
5427
|
+
policy: apiDestination.arn.apply(
|
|
5428
|
+
(destArn) => JSON.stringify({
|
|
5429
|
+
Version: "2012-10-17",
|
|
5430
|
+
Statement: [
|
|
5431
|
+
{
|
|
5432
|
+
Effect: "Allow",
|
|
5433
|
+
Action: ["events:InvokeApiDestination"],
|
|
5434
|
+
Resource: destArn
|
|
5435
|
+
}
|
|
5436
|
+
]
|
|
5437
|
+
})
|
|
5438
|
+
)
|
|
5439
|
+
});
|
|
5440
|
+
const target = new aws6.cloudwatch.EventTarget(
|
|
5441
|
+
"wraps-user-webhook-target",
|
|
5442
|
+
{
|
|
5443
|
+
rule: config2.ruleName,
|
|
5444
|
+
eventBusName: config2.eventBusName,
|
|
5445
|
+
arn: apiDestination.arn,
|
|
5446
|
+
roleArn: role.arn,
|
|
5447
|
+
inputTransformer: {
|
|
5448
|
+
inputPaths: {
|
|
5449
|
+
event: "$.detail.eventType",
|
|
5450
|
+
detail: "$.detail",
|
|
5451
|
+
timestamp: "$.time",
|
|
5452
|
+
messageId: "$.detail.mail.messageId"
|
|
5453
|
+
},
|
|
5454
|
+
inputTemplate: '{"event":<event>,"detail":<detail>,"timestamp":<timestamp>,"messageId":<messageId>,"source":"wraps"}'
|
|
5455
|
+
}
|
|
5456
|
+
}
|
|
5457
|
+
);
|
|
5458
|
+
return {
|
|
5459
|
+
connection,
|
|
5460
|
+
apiDestination,
|
|
5461
|
+
target,
|
|
5462
|
+
role
|
|
5463
|
+
};
|
|
5464
|
+
}
|
|
5465
|
+
var init_eventbridge_user_webhook = __esm({
|
|
5466
|
+
"src/infrastructure/resources/eventbridge-user-webhook.ts"() {
|
|
5467
|
+
"use strict";
|
|
5468
|
+
init_esm_shims();
|
|
5469
|
+
}
|
|
5470
|
+
});
|
|
5471
|
+
|
|
5353
5472
|
// src/infrastructure/resources/lambda.ts
|
|
5354
5473
|
var lambda_exports = {};
|
|
5355
5474
|
__export(lambda_exports, {
|
|
@@ -5362,7 +5481,7 @@ import { builtinModules } from "module";
|
|
|
5362
5481
|
import { tmpdir } from "os";
|
|
5363
5482
|
import { dirname as dirname2, join as join7 } from "path";
|
|
5364
5483
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5365
|
-
import * as
|
|
5484
|
+
import * as aws8 from "@pulumi/aws";
|
|
5366
5485
|
import * as pulumi11 from "@pulumi/pulumi";
|
|
5367
5486
|
import { build } from "esbuild";
|
|
5368
5487
|
function getPackageRoot() {
|
|
@@ -5437,7 +5556,7 @@ Try running: pnpm build`
|
|
|
5437
5556
|
}
|
|
5438
5557
|
async function deployLambdaFunctions(config2) {
|
|
5439
5558
|
const eventProcessorCode = await getLambdaCode("event-processor");
|
|
5440
|
-
const lambdaRole = new
|
|
5559
|
+
const lambdaRole = new aws8.iam.Role("wraps-email-lambda-role", {
|
|
5441
5560
|
assumeRolePolicy: JSON.stringify({
|
|
5442
5561
|
Version: "2012-10-17",
|
|
5443
5562
|
Statement: [
|
|
@@ -5453,11 +5572,11 @@ async function deployLambdaFunctions(config2) {
|
|
|
5453
5572
|
Service: "email"
|
|
5454
5573
|
}
|
|
5455
5574
|
});
|
|
5456
|
-
new
|
|
5575
|
+
new aws8.iam.RolePolicyAttachment("wraps-email-lambda-basic-execution", {
|
|
5457
5576
|
role: lambdaRole.name,
|
|
5458
5577
|
policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
5459
5578
|
});
|
|
5460
|
-
new
|
|
5579
|
+
new aws8.iam.RolePolicy("wraps-email-lambda-policy", {
|
|
5461
5580
|
role: lambdaRole.name,
|
|
5462
5581
|
policy: pulumi11.all([config2.tableName, config2.queueArn]).apply(
|
|
5463
5582
|
([tableName, queueArn]) => JSON.stringify({
|
|
@@ -5501,7 +5620,7 @@ async function deployLambdaFunctions(config2) {
|
|
|
5501
5620
|
RETENTION_DAYS: config2.retentionDays.toString()
|
|
5502
5621
|
}
|
|
5503
5622
|
};
|
|
5504
|
-
const eventProcessor = exists ? new
|
|
5623
|
+
const eventProcessor = exists ? new aws8.lambda.Function(
|
|
5505
5624
|
functionName,
|
|
5506
5625
|
{
|
|
5507
5626
|
name: functionName,
|
|
@@ -5523,7 +5642,7 @@ async function deployLambdaFunctions(config2) {
|
|
|
5523
5642
|
import: functionName
|
|
5524
5643
|
// Import existing function
|
|
5525
5644
|
}
|
|
5526
|
-
) : new
|
|
5645
|
+
) : new aws8.lambda.Function(functionName, {
|
|
5527
5646
|
name: functionName,
|
|
5528
5647
|
runtime: "nodejs24.x",
|
|
5529
5648
|
handler: "index.handler",
|
|
@@ -5553,14 +5672,14 @@ async function deployLambdaFunctions(config2) {
|
|
|
5553
5672
|
functionResponseTypes: ["ReportBatchItemFailures"]
|
|
5554
5673
|
// Enable partial batch responses
|
|
5555
5674
|
};
|
|
5556
|
-
const eventSourceMapping = existingMappingUuid ? new
|
|
5675
|
+
const eventSourceMapping = existingMappingUuid ? new aws8.lambda.EventSourceMapping(
|
|
5557
5676
|
"wraps-email-event-source-mapping",
|
|
5558
5677
|
mappingConfig,
|
|
5559
5678
|
{
|
|
5560
5679
|
import: existingMappingUuid
|
|
5561
5680
|
// Import with the UUID
|
|
5562
5681
|
}
|
|
5563
|
-
) : new
|
|
5682
|
+
) : new aws8.lambda.EventSourceMapping(
|
|
5564
5683
|
"wraps-email-event-source-mapping",
|
|
5565
5684
|
mappingConfig
|
|
5566
5685
|
);
|
|
@@ -5587,7 +5706,7 @@ __export(acm_exports, {
|
|
|
5587
5706
|
createACMCertificate: () => createACMCertificate
|
|
5588
5707
|
});
|
|
5589
5708
|
import { ACMClient as ACMClient2, DescribeCertificateCommand as DescribeCertificateCommand2 } from "@aws-sdk/client-acm";
|
|
5590
|
-
import * as
|
|
5709
|
+
import * as aws12 from "@pulumi/aws";
|
|
5591
5710
|
async function checkCertificateValidation(domain) {
|
|
5592
5711
|
try {
|
|
5593
5712
|
const acm3 = new ACMClient2({ region: "us-east-1" });
|
|
@@ -5614,10 +5733,10 @@ async function checkCertificateValidation(domain) {
|
|
|
5614
5733
|
}
|
|
5615
5734
|
}
|
|
5616
5735
|
async function createACMCertificate(config2) {
|
|
5617
|
-
const usEast1Provider = new
|
|
5736
|
+
const usEast1Provider = new aws12.Provider("acm-us-east-1", {
|
|
5618
5737
|
region: "us-east-1"
|
|
5619
5738
|
});
|
|
5620
|
-
const certificate = new
|
|
5739
|
+
const certificate = new aws12.acm.Certificate(
|
|
5621
5740
|
"wraps-email-tracking-cert",
|
|
5622
5741
|
{
|
|
5623
5742
|
domainName: config2.domain,
|
|
@@ -5640,7 +5759,7 @@ async function createACMCertificate(config2) {
|
|
|
5640
5759
|
);
|
|
5641
5760
|
let certificateValidation;
|
|
5642
5761
|
if (config2.hostedZoneId) {
|
|
5643
|
-
const validationRecord = new
|
|
5762
|
+
const validationRecord = new aws12.route53.Record(
|
|
5644
5763
|
"wraps-email-tracking-cert-validation",
|
|
5645
5764
|
{
|
|
5646
5765
|
zoneId: config2.hostedZoneId,
|
|
@@ -5650,7 +5769,7 @@ async function createACMCertificate(config2) {
|
|
|
5650
5769
|
ttl: 60
|
|
5651
5770
|
}
|
|
5652
5771
|
);
|
|
5653
|
-
certificateValidation = new
|
|
5772
|
+
certificateValidation = new aws12.acm.CertificateValidation(
|
|
5654
5773
|
"wraps-email-tracking-cert-validation-waiter",
|
|
5655
5774
|
{
|
|
5656
5775
|
certificateArn: certificate.arn,
|
|
@@ -5679,7 +5798,7 @@ var cloudfront_exports = {};
|
|
|
5679
5798
|
__export(cloudfront_exports, {
|
|
5680
5799
|
createCloudFrontTracking: () => createCloudFrontTracking
|
|
5681
5800
|
});
|
|
5682
|
-
import * as
|
|
5801
|
+
import * as aws13 from "@pulumi/aws";
|
|
5683
5802
|
async function findDistributionByAlias(alias) {
|
|
5684
5803
|
try {
|
|
5685
5804
|
const { CloudFrontClient, ListDistributionsCommand } = await import("@aws-sdk/client-cloudfront");
|
|
@@ -5695,10 +5814,10 @@ async function findDistributionByAlias(alias) {
|
|
|
5695
5814
|
}
|
|
5696
5815
|
}
|
|
5697
5816
|
async function createWAFWebACL() {
|
|
5698
|
-
const usEast1Provider = new
|
|
5817
|
+
const usEast1Provider = new aws13.Provider("waf-us-east-1", {
|
|
5699
5818
|
region: "us-east-1"
|
|
5700
5819
|
});
|
|
5701
|
-
const webAcl = new
|
|
5820
|
+
const webAcl = new aws13.wafv2.WebAcl(
|
|
5702
5821
|
"wraps-email-tracking-waf",
|
|
5703
5822
|
{
|
|
5704
5823
|
scope: "CLOUDFRONT",
|
|
@@ -5813,14 +5932,14 @@ async function createCloudFrontTracking(config2) {
|
|
|
5813
5932
|
Description: "Wraps email tracking CloudFront distribution"
|
|
5814
5933
|
}
|
|
5815
5934
|
};
|
|
5816
|
-
const distribution = existingDistributionId ? new
|
|
5935
|
+
const distribution = existingDistributionId ? new aws13.cloudfront.Distribution(
|
|
5817
5936
|
"wraps-email-tracking-cdn",
|
|
5818
5937
|
distributionConfig,
|
|
5819
5938
|
{
|
|
5820
5939
|
import: existingDistributionId
|
|
5821
5940
|
// Import existing distribution
|
|
5822
5941
|
}
|
|
5823
|
-
) : new
|
|
5942
|
+
) : new aws13.cloudfront.Distribution(
|
|
5824
5943
|
"wraps-email-tracking-cdn",
|
|
5825
5944
|
distributionConfig
|
|
5826
5945
|
);
|
|
@@ -6002,10 +6121,10 @@ var s3_inbound_exports = {};
|
|
|
6002
6121
|
__export(s3_inbound_exports, {
|
|
6003
6122
|
createS3InboundResources: () => createS3InboundResources
|
|
6004
6123
|
});
|
|
6005
|
-
import * as
|
|
6124
|
+
import * as aws14 from "@pulumi/aws";
|
|
6006
6125
|
async function createS3InboundResources(config2) {
|
|
6007
6126
|
const bucketName = `wraps-inbound-${config2.accountId}-${config2.region}`;
|
|
6008
|
-
const bucket = new
|
|
6127
|
+
const bucket = new aws14.s3.BucketV2("wraps-inbound-bucket", {
|
|
6009
6128
|
bucket: bucketName,
|
|
6010
6129
|
forceDestroy: true,
|
|
6011
6130
|
tags: {
|
|
@@ -6013,14 +6132,14 @@ async function createS3InboundResources(config2) {
|
|
|
6013
6132
|
Service: "email-inbound"
|
|
6014
6133
|
}
|
|
6015
6134
|
});
|
|
6016
|
-
new
|
|
6135
|
+
new aws14.s3.BucketPublicAccessBlock("wraps-inbound-public-access-block", {
|
|
6017
6136
|
bucket: bucket.id,
|
|
6018
6137
|
blockPublicAcls: true,
|
|
6019
6138
|
blockPublicPolicy: true,
|
|
6020
6139
|
ignorePublicAcls: true,
|
|
6021
6140
|
restrictPublicBuckets: true
|
|
6022
6141
|
});
|
|
6023
|
-
new
|
|
6142
|
+
new aws14.s3.BucketServerSideEncryptionConfigurationV2(
|
|
6024
6143
|
"wraps-inbound-encryption",
|
|
6025
6144
|
{
|
|
6026
6145
|
bucket: bucket.id,
|
|
@@ -6036,7 +6155,7 @@ async function createS3InboundResources(config2) {
|
|
|
6036
6155
|
if (config2.retention) {
|
|
6037
6156
|
const retentionDays = retentionToDays2(config2.retention);
|
|
6038
6157
|
if (retentionDays > 0) {
|
|
6039
|
-
new
|
|
6158
|
+
new aws14.s3.BucketLifecycleConfigurationV2("wraps-inbound-lifecycle", {
|
|
6040
6159
|
bucket: bucket.id,
|
|
6041
6160
|
rules: [
|
|
6042
6161
|
{
|
|
@@ -6050,8 +6169,8 @@ async function createS3InboundResources(config2) {
|
|
|
6050
6169
|
});
|
|
6051
6170
|
}
|
|
6052
6171
|
}
|
|
6053
|
-
const identity = await
|
|
6054
|
-
new
|
|
6172
|
+
const identity = await aws14.getCallerIdentity();
|
|
6173
|
+
new aws14.s3.BucketPolicy("wraps-inbound-bucket-policy", {
|
|
6055
6174
|
bucket: bucket.id,
|
|
6056
6175
|
policy: bucket.arn.apply(
|
|
6057
6176
|
(arn) => JSON.stringify({
|
|
@@ -6093,9 +6212,9 @@ var sqs_inbound_exports = {};
|
|
|
6093
6212
|
__export(sqs_inbound_exports, {
|
|
6094
6213
|
createSQSInboundResources: () => createSQSInboundResources
|
|
6095
6214
|
});
|
|
6096
|
-
import * as
|
|
6215
|
+
import * as aws15 from "@pulumi/aws";
|
|
6097
6216
|
function createSQSInboundResources() {
|
|
6098
|
-
const dlq = new
|
|
6217
|
+
const dlq = new aws15.sqs.Queue("wraps-inbound-events-dlq", {
|
|
6099
6218
|
name: "wraps-inbound-events-dlq",
|
|
6100
6219
|
messageRetentionSeconds: 1209600,
|
|
6101
6220
|
// 14 days
|
|
@@ -6105,7 +6224,7 @@ function createSQSInboundResources() {
|
|
|
6105
6224
|
Description: "Dead letter queue for failed inbound email processing"
|
|
6106
6225
|
}
|
|
6107
6226
|
});
|
|
6108
|
-
const queue = new
|
|
6227
|
+
const queue = new aws15.sqs.Queue("wraps-inbound-events", {
|
|
6109
6228
|
name: "wraps-inbound-events",
|
|
6110
6229
|
visibilityTimeoutSeconds: 300,
|
|
6111
6230
|
// Must be >= Lambda timeout (120s) * 2 + buffer
|
|
@@ -6142,11 +6261,11 @@ var lambda_inbound_exports = {};
|
|
|
6142
6261
|
__export(lambda_inbound_exports, {
|
|
6143
6262
|
deployInboundLambda: () => deployInboundLambda
|
|
6144
6263
|
});
|
|
6145
|
-
import * as
|
|
6264
|
+
import * as aws16 from "@pulumi/aws";
|
|
6146
6265
|
import * as pulumi12 from "@pulumi/pulumi";
|
|
6147
6266
|
async function deployInboundLambda(config2) {
|
|
6148
6267
|
const inboundProcessorCode = await getLambdaCode("inbound-processor");
|
|
6149
|
-
const lambdaRole = new
|
|
6268
|
+
const lambdaRole = new aws16.iam.Role("wraps-inbound-lambda-role", {
|
|
6150
6269
|
name: "wraps-inbound-lambda-role",
|
|
6151
6270
|
assumeRolePolicy: JSON.stringify({
|
|
6152
6271
|
Version: "2012-10-17",
|
|
@@ -6163,11 +6282,11 @@ async function deployInboundLambda(config2) {
|
|
|
6163
6282
|
Service: "email-inbound"
|
|
6164
6283
|
}
|
|
6165
6284
|
});
|
|
6166
|
-
new
|
|
6285
|
+
new aws16.iam.RolePolicyAttachment("wraps-inbound-lambda-basic-execution", {
|
|
6167
6286
|
role: lambdaRole.name,
|
|
6168
6287
|
policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
6169
6288
|
});
|
|
6170
|
-
new
|
|
6289
|
+
new aws16.iam.RolePolicy("wraps-inbound-lambda-policy", {
|
|
6171
6290
|
role: lambdaRole.name,
|
|
6172
6291
|
policy: pulumi12.all([config2.bucketArn, config2.dlqArn]).apply(
|
|
6173
6292
|
([bucketArn, dlqArn]) => JSON.stringify({
|
|
@@ -6196,7 +6315,7 @@ async function deployInboundLambda(config2) {
|
|
|
6196
6315
|
)
|
|
6197
6316
|
});
|
|
6198
6317
|
const functionName = "wraps-inbound-email-processor";
|
|
6199
|
-
const lambdaFunction = new
|
|
6318
|
+
const lambdaFunction = new aws16.lambda.Function(functionName, {
|
|
6200
6319
|
name: functionName,
|
|
6201
6320
|
runtime: "nodejs20.x",
|
|
6202
6321
|
handler: "index.handler",
|
|
@@ -6218,7 +6337,7 @@ async function deployInboundLambda(config2) {
|
|
|
6218
6337
|
Service: "email-inbound"
|
|
6219
6338
|
}
|
|
6220
6339
|
});
|
|
6221
|
-
const s3InvokePermission = new
|
|
6340
|
+
const s3InvokePermission = new aws16.lambda.Permission(
|
|
6222
6341
|
"wraps-inbound-s3-invoke",
|
|
6223
6342
|
{
|
|
6224
6343
|
action: "lambda:InvokeFunction",
|
|
@@ -6246,9 +6365,9 @@ var eventbridge_inbound_exports = {};
|
|
|
6246
6365
|
__export(eventbridge_inbound_exports, {
|
|
6247
6366
|
createEventBridgeInboundResources: () => createEventBridgeInboundResources
|
|
6248
6367
|
});
|
|
6249
|
-
import * as
|
|
6368
|
+
import * as aws17 from "@pulumi/aws";
|
|
6250
6369
|
function createEventBridgeInboundResources(config2) {
|
|
6251
|
-
const rule = new
|
|
6370
|
+
const rule = new aws17.cloudwatch.EventRule("wraps-inbound-events-rule", {
|
|
6252
6371
|
name: "wraps-inbound-events-to-webhook",
|
|
6253
6372
|
description: "Route inbound email events to webhook",
|
|
6254
6373
|
eventBusName: "default",
|
|
@@ -6265,7 +6384,7 @@ function createEventBridgeInboundResources(config2) {
|
|
|
6265
6384
|
let webhookApiDestination;
|
|
6266
6385
|
let webhookTarget;
|
|
6267
6386
|
if (config2.webhookUrl && config2.webhookSecret) {
|
|
6268
|
-
webhookConnection = new
|
|
6387
|
+
webhookConnection = new aws17.cloudwatch.EventConnection(
|
|
6269
6388
|
"wraps-inbound-webhook-connection",
|
|
6270
6389
|
{
|
|
6271
6390
|
name: "wraps-inbound-webhook-connection",
|
|
@@ -6279,7 +6398,7 @@ function createEventBridgeInboundResources(config2) {
|
|
|
6279
6398
|
}
|
|
6280
6399
|
}
|
|
6281
6400
|
);
|
|
6282
|
-
webhookApiDestination = new
|
|
6401
|
+
webhookApiDestination = new aws17.cloudwatch.EventApiDestination(
|
|
6283
6402
|
"wraps-inbound-webhook-destination",
|
|
6284
6403
|
{
|
|
6285
6404
|
name: "wraps-inbound-webhook-destination",
|
|
@@ -6290,7 +6409,7 @@ function createEventBridgeInboundResources(config2) {
|
|
|
6290
6409
|
invocationRateLimitPerSecond: 100
|
|
6291
6410
|
}
|
|
6292
6411
|
);
|
|
6293
|
-
const webhookRole = new
|
|
6412
|
+
const webhookRole = new aws17.iam.Role("wraps-inbound-webhook-role", {
|
|
6294
6413
|
name: "wraps-inbound-eventbridge-webhook-role",
|
|
6295
6414
|
assumeRolePolicy: JSON.stringify({
|
|
6296
6415
|
Version: "2012-10-17",
|
|
@@ -6309,7 +6428,7 @@ function createEventBridgeInboundResources(config2) {
|
|
|
6309
6428
|
Service: "email-inbound"
|
|
6310
6429
|
}
|
|
6311
6430
|
});
|
|
6312
|
-
new
|
|
6431
|
+
new aws17.iam.RolePolicy("wraps-inbound-webhook-policy", {
|
|
6313
6432
|
role: webhookRole.name,
|
|
6314
6433
|
policy: webhookApiDestination.arn.apply(
|
|
6315
6434
|
(destArn) => JSON.stringify({
|
|
@@ -6324,7 +6443,7 @@ function createEventBridgeInboundResources(config2) {
|
|
|
6324
6443
|
})
|
|
6325
6444
|
)
|
|
6326
6445
|
});
|
|
6327
|
-
webhookTarget = new
|
|
6446
|
+
webhookTarget = new aws17.cloudwatch.EventTarget(
|
|
6328
6447
|
"wraps-inbound-webhook-target",
|
|
6329
6448
|
{
|
|
6330
6449
|
rule: rule.name,
|
|
@@ -10496,10 +10615,10 @@ import * as aws3 from "@pulumi/aws";
|
|
|
10496
10615
|
async function getExistingOIDCProviderArn(url) {
|
|
10497
10616
|
try {
|
|
10498
10617
|
const { IAMClient: IAMClient4, ListOpenIDConnectProvidersCommand } = await import("@aws-sdk/client-iam");
|
|
10499
|
-
const
|
|
10618
|
+
const iam10 = new IAMClient4({
|
|
10500
10619
|
region: getDefaultRegion()
|
|
10501
10620
|
});
|
|
10502
|
-
const response = await
|
|
10621
|
+
const response = await iam10.send(new ListOpenIDConnectProvidersCommand({}));
|
|
10503
10622
|
const expectedArnSuffix = url.replace("https://", "");
|
|
10504
10623
|
const provider = response.OpenIDConnectProviderList?.find(
|
|
10505
10624
|
(p) => p.Arn?.endsWith(expectedArnSuffix)
|
|
@@ -15439,7 +15558,7 @@ import pc17 from "picocolors";
|
|
|
15439
15558
|
// src/infrastructure/email-stack.ts
|
|
15440
15559
|
init_esm_shims();
|
|
15441
15560
|
init_dist();
|
|
15442
|
-
import * as
|
|
15561
|
+
import * as aws18 from "@pulumi/aws";
|
|
15443
15562
|
|
|
15444
15563
|
// src/infrastructure/resources/alerting.ts
|
|
15445
15564
|
init_esm_shims();
|
|
@@ -15713,11 +15832,11 @@ async function createDynamoDBTables(_config) {
|
|
|
15713
15832
|
|
|
15714
15833
|
// src/infrastructure/resources/eventbridge.ts
|
|
15715
15834
|
init_esm_shims();
|
|
15716
|
-
import * as
|
|
15835
|
+
import * as aws7 from "@pulumi/aws";
|
|
15717
15836
|
import * as pulumi10 from "@pulumi/pulumi";
|
|
15718
15837
|
async function createEventBridgeResources(config2) {
|
|
15719
15838
|
const eventBusName = config2.eventBusArn.apply((arn) => arn.split("/").pop());
|
|
15720
|
-
const rule = new
|
|
15839
|
+
const rule = new aws7.cloudwatch.EventRule("wraps-email-events-rule", {
|
|
15721
15840
|
name: "wraps-email-events-to-sqs",
|
|
15722
15841
|
description: "Route all SES email events to SQS for processing",
|
|
15723
15842
|
eventBusName,
|
|
@@ -15731,7 +15850,7 @@ async function createEventBridgeResources(config2) {
|
|
|
15731
15850
|
Service: "email"
|
|
15732
15851
|
}
|
|
15733
15852
|
});
|
|
15734
|
-
new
|
|
15853
|
+
new aws7.sqs.QueuePolicy("wraps-email-events-queue-policy", {
|
|
15735
15854
|
queueUrl: config2.queueUrl,
|
|
15736
15855
|
policy: pulumi10.all([config2.queueArn, rule.arn]).apply(
|
|
15737
15856
|
([queueArn, ruleArn]) => JSON.stringify({
|
|
@@ -15754,7 +15873,7 @@ async function createEventBridgeResources(config2) {
|
|
|
15754
15873
|
})
|
|
15755
15874
|
)
|
|
15756
15875
|
});
|
|
15757
|
-
const target = new
|
|
15876
|
+
const target = new aws7.cloudwatch.EventTarget("wraps-email-events-target", {
|
|
15758
15877
|
rule: rule.name,
|
|
15759
15878
|
eventBusName,
|
|
15760
15879
|
arn: config2.queueArn
|
|
@@ -15765,7 +15884,7 @@ async function createEventBridgeResources(config2) {
|
|
|
15765
15884
|
if (config2.webhook) {
|
|
15766
15885
|
const { awsAccountNumber, webhookSecret, webhookUrl } = config2.webhook;
|
|
15767
15886
|
const baseUrl = webhookUrl || "https://api.wraps.dev";
|
|
15768
|
-
webhookConnection = new
|
|
15887
|
+
webhookConnection = new aws7.cloudwatch.EventConnection(
|
|
15769
15888
|
"wraps-webhook-connection",
|
|
15770
15889
|
{
|
|
15771
15890
|
name: "wraps-webhook-connection",
|
|
@@ -15779,7 +15898,7 @@ async function createEventBridgeResources(config2) {
|
|
|
15779
15898
|
}
|
|
15780
15899
|
}
|
|
15781
15900
|
);
|
|
15782
|
-
webhookApiDestination = new
|
|
15901
|
+
webhookApiDestination = new aws7.cloudwatch.EventApiDestination(
|
|
15783
15902
|
"wraps-webhook-destination",
|
|
15784
15903
|
{
|
|
15785
15904
|
name: "wraps-webhook-destination",
|
|
@@ -15791,7 +15910,7 @@ async function createEventBridgeResources(config2) {
|
|
|
15791
15910
|
// Rate limit
|
|
15792
15911
|
}
|
|
15793
15912
|
);
|
|
15794
|
-
const webhookRole = new
|
|
15913
|
+
const webhookRole = new aws7.iam.Role("wraps-webhook-role", {
|
|
15795
15914
|
name: "wraps-eventbridge-webhook-role",
|
|
15796
15915
|
assumeRolePolicy: JSON.stringify({
|
|
15797
15916
|
Version: "2012-10-17",
|
|
@@ -15810,7 +15929,7 @@ async function createEventBridgeResources(config2) {
|
|
|
15810
15929
|
Service: "email"
|
|
15811
15930
|
}
|
|
15812
15931
|
});
|
|
15813
|
-
new
|
|
15932
|
+
new aws7.iam.RolePolicy("wraps-webhook-policy", {
|
|
15814
15933
|
role: webhookRole.name,
|
|
15815
15934
|
policy: webhookApiDestination.arn.apply(
|
|
15816
15935
|
(destArn) => JSON.stringify({
|
|
@@ -15825,19 +15944,37 @@ async function createEventBridgeResources(config2) {
|
|
|
15825
15944
|
})
|
|
15826
15945
|
)
|
|
15827
15946
|
});
|
|
15828
|
-
webhookTarget = new
|
|
15947
|
+
webhookTarget = new aws7.cloudwatch.EventTarget("wraps-webhook-target", {
|
|
15829
15948
|
rule: rule.name,
|
|
15830
15949
|
eventBusName,
|
|
15831
15950
|
arn: webhookApiDestination.arn,
|
|
15832
15951
|
roleArn: webhookRole.arn
|
|
15833
15952
|
});
|
|
15834
15953
|
}
|
|
15954
|
+
let userWebhookConnection;
|
|
15955
|
+
let userWebhookApiDestination;
|
|
15956
|
+
let userWebhookTarget;
|
|
15957
|
+
if (config2.userWebhook) {
|
|
15958
|
+
const { createUserWebhookResources: createUserWebhookResources2 } = await Promise.resolve().then(() => (init_eventbridge_user_webhook(), eventbridge_user_webhook_exports));
|
|
15959
|
+
const userWebhookResources = createUserWebhookResources2({
|
|
15960
|
+
url: config2.userWebhook.url,
|
|
15961
|
+
secret: config2.userWebhook.secret,
|
|
15962
|
+
ruleName: rule.name,
|
|
15963
|
+
eventBusName
|
|
15964
|
+
});
|
|
15965
|
+
userWebhookConnection = userWebhookResources.connection;
|
|
15966
|
+
userWebhookApiDestination = userWebhookResources.apiDestination;
|
|
15967
|
+
userWebhookTarget = userWebhookResources.target;
|
|
15968
|
+
}
|
|
15835
15969
|
return {
|
|
15836
15970
|
rule,
|
|
15837
15971
|
target,
|
|
15838
15972
|
webhookConnection,
|
|
15839
15973
|
webhookApiDestination,
|
|
15840
|
-
webhookTarget
|
|
15974
|
+
webhookTarget,
|
|
15975
|
+
userWebhookConnection,
|
|
15976
|
+
userWebhookApiDestination,
|
|
15977
|
+
userWebhookTarget
|
|
15841
15978
|
};
|
|
15842
15979
|
}
|
|
15843
15980
|
|
|
@@ -15977,7 +16114,7 @@ init_lambda();
|
|
|
15977
16114
|
|
|
15978
16115
|
// src/infrastructure/resources/ses.ts
|
|
15979
16116
|
init_esm_shims();
|
|
15980
|
-
import * as
|
|
16117
|
+
import * as aws9 from "@pulumi/aws";
|
|
15981
16118
|
async function configurationSetExists(configSetName, region) {
|
|
15982
16119
|
try {
|
|
15983
16120
|
const { SESv2Client: SESv2Client9, GetConfigurationSetCommand: GetConfigurationSetCommand2 } = await import("@aws-sdk/client-sesv2");
|
|
@@ -16056,15 +16193,15 @@ async function createSESResources(config2) {
|
|
|
16056
16193
|
}
|
|
16057
16194
|
const configSetName = "wraps-email-tracking";
|
|
16058
16195
|
const exists = await configurationSetExists(configSetName, config2.region);
|
|
16059
|
-
const configSet = exists && !config2.skipResourceImports ? new
|
|
16196
|
+
const configSet = exists && !config2.skipResourceImports ? new aws9.sesv2.ConfigurationSet(configSetName, configSetOptions, {
|
|
16060
16197
|
import: configSetName
|
|
16061
|
-
}) : new
|
|
16062
|
-
const defaultEventBus =
|
|
16198
|
+
}) : new aws9.sesv2.ConfigurationSet(configSetName, configSetOptions);
|
|
16199
|
+
const defaultEventBus = aws9.cloudwatch.getEventBusOutput({
|
|
16063
16200
|
name: "default"
|
|
16064
16201
|
});
|
|
16065
16202
|
if (config2.eventTrackingEnabled) {
|
|
16066
16203
|
const eventDestName = "wraps-email-eventbridge";
|
|
16067
|
-
new
|
|
16204
|
+
new aws9.sesv2.ConfigurationSetEventDestination(
|
|
16068
16205
|
"wraps-email-all-events",
|
|
16069
16206
|
{
|
|
16070
16207
|
configurationSetName: configSet.configurationSetName,
|
|
@@ -16104,7 +16241,7 @@ async function createSESResources(config2) {
|
|
|
16104
16241
|
config2.domain,
|
|
16105
16242
|
config2.region
|
|
16106
16243
|
);
|
|
16107
|
-
domainIdentity = identityExists && !config2.skipResourceImports ? new
|
|
16244
|
+
domainIdentity = identityExists && !config2.skipResourceImports ? new aws9.sesv2.EmailIdentity(
|
|
16108
16245
|
"wraps-email-domain",
|
|
16109
16246
|
{
|
|
16110
16247
|
emailIdentity: config2.domain,
|
|
@@ -16119,7 +16256,7 @@ async function createSESResources(config2) {
|
|
|
16119
16256
|
{
|
|
16120
16257
|
import: config2.domain
|
|
16121
16258
|
}
|
|
16122
|
-
) : new
|
|
16259
|
+
) : new aws9.sesv2.EmailIdentity("wraps-email-domain", {
|
|
16123
16260
|
emailIdentity: config2.domain,
|
|
16124
16261
|
configurationSetName: configSet.configurationSetName,
|
|
16125
16262
|
// Link configuration set to domain
|
|
@@ -16136,7 +16273,7 @@ async function createSESResources(config2) {
|
|
|
16136
16273
|
);
|
|
16137
16274
|
if (config2.mailFromDomain) {
|
|
16138
16275
|
mailFromDomain = config2.mailFromDomain;
|
|
16139
|
-
new
|
|
16276
|
+
new aws9.sesv2.EmailIdentityMailFromAttributes(
|
|
16140
16277
|
"wraps-email-mail-from",
|
|
16141
16278
|
{
|
|
16142
16279
|
emailIdentity: config2.domain,
|
|
@@ -16168,7 +16305,7 @@ async function createSESResources(config2) {
|
|
|
16168
16305
|
init_esm_shims();
|
|
16169
16306
|
init_constants();
|
|
16170
16307
|
import { createHmac as createHmac2 } from "crypto";
|
|
16171
|
-
import * as
|
|
16308
|
+
import * as aws10 from "@pulumi/aws";
|
|
16172
16309
|
function convertToSMTPPassword2(secretAccessKey, region) {
|
|
16173
16310
|
const DATE = "11111111";
|
|
16174
16311
|
const SERVICE = "ses";
|
|
@@ -16189,10 +16326,10 @@ function convertToSMTPPassword2(secretAccessKey, region) {
|
|
|
16189
16326
|
async function userExists(userName) {
|
|
16190
16327
|
try {
|
|
16191
16328
|
const { IAMClient: IAMClient4, GetUserCommand } = await import("@aws-sdk/client-iam");
|
|
16192
|
-
const
|
|
16329
|
+
const iam10 = new IAMClient4({
|
|
16193
16330
|
region: getDefaultRegion()
|
|
16194
16331
|
});
|
|
16195
|
-
await
|
|
16332
|
+
await iam10.send(new GetUserCommand({ UserName: userName }));
|
|
16196
16333
|
return true;
|
|
16197
16334
|
} catch (error) {
|
|
16198
16335
|
if (error instanceof Error && (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity")) {
|
|
@@ -16204,7 +16341,7 @@ async function userExists(userName) {
|
|
|
16204
16341
|
async function createSMTPCredentials(config2) {
|
|
16205
16342
|
const userName = "wraps-email-smtp-user";
|
|
16206
16343
|
const userAlreadyExists = await userExists(userName);
|
|
16207
|
-
const iamUser = userAlreadyExists ? new
|
|
16344
|
+
const iamUser = userAlreadyExists ? new aws10.iam.User(
|
|
16208
16345
|
userName,
|
|
16209
16346
|
{
|
|
16210
16347
|
name: userName,
|
|
@@ -16215,14 +16352,14 @@ async function createSMTPCredentials(config2) {
|
|
|
16215
16352
|
}
|
|
16216
16353
|
},
|
|
16217
16354
|
{ import: userName }
|
|
16218
|
-
) : new
|
|
16355
|
+
) : new aws10.iam.User(userName, {
|
|
16219
16356
|
name: userName,
|
|
16220
16357
|
tags: {
|
|
16221
16358
|
ManagedBy: "wraps-cli",
|
|
16222
16359
|
Purpose: "SES SMTP Authentication"
|
|
16223
16360
|
}
|
|
16224
16361
|
});
|
|
16225
|
-
new
|
|
16362
|
+
new aws10.iam.UserPolicy("wraps-email-smtp-policy", {
|
|
16226
16363
|
user: iamUser.name,
|
|
16227
16364
|
policy: JSON.stringify({
|
|
16228
16365
|
Version: "2012-10-17",
|
|
@@ -16240,7 +16377,7 @@ async function createSMTPCredentials(config2) {
|
|
|
16240
16377
|
]
|
|
16241
16378
|
})
|
|
16242
16379
|
});
|
|
16243
|
-
const accessKey = new
|
|
16380
|
+
const accessKey = new aws10.iam.AccessKey("wraps-email-smtp-key", {
|
|
16244
16381
|
user: iamUser.name
|
|
16245
16382
|
});
|
|
16246
16383
|
const smtpPassword = accessKey.secret.apply(
|
|
@@ -16255,9 +16392,9 @@ async function createSMTPCredentials(config2) {
|
|
|
16255
16392
|
|
|
16256
16393
|
// src/infrastructure/resources/sqs.ts
|
|
16257
16394
|
init_esm_shims();
|
|
16258
|
-
import * as
|
|
16395
|
+
import * as aws11 from "@pulumi/aws";
|
|
16259
16396
|
async function createSQSResources() {
|
|
16260
|
-
const dlq = new
|
|
16397
|
+
const dlq = new aws11.sqs.Queue("wraps-email-events-dlq", {
|
|
16261
16398
|
name: "wraps-email-events-dlq",
|
|
16262
16399
|
messageRetentionSeconds: 1209600,
|
|
16263
16400
|
// 14 days
|
|
@@ -16267,7 +16404,7 @@ async function createSQSResources() {
|
|
|
16267
16404
|
Description: "Dead letter queue for failed SES event processing"
|
|
16268
16405
|
}
|
|
16269
16406
|
});
|
|
16270
|
-
const queue = new
|
|
16407
|
+
const queue = new aws11.sqs.Queue("wraps-email-events", {
|
|
16271
16408
|
name: "wraps-email-events",
|
|
16272
16409
|
visibilityTimeoutSeconds: 60,
|
|
16273
16410
|
// Must be >= Lambda timeout
|
|
@@ -16296,7 +16433,7 @@ async function createSQSResources() {
|
|
|
16296
16433
|
|
|
16297
16434
|
// src/infrastructure/email-stack.ts
|
|
16298
16435
|
async function deployEmailStack(config2) {
|
|
16299
|
-
const identity = await
|
|
16436
|
+
const identity = await aws18.getCallerIdentity();
|
|
16300
16437
|
const accountId = identity.accountId;
|
|
16301
16438
|
let oidcProvider;
|
|
16302
16439
|
if (config2.provider === "vercel" && config2.vercel) {
|
|
@@ -16400,7 +16537,12 @@ async function deployEmailStack(config2) {
|
|
|
16400
16537
|
queueArn: sqsResources.queue.arn,
|
|
16401
16538
|
queueUrl: sqsResources.queue.url,
|
|
16402
16539
|
// Include webhook config if provided (for Wraps platform integration)
|
|
16403
|
-
webhook: config2.webhook
|
|
16540
|
+
webhook: config2.webhook,
|
|
16541
|
+
// Include user webhook config if provided
|
|
16542
|
+
userWebhook: emailConfig.userWebhook?.enabled && emailConfig.userWebhook.url && emailConfig.userWebhook.secret ? {
|
|
16543
|
+
url: emailConfig.userWebhook.url,
|
|
16544
|
+
secret: emailConfig.userWebhook.secret
|
|
16545
|
+
} : void 0
|
|
16404
16546
|
});
|
|
16405
16547
|
}
|
|
16406
16548
|
let lambdaFunctions;
|
|
@@ -16461,7 +16603,7 @@ async function deployEmailStack(config2) {
|
|
|
16461
16603
|
region: config2.region,
|
|
16462
16604
|
dlqArn: sqsInbound.dlq.arn
|
|
16463
16605
|
});
|
|
16464
|
-
new
|
|
16606
|
+
new aws18.s3.BucketNotification(
|
|
16465
16607
|
"wraps-inbound-s3-notification",
|
|
16466
16608
|
{
|
|
16467
16609
|
bucket: s3Inbound.bucket.id,
|
|
@@ -16522,7 +16664,10 @@ async function deployEmailStack(config2) {
|
|
|
16522
16664
|
inboundBucketName: inboundResources?.bucketName,
|
|
16523
16665
|
inboundBucketArn: inboundResources?.bucketArn,
|
|
16524
16666
|
inboundLambdaArn: inboundResources?.lambdaArn,
|
|
16525
|
-
inboundReceivingDomain: emailConfig.inbound?.receivingDomain
|
|
16667
|
+
inboundReceivingDomain: emailConfig.inbound?.receivingDomain,
|
|
16668
|
+
// User webhook outputs
|
|
16669
|
+
userWebhookUrl: emailConfig.userWebhook?.enabled ? emailConfig.userWebhook.url : void 0,
|
|
16670
|
+
userWebhookSecret: emailConfig.userWebhook?.enabled ? emailConfig.userWebhook.secret : void 0
|
|
16526
16671
|
};
|
|
16527
16672
|
}
|
|
16528
16673
|
|
|
@@ -16998,13 +17143,13 @@ async function scanLambdaFunctions(region) {
|
|
|
16998
17143
|
}
|
|
16999
17144
|
}
|
|
17000
17145
|
async function scanIAMRoles(region) {
|
|
17001
|
-
const
|
|
17146
|
+
const iam10 = new IAMClient({ region });
|
|
17002
17147
|
const roles = [];
|
|
17003
17148
|
try {
|
|
17004
17149
|
let marker;
|
|
17005
17150
|
let hasMore = true;
|
|
17006
17151
|
while (hasMore) {
|
|
17007
|
-
const listResponse = await
|
|
17152
|
+
const listResponse = await iam10.send(
|
|
17008
17153
|
new ListRolesCommand({
|
|
17009
17154
|
Marker: marker,
|
|
17010
17155
|
MaxItems: 100
|
|
@@ -21792,6 +21937,12 @@ ${pc30.bold("Current Configuration:")}
|
|
|
21792
21937
|
);
|
|
21793
21938
|
}
|
|
21794
21939
|
}
|
|
21940
|
+
if (config2.userWebhook?.enabled) {
|
|
21941
|
+
console.log(` ${pc30.green("\u2713")} Webhook Endpoint`);
|
|
21942
|
+
console.log(
|
|
21943
|
+
` ${pc30.dim("\u2514\u2500")} URL: ${pc30.cyan(config2.userWebhook.url)}`
|
|
21944
|
+
);
|
|
21945
|
+
}
|
|
21795
21946
|
const currentCostData = calculateCosts(config2, 5e4);
|
|
21796
21947
|
console.log(
|
|
21797
21948
|
`
|
|
@@ -21852,6 +22003,11 @@ ${pc30.bold("Current Configuration:")}
|
|
|
21852
22003
|
label: metadata.services.email?.webhookSecret ? "Manage Wraps Dashboard connection" : "Connect to Wraps Dashboard",
|
|
21853
22004
|
hint: metadata.services.email?.webhookSecret ? "Regenerate secret or disconnect" : "Send events to dashboard for analytics"
|
|
21854
22005
|
},
|
|
22006
|
+
{
|
|
22007
|
+
value: "user-webhook",
|
|
22008
|
+
label: config2.userWebhook?.enabled ? "Manage webhook endpoint" : "Configure webhook endpoint",
|
|
22009
|
+
hint: config2.userWebhook?.enabled ? `Sending events to ${config2.userWebhook.url}` : "Send SES events to your own URL"
|
|
22010
|
+
},
|
|
21855
22011
|
{
|
|
21856
22012
|
value: "smtp-credentials",
|
|
21857
22013
|
label: metadata.services.email?.smtpCredentials?.enabled ? "Manage SMTP credentials" : "Enable SMTP credentials",
|
|
@@ -22679,6 +22835,166 @@ ${pc30.bold("Webhook Configuration:")}`);
|
|
|
22679
22835
|
newPreset = void 0;
|
|
22680
22836
|
break;
|
|
22681
22837
|
}
|
|
22838
|
+
case "user-webhook": {
|
|
22839
|
+
const validateWebhookUrl = (value) => {
|
|
22840
|
+
try {
|
|
22841
|
+
const url = new URL(value);
|
|
22842
|
+
if (url.protocol !== "https:") {
|
|
22843
|
+
return "Webhook URL must use HTTPS";
|
|
22844
|
+
}
|
|
22845
|
+
if (!url.hostname.includes(".")) {
|
|
22846
|
+
return "Webhook URL must use a public hostname";
|
|
22847
|
+
}
|
|
22848
|
+
} catch {
|
|
22849
|
+
return "Please enter a valid URL";
|
|
22850
|
+
}
|
|
22851
|
+
};
|
|
22852
|
+
if (!config2.eventTracking?.enabled) {
|
|
22853
|
+
clack29.log.warn(
|
|
22854
|
+
"Event tracking must be enabled to configure a webhook endpoint."
|
|
22855
|
+
);
|
|
22856
|
+
clack29.log.info(
|
|
22857
|
+
"Enabling event tracking will allow SES events to be sent to your endpoint."
|
|
22858
|
+
);
|
|
22859
|
+
const enableEventTracking = await clack29.confirm({
|
|
22860
|
+
message: "Enable event tracking now?",
|
|
22861
|
+
initialValue: true
|
|
22862
|
+
});
|
|
22863
|
+
if (clack29.isCancel(enableEventTracking) || !enableEventTracking) {
|
|
22864
|
+
clack29.cancel("Webhook configuration cancelled.");
|
|
22865
|
+
process.exit(0);
|
|
22866
|
+
}
|
|
22867
|
+
updatedConfig = {
|
|
22868
|
+
...config2,
|
|
22869
|
+
eventTracking: {
|
|
22870
|
+
enabled: true,
|
|
22871
|
+
eventBridge: true,
|
|
22872
|
+
events: [
|
|
22873
|
+
"SEND",
|
|
22874
|
+
"DELIVERY",
|
|
22875
|
+
"OPEN",
|
|
22876
|
+
"CLICK",
|
|
22877
|
+
"BOUNCE",
|
|
22878
|
+
"COMPLAINT"
|
|
22879
|
+
],
|
|
22880
|
+
dynamoDBHistory: config2.eventTracking?.dynamoDBHistory ?? false,
|
|
22881
|
+
archiveRetention: config2.eventTracking?.archiveRetention ?? "90days"
|
|
22882
|
+
}
|
|
22883
|
+
};
|
|
22884
|
+
}
|
|
22885
|
+
if (config2.userWebhook?.enabled) {
|
|
22886
|
+
clack29.log.info(
|
|
22887
|
+
`Webhook endpoint currently sending events to: ${pc30.cyan(config2.userWebhook.url)}`
|
|
22888
|
+
);
|
|
22889
|
+
const action = await clack29.select({
|
|
22890
|
+
message: "What would you like to do?",
|
|
22891
|
+
options: [
|
|
22892
|
+
{
|
|
22893
|
+
value: "change-url",
|
|
22894
|
+
label: "Change webhook URL",
|
|
22895
|
+
hint: config2.userWebhook.url
|
|
22896
|
+
},
|
|
22897
|
+
{
|
|
22898
|
+
value: "regenerate-secret",
|
|
22899
|
+
label: "Regenerate webhook secret",
|
|
22900
|
+
hint: "Create new secret (update your endpoint)"
|
|
22901
|
+
},
|
|
22902
|
+
{
|
|
22903
|
+
value: "disable",
|
|
22904
|
+
label: "Disable webhook",
|
|
22905
|
+
hint: "Stop sending events to your endpoint"
|
|
22906
|
+
},
|
|
22907
|
+
{
|
|
22908
|
+
value: "cancel",
|
|
22909
|
+
label: "Cancel",
|
|
22910
|
+
hint: "Keep current settings"
|
|
22911
|
+
}
|
|
22912
|
+
]
|
|
22913
|
+
});
|
|
22914
|
+
if (clack29.isCancel(action) || action === "cancel") {
|
|
22915
|
+
clack29.log.info("No changes made.");
|
|
22916
|
+
process.exit(0);
|
|
22917
|
+
}
|
|
22918
|
+
if (action === "disable") {
|
|
22919
|
+
const confirmDisable = await clack29.confirm({
|
|
22920
|
+
message: "Are you sure? Events will no longer be sent to your webhook endpoint.",
|
|
22921
|
+
initialValue: false
|
|
22922
|
+
});
|
|
22923
|
+
if (clack29.isCancel(confirmDisable) || !confirmDisable) {
|
|
22924
|
+
clack29.log.info("Webhook not disabled.");
|
|
22925
|
+
process.exit(0);
|
|
22926
|
+
}
|
|
22927
|
+
updatedConfig = {
|
|
22928
|
+
...updatedConfig,
|
|
22929
|
+
userWebhook: { enabled: false }
|
|
22930
|
+
};
|
|
22931
|
+
newPreset = void 0;
|
|
22932
|
+
break;
|
|
22933
|
+
}
|
|
22934
|
+
if (action === "change-url") {
|
|
22935
|
+
const newUrl = await clack29.text({
|
|
22936
|
+
message: "New webhook URL:",
|
|
22937
|
+
placeholder: "https://your-app.com/webhooks/email-events",
|
|
22938
|
+
validate: validateWebhookUrl
|
|
22939
|
+
});
|
|
22940
|
+
if (clack29.isCancel(newUrl)) {
|
|
22941
|
+
clack29.cancel("Webhook configuration cancelled.");
|
|
22942
|
+
process.exit(0);
|
|
22943
|
+
}
|
|
22944
|
+
updatedConfig = {
|
|
22945
|
+
...updatedConfig,
|
|
22946
|
+
userWebhook: {
|
|
22947
|
+
enabled: true,
|
|
22948
|
+
url: newUrl,
|
|
22949
|
+
secret: config2.userWebhook.secret
|
|
22950
|
+
}
|
|
22951
|
+
};
|
|
22952
|
+
newPreset = void 0;
|
|
22953
|
+
break;
|
|
22954
|
+
}
|
|
22955
|
+
if (action === "regenerate-secret") {
|
|
22956
|
+
const newSecret = generateWebhookSecret();
|
|
22957
|
+
updatedConfig = {
|
|
22958
|
+
...updatedConfig,
|
|
22959
|
+
userWebhook: {
|
|
22960
|
+
enabled: true,
|
|
22961
|
+
url: config2.userWebhook.url,
|
|
22962
|
+
secret: newSecret
|
|
22963
|
+
}
|
|
22964
|
+
};
|
|
22965
|
+
newPreset = void 0;
|
|
22966
|
+
break;
|
|
22967
|
+
}
|
|
22968
|
+
} else {
|
|
22969
|
+
clack29.log.info(`
|
|
22970
|
+
${pc30.bold("Webhook Endpoint")}
|
|
22971
|
+
`);
|
|
22972
|
+
clack29.log.info(
|
|
22973
|
+
pc30.dim("Send SES events (send, delivery, open, click, bounce, etc.)")
|
|
22974
|
+
);
|
|
22975
|
+
clack29.log.info(pc30.dim("to your own HTTP endpoint in real-time.\n"));
|
|
22976
|
+
const webhookUrl = await clack29.text({
|
|
22977
|
+
message: "Webhook URL (must be HTTPS):",
|
|
22978
|
+
placeholder: "https://your-app.com/webhooks/email-events",
|
|
22979
|
+
validate: validateWebhookUrl
|
|
22980
|
+
});
|
|
22981
|
+
if (clack29.isCancel(webhookUrl)) {
|
|
22982
|
+
clack29.cancel("Webhook configuration cancelled.");
|
|
22983
|
+
process.exit(0);
|
|
22984
|
+
}
|
|
22985
|
+
const secret = generateWebhookSecret();
|
|
22986
|
+
updatedConfig = {
|
|
22987
|
+
...updatedConfig,
|
|
22988
|
+
userWebhook: {
|
|
22989
|
+
enabled: true,
|
|
22990
|
+
url: webhookUrl,
|
|
22991
|
+
secret
|
|
22992
|
+
}
|
|
22993
|
+
};
|
|
22994
|
+
newPreset = void 0;
|
|
22995
|
+
}
|
|
22996
|
+
break;
|
|
22997
|
+
}
|
|
22682
22998
|
case "smtp-credentials": {
|
|
22683
22999
|
if (metadata.services.email?.smtpCredentials?.enabled) {
|
|
22684
23000
|
clack29.log.info(
|
|
@@ -23318,6 +23634,29 @@ ${pc30.green("\u2713")} ${pc30.bold("Upgrade complete!")}
|
|
|
23318
23634
|
)
|
|
23319
23635
|
);
|
|
23320
23636
|
}
|
|
23637
|
+
if (upgradeAction === "user-webhook" && updatedConfig.userWebhook?.enabled) {
|
|
23638
|
+
console.log(pc30.bold("Webhook Endpoint Configuration\n"));
|
|
23639
|
+
console.log(` ${pc30.green("\u2713")} EventBridge API Destination created`);
|
|
23640
|
+
console.log(
|
|
23641
|
+
` ${pc30.green("\u2713")} Events will be sent to: ${pc30.cyan(updatedConfig.userWebhook.url)}
|
|
23642
|
+
`
|
|
23643
|
+
);
|
|
23644
|
+
if (updatedConfig.userWebhook.secret !== config2.userWebhook?.secret) {
|
|
23645
|
+
console.log(pc30.bold(" Webhook Secret (save this now!):\n"));
|
|
23646
|
+
console.log(
|
|
23647
|
+
` ${pc30.bgBlack(pc30.white(` ${updatedConfig.userWebhook.secret} `))}
|
|
23648
|
+
`
|
|
23649
|
+
);
|
|
23650
|
+
console.log(
|
|
23651
|
+
pc30.dim(" Include this in the X-Wraps-Signature header validation")
|
|
23652
|
+
);
|
|
23653
|
+
console.log(
|
|
23654
|
+
pc30.dim(
|
|
23655
|
+
" to verify requests are from your Wraps deployment.\n"
|
|
23656
|
+
)
|
|
23657
|
+
);
|
|
23658
|
+
}
|
|
23659
|
+
}
|
|
23321
23660
|
if (upgradeAction === "smtp-credentials" && outputs.smtpUsername && outputs.smtpPassword) {
|
|
23322
23661
|
console.log(pc30.bold("\n\u{1F4E7} SMTP Connection Details\n"));
|
|
23323
23662
|
console.log(` ${pc30.cyan("Server:")} ${outputs.smtpEndpoint}`);
|
|
@@ -23368,6 +23707,9 @@ ${pc30.green("\u2713")} ${pc30.bold("Upgrade complete!")}
|
|
|
23368
23707
|
if (updatedConfig.alerts?.enabled) {
|
|
23369
23708
|
enabledFeatures.push("alerts");
|
|
23370
23709
|
}
|
|
23710
|
+
if (updatedConfig.userWebhook?.enabled) {
|
|
23711
|
+
enabledFeatures.push("user_webhook");
|
|
23712
|
+
}
|
|
23371
23713
|
trackServiceUpgrade("email", {
|
|
23372
23714
|
from_preset: metadata.services.email?.preset,
|
|
23373
23715
|
to_preset: newPreset,
|
|
@@ -23454,33 +23796,59 @@ function getTriggerName(type) {
|
|
|
23454
23796
|
return names[type] || "Trigger";
|
|
23455
23797
|
}
|
|
23456
23798
|
function flattenSteps(stepDefs, steps, transitions, fromStepId, branch) {
|
|
23457
|
-
let
|
|
23799
|
+
let prevIds = [fromStepId];
|
|
23458
23800
|
let firstStepInBranch = true;
|
|
23459
23801
|
for (const def of stepDefs) {
|
|
23460
23802
|
const step = toWorkflowStep(def);
|
|
23461
23803
|
steps.push(step);
|
|
23462
|
-
const
|
|
23463
|
-
|
|
23464
|
-
|
|
23465
|
-
|
|
23466
|
-
|
|
23467
|
-
|
|
23468
|
-
|
|
23804
|
+
for (const prevId of prevIds) {
|
|
23805
|
+
const transition = {
|
|
23806
|
+
id: `t-${prevId}-${step.id}`,
|
|
23807
|
+
fromStepId: prevId,
|
|
23808
|
+
toStepId: step.id
|
|
23809
|
+
};
|
|
23810
|
+
if (firstStepInBranch && branch && prevId === fromStepId) {
|
|
23811
|
+
transition.condition = { branch };
|
|
23812
|
+
}
|
|
23813
|
+
transitions.push(transition);
|
|
23469
23814
|
}
|
|
23470
|
-
transitions.push(transition);
|
|
23471
23815
|
firstStepInBranch = false;
|
|
23472
23816
|
if (def.type === "condition" && def.branches) {
|
|
23817
|
+
const leafIds = [];
|
|
23473
23818
|
if (def.branches.yes && def.branches.yes.length > 0) {
|
|
23474
|
-
|
|
23819
|
+
const yesLeaves = flattenSteps(
|
|
23820
|
+
def.branches.yes,
|
|
23821
|
+
steps,
|
|
23822
|
+
transitions,
|
|
23823
|
+
step.id,
|
|
23824
|
+
"yes"
|
|
23825
|
+
);
|
|
23826
|
+
leafIds.push(...yesLeaves);
|
|
23827
|
+
} else {
|
|
23828
|
+
leafIds.push(step.id);
|
|
23475
23829
|
}
|
|
23476
23830
|
if (def.branches.no && def.branches.no.length > 0) {
|
|
23477
|
-
|
|
23831
|
+
const noLeaves = flattenSteps(
|
|
23832
|
+
def.branches.no,
|
|
23833
|
+
steps,
|
|
23834
|
+
transitions,
|
|
23835
|
+
step.id,
|
|
23836
|
+
"no"
|
|
23837
|
+
);
|
|
23838
|
+
leafIds.push(...noLeaves);
|
|
23839
|
+
} else if (!leafIds.includes(step.id)) {
|
|
23840
|
+
leafIds.push(step.id);
|
|
23478
23841
|
}
|
|
23479
|
-
|
|
23842
|
+
prevIds = leafIds;
|
|
23843
|
+
continue;
|
|
23844
|
+
}
|
|
23845
|
+
if (def.type === "exit") {
|
|
23846
|
+
prevIds = [];
|
|
23847
|
+
continue;
|
|
23480
23848
|
}
|
|
23481
|
-
|
|
23849
|
+
prevIds = [step.id];
|
|
23482
23850
|
}
|
|
23483
|
-
return
|
|
23851
|
+
return prevIds;
|
|
23484
23852
|
}
|
|
23485
23853
|
function toWorkflowStep(def) {
|
|
23486
23854
|
return {
|
|
@@ -25434,10 +25802,10 @@ async function deployEventBridge(metadata, region, identity, webhookSecret, prog
|
|
|
25434
25802
|
var WRAPS_PLATFORM_ACCOUNT_ID = "905130073023";
|
|
25435
25803
|
async function updatePlatformRole(metadata, progress, externalId) {
|
|
25436
25804
|
const roleName = "wraps-console-access-role";
|
|
25437
|
-
const
|
|
25805
|
+
const iam10 = new IAMClient2({ region: "us-east-1" });
|
|
25438
25806
|
let roleExists2 = false;
|
|
25439
25807
|
try {
|
|
25440
|
-
await
|
|
25808
|
+
await iam10.send(new GetRoleCommand({ RoleName: roleName }));
|
|
25441
25809
|
roleExists2 = true;
|
|
25442
25810
|
} catch (error) {
|
|
25443
25811
|
const isNotFound = error instanceof Error && (error.name === "NoSuchEntityException" || error.name === "NoSuchEntity" || error.message.includes("NoSuchEntity"));
|
|
@@ -25450,7 +25818,7 @@ async function updatePlatformRole(metadata, progress, externalId) {
|
|
|
25450
25818
|
const policy = buildConsolePolicyDocument(emailConfig, smsConfig);
|
|
25451
25819
|
if (roleExists2) {
|
|
25452
25820
|
await progress.execute("Updating platform access role", async () => {
|
|
25453
|
-
await
|
|
25821
|
+
await iam10.send(
|
|
25454
25822
|
new PutRolePolicyCommand({
|
|
25455
25823
|
RoleName: roleName,
|
|
25456
25824
|
PolicyName: "wraps-console-access-policy",
|
|
@@ -25478,7 +25846,7 @@ async function updatePlatformRole(metadata, progress, externalId) {
|
|
|
25478
25846
|
}
|
|
25479
25847
|
]
|
|
25480
25848
|
};
|
|
25481
|
-
await
|
|
25849
|
+
await iam10.send(
|
|
25482
25850
|
new CreateRoleCommand({
|
|
25483
25851
|
RoleName: roleName,
|
|
25484
25852
|
Description: "Allows Wraps dashboard to access CloudWatch metrics and SES data",
|
|
@@ -25489,7 +25857,7 @@ async function updatePlatformRole(metadata, progress, externalId) {
|
|
|
25489
25857
|
]
|
|
25490
25858
|
})
|
|
25491
25859
|
);
|
|
25492
|
-
await
|
|
25860
|
+
await iam10.send(
|
|
25493
25861
|
new PutRolePolicyCommand({
|
|
25494
25862
|
RoleName: roleName,
|
|
25495
25863
|
PolicyName: "wraps-console-access-policy",
|
|
@@ -25910,10 +26278,10 @@ Run ${pc35.cyan("wraps email init")} or ${pc35.cyan("wraps sms init")} first.
|
|
|
25910
26278
|
progress.succeed("Event streaming already configured");
|
|
25911
26279
|
}
|
|
25912
26280
|
const roleName = "wraps-console-access-role";
|
|
25913
|
-
const
|
|
26281
|
+
const iam10 = new IAMClient2({ region: "us-east-1" });
|
|
25914
26282
|
let roleExists2 = false;
|
|
25915
26283
|
try {
|
|
25916
|
-
await
|
|
26284
|
+
await iam10.send(new GetRoleCommand({ RoleName: roleName }));
|
|
25917
26285
|
roleExists2 = true;
|
|
25918
26286
|
} catch (error) {
|
|
25919
26287
|
const isNotFound = error instanceof Error && (error.name === "NoSuchEntityException" || error.name === "NoSuchEntity" || error.message.includes("NoSuchEntity"));
|
|
@@ -25926,7 +26294,7 @@ Run ${pc35.cyan("wraps email init")} or ${pc35.cyan("wraps sms init")} first.
|
|
|
25926
26294
|
const smsConfig = metadata.services.sms?.config;
|
|
25927
26295
|
const policy = buildConsolePolicyDocument(emailConfig, smsConfig);
|
|
25928
26296
|
await progress.execute("Updating platform access role", async () => {
|
|
25929
|
-
await
|
|
26297
|
+
await iam10.send(
|
|
25930
26298
|
new PutRolePolicyCommand({
|
|
25931
26299
|
RoleName: roleName,
|
|
25932
26300
|
PolicyName: "wraps-console-access-policy",
|
|
@@ -26078,10 +26446,10 @@ Run ${pc37.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
26078
26446
|
process.exit(1);
|
|
26079
26447
|
}
|
|
26080
26448
|
const roleName = "wraps-console-access-role";
|
|
26081
|
-
const
|
|
26449
|
+
const iam10 = new IAMClient3({ region: "us-east-1" });
|
|
26082
26450
|
let roleExists2 = false;
|
|
26083
26451
|
try {
|
|
26084
|
-
await
|
|
26452
|
+
await iam10.send(new GetRoleCommand2({ RoleName: roleName }));
|
|
26085
26453
|
roleExists2 = true;
|
|
26086
26454
|
} catch (error) {
|
|
26087
26455
|
const isNotFound = error instanceof Error && (error.name === "NoSuchEntityException" || error.name === "NoSuchEntity" || error.message.includes("NoSuchEntity"));
|
|
@@ -26150,7 +26518,7 @@ Run ${pc37.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
26150
26518
|
}
|
|
26151
26519
|
]
|
|
26152
26520
|
};
|
|
26153
|
-
await
|
|
26521
|
+
await iam10.send(
|
|
26154
26522
|
new CreateRoleCommand2({
|
|
26155
26523
|
RoleName: roleName,
|
|
26156
26524
|
Description: "Allows Wraps dashboard to access CloudWatch metrics and SES data",
|
|
@@ -26162,7 +26530,7 @@ Run ${pc37.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
26162
26530
|
})
|
|
26163
26531
|
);
|
|
26164
26532
|
const { PutRolePolicyCommand: PutRolePolicyCommand2 } = await import("@aws-sdk/client-iam");
|
|
26165
|
-
await
|
|
26533
|
+
await iam10.send(
|
|
26166
26534
|
new PutRolePolicyCommand2({
|
|
26167
26535
|
RoleName: roleName,
|
|
26168
26536
|
PolicyName: "wraps-console-access-policy",
|
|
@@ -26173,7 +26541,7 @@ Run ${pc37.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
26173
26541
|
} else {
|
|
26174
26542
|
await progress.execute("Updating IAM role permissions", async () => {
|
|
26175
26543
|
const { PutRolePolicyCommand: PutRolePolicyCommand2 } = await import("@aws-sdk/client-iam");
|
|
26176
|
-
await
|
|
26544
|
+
await iam10.send(
|
|
26177
26545
|
new PutRolePolicyCommand2({
|
|
26178
26546
|
RoleName: roleName,
|
|
26179
26547
|
PolicyName: "wraps-console-access-policy",
|
|
@@ -27523,7 +27891,7 @@ import {
|
|
|
27523
27891
|
} from "@aws-sdk/client-cloudwatch";
|
|
27524
27892
|
async function fetchSESMetrics(roleArn, region, timeRange, tableName) {
|
|
27525
27893
|
const credentials = roleArn ? await assumeRole(roleArn, region) : void 0;
|
|
27526
|
-
const
|
|
27894
|
+
const cloudwatch6 = new CloudWatchClient({ region, credentials });
|
|
27527
27895
|
const queries = [
|
|
27528
27896
|
{
|
|
27529
27897
|
Id: "sends",
|
|
@@ -27571,7 +27939,7 @@ async function fetchSESMetrics(roleArn, region, timeRange, tableName) {
|
|
|
27571
27939
|
}
|
|
27572
27940
|
}
|
|
27573
27941
|
];
|
|
27574
|
-
const response = await
|
|
27942
|
+
const response = await cloudwatch6.send(
|
|
27575
27943
|
new GetMetricDataCommand({
|
|
27576
27944
|
MetricDataQueries: queries,
|
|
27577
27945
|
StartTime: timeRange.start,
|
|
@@ -28356,7 +28724,7 @@ import {
|
|
|
28356
28724
|
import { unmarshall as unmarshall4 } from "@aws-sdk/util-dynamodb";
|
|
28357
28725
|
async function fetchSMSSpendLimits(region) {
|
|
28358
28726
|
const smsClient = new PinpointSMSVoiceV2Client({ region });
|
|
28359
|
-
const
|
|
28727
|
+
const cloudwatch6 = new CloudWatchClient2({ region });
|
|
28360
28728
|
try {
|
|
28361
28729
|
const spendLimits = await smsClient.send(
|
|
28362
28730
|
new DescribeSpendLimitsCommand({})
|
|
@@ -28370,7 +28738,7 @@ async function fetchSMSSpendLimits(region) {
|
|
|
28370
28738
|
}
|
|
28371
28739
|
const now = /* @__PURE__ */ new Date();
|
|
28372
28740
|
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
28373
|
-
const metricsResponse = await
|
|
28741
|
+
const metricsResponse = await cloudwatch6.send(
|
|
28374
28742
|
new GetMetricDataCommand2({
|
|
28375
28743
|
MetricDataQueries: [
|
|
28376
28744
|
{
|
|
@@ -29370,7 +29738,7 @@ import pc41 from "picocolors";
|
|
|
29370
29738
|
// src/infrastructure/sms-stack.ts
|
|
29371
29739
|
init_esm_shims();
|
|
29372
29740
|
init_constants();
|
|
29373
|
-
import * as
|
|
29741
|
+
import * as aws19 from "@pulumi/aws";
|
|
29374
29742
|
import * as pulumi24 from "@pulumi/pulumi";
|
|
29375
29743
|
init_resource_checks();
|
|
29376
29744
|
async function createSMSIAMRole(config2) {
|
|
@@ -29467,7 +29835,7 @@ async function createSMSIAMRole(config2) {
|
|
|
29467
29835
|
});
|
|
29468
29836
|
}
|
|
29469
29837
|
function createSMSConfigurationSet() {
|
|
29470
|
-
return new
|
|
29838
|
+
return new aws19.pinpoint.Smsvoicev2ConfigurationSet("wraps-sms-config", {
|
|
29471
29839
|
name: "wraps-sms-config",
|
|
29472
29840
|
defaultMessageType: "TRANSACTIONAL",
|
|
29473
29841
|
tags: {
|
|
@@ -29477,7 +29845,7 @@ function createSMSConfigurationSet() {
|
|
|
29477
29845
|
});
|
|
29478
29846
|
}
|
|
29479
29847
|
function createSMSOptOutList() {
|
|
29480
|
-
return new
|
|
29848
|
+
return new aws19.pinpoint.Smsvoicev2OptOutList("wraps-sms-optouts", {
|
|
29481
29849
|
name: "wraps-sms-optouts",
|
|
29482
29850
|
tags: {
|
|
29483
29851
|
ManagedBy: "wraps-cli",
|
|
@@ -29516,8 +29884,12 @@ async function findExistingPhoneNumber(phoneNumberType) {
|
|
|
29516
29884
|
}
|
|
29517
29885
|
}
|
|
29518
29886
|
return null;
|
|
29519
|
-
} catch {
|
|
29520
|
-
|
|
29887
|
+
} catch (error) {
|
|
29888
|
+
const name = error instanceof Error ? error.name : "";
|
|
29889
|
+
if (name === "ResourceNotFoundException") {
|
|
29890
|
+
return null;
|
|
29891
|
+
}
|
|
29892
|
+
throw error;
|
|
29521
29893
|
}
|
|
29522
29894
|
}
|
|
29523
29895
|
async function createSMSPhoneNumber(phoneNumberType, optOutList) {
|
|
@@ -29541,7 +29913,7 @@ async function createSMSPhoneNumber(phoneNumberType, optOutList) {
|
|
|
29541
29913
|
}
|
|
29542
29914
|
};
|
|
29543
29915
|
if (existingArn) {
|
|
29544
|
-
return new
|
|
29916
|
+
return new aws19.pinpoint.Smsvoicev2PhoneNumber(
|
|
29545
29917
|
"wraps-sms-number",
|
|
29546
29918
|
phoneConfig,
|
|
29547
29919
|
{
|
|
@@ -29550,7 +29922,7 @@ async function createSMSPhoneNumber(phoneNumberType, optOutList) {
|
|
|
29550
29922
|
}
|
|
29551
29923
|
);
|
|
29552
29924
|
}
|
|
29553
|
-
return new
|
|
29925
|
+
return new aws19.pinpoint.Smsvoicev2PhoneNumber(
|
|
29554
29926
|
"wraps-sms-number",
|
|
29555
29927
|
phoneConfig,
|
|
29556
29928
|
{
|
|
@@ -29579,10 +29951,10 @@ async function createSMSSQSResources() {
|
|
|
29579
29951
|
Description: "Dead letter queue for failed SMS event processing"
|
|
29580
29952
|
}
|
|
29581
29953
|
};
|
|
29582
|
-
const dlq = dlqUrl ? new
|
|
29954
|
+
const dlq = dlqUrl ? new aws19.sqs.Queue(dlqName, dlqConfig, {
|
|
29583
29955
|
import: dlqUrl,
|
|
29584
29956
|
customTimeouts: { create: "2m", update: "2m", delete: "2m" }
|
|
29585
|
-
}) : new
|
|
29957
|
+
}) : new aws19.sqs.Queue(dlqName, dlqConfig, {
|
|
29586
29958
|
customTimeouts: { create: "2m", update: "2m", delete: "2m" }
|
|
29587
29959
|
});
|
|
29588
29960
|
const queueConfig = {
|
|
@@ -29605,10 +29977,10 @@ async function createSMSSQSResources() {
|
|
|
29605
29977
|
Description: "Queue for SMS events from SNS"
|
|
29606
29978
|
}
|
|
29607
29979
|
};
|
|
29608
|
-
const queue = queueUrl ? new
|
|
29980
|
+
const queue = queueUrl ? new aws19.sqs.Queue(queueName, queueConfig, {
|
|
29609
29981
|
import: queueUrl,
|
|
29610
29982
|
customTimeouts: { create: "2m", update: "2m", delete: "2m" }
|
|
29611
|
-
}) : new
|
|
29983
|
+
}) : new aws19.sqs.Queue(queueName, queueConfig, {
|
|
29612
29984
|
customTimeouts: { create: "2m", update: "2m", delete: "2m" }
|
|
29613
29985
|
});
|
|
29614
29986
|
return { queue, dlq };
|
|
@@ -29624,13 +29996,13 @@ async function createSMSSNSResources(config2) {
|
|
|
29624
29996
|
Description: "SNS topic for SMS delivery events"
|
|
29625
29997
|
}
|
|
29626
29998
|
};
|
|
29627
|
-
const topic = topicArn ? new
|
|
29999
|
+
const topic = topicArn ? new aws19.sns.Topic("wraps-sms-events-topic", topicConfig, {
|
|
29628
30000
|
import: topicArn,
|
|
29629
30001
|
customTimeouts: { create: "2m", update: "2m", delete: "2m" }
|
|
29630
|
-
}) : new
|
|
30002
|
+
}) : new aws19.sns.Topic("wraps-sms-events-topic", topicConfig, {
|
|
29631
30003
|
customTimeouts: { create: "2m", update: "2m", delete: "2m" }
|
|
29632
30004
|
});
|
|
29633
|
-
new
|
|
30005
|
+
new aws19.sns.TopicPolicy("wraps-sms-events-topic-policy", {
|
|
29634
30006
|
arn: topic.arn,
|
|
29635
30007
|
policy: topic.arn.apply(
|
|
29636
30008
|
(topicArn2) => JSON.stringify({
|
|
@@ -29647,7 +30019,7 @@ async function createSMSSNSResources(config2) {
|
|
|
29647
30019
|
})
|
|
29648
30020
|
)
|
|
29649
30021
|
});
|
|
29650
|
-
new
|
|
30022
|
+
new aws19.sqs.QueuePolicy("wraps-sms-events-queue-policy", {
|
|
29651
30023
|
queueUrl: config2.queueUrl,
|
|
29652
30024
|
policy: pulumi24.all([config2.queueArn, topic.arn]).apply(
|
|
29653
30025
|
([queueArn, topicArn2]) => JSON.stringify({
|
|
@@ -29666,7 +30038,7 @@ async function createSMSSNSResources(config2) {
|
|
|
29666
30038
|
})
|
|
29667
30039
|
)
|
|
29668
30040
|
});
|
|
29669
|
-
const subscription = new
|
|
30041
|
+
const subscription = new aws19.sns.TopicSubscription(
|
|
29670
30042
|
"wraps-sms-events-subscription",
|
|
29671
30043
|
{
|
|
29672
30044
|
topic: topic.arn,
|
|
@@ -29715,17 +30087,17 @@ async function createSMSDynamoDBTable() {
|
|
|
29715
30087
|
Service: "sms"
|
|
29716
30088
|
}
|
|
29717
30089
|
};
|
|
29718
|
-
return exists ? new
|
|
30090
|
+
return exists ? new aws19.dynamodb.Table(tableName, tableConfig, {
|
|
29719
30091
|
import: tableName,
|
|
29720
30092
|
customTimeouts: { create: "5m", update: "5m", delete: "5m" }
|
|
29721
|
-
}) : new
|
|
30093
|
+
}) : new aws19.dynamodb.Table(tableName, tableConfig, {
|
|
29722
30094
|
customTimeouts: { create: "5m", update: "5m", delete: "5m" }
|
|
29723
30095
|
});
|
|
29724
30096
|
}
|
|
29725
30097
|
async function deploySMSLambdaFunction(config2) {
|
|
29726
30098
|
const { getLambdaCode: getLambdaCode2 } = await Promise.resolve().then(() => (init_lambda(), lambda_exports));
|
|
29727
30099
|
const codeDir = await getLambdaCode2("sms-event-processor");
|
|
29728
|
-
const lambdaRole = new
|
|
30100
|
+
const lambdaRole = new aws19.iam.Role("wraps-sms-lambda-role", {
|
|
29729
30101
|
name: "wraps-sms-lambda-role",
|
|
29730
30102
|
assumeRolePolicy: JSON.stringify({
|
|
29731
30103
|
Version: "2012-10-17",
|
|
@@ -29742,11 +30114,11 @@ async function deploySMSLambdaFunction(config2) {
|
|
|
29742
30114
|
Service: "sms"
|
|
29743
30115
|
}
|
|
29744
30116
|
});
|
|
29745
|
-
new
|
|
30117
|
+
new aws19.iam.RolePolicyAttachment("wraps-sms-lambda-basic-execution", {
|
|
29746
30118
|
role: lambdaRole.name,
|
|
29747
30119
|
policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
29748
30120
|
});
|
|
29749
|
-
new
|
|
30121
|
+
new aws19.iam.RolePolicy("wraps-sms-lambda-policy", {
|
|
29750
30122
|
role: lambdaRole.name,
|
|
29751
30123
|
policy: pulumi24.all([config2.tableName, config2.queueArn]).apply(
|
|
29752
30124
|
([tableName, queueArn]) => JSON.stringify({
|
|
@@ -29778,7 +30150,7 @@ async function deploySMSLambdaFunction(config2) {
|
|
|
29778
30150
|
})
|
|
29779
30151
|
)
|
|
29780
30152
|
});
|
|
29781
|
-
const eventProcessor = new
|
|
30153
|
+
const eventProcessor = new aws19.lambda.Function(
|
|
29782
30154
|
"wraps-sms-event-processor",
|
|
29783
30155
|
{
|
|
29784
30156
|
name: "wraps-sms-event-processor",
|
|
@@ -29806,7 +30178,7 @@ async function deploySMSLambdaFunction(config2) {
|
|
|
29806
30178
|
customTimeouts: { create: "5m", update: "5m", delete: "2m" }
|
|
29807
30179
|
}
|
|
29808
30180
|
);
|
|
29809
|
-
new
|
|
30181
|
+
new aws19.lambda.EventSourceMapping(
|
|
29810
30182
|
"wraps-sms-event-source-mapping",
|
|
29811
30183
|
{
|
|
29812
30184
|
eventSourceArn: config2.queueArn,
|
|
@@ -29822,7 +30194,7 @@ async function deploySMSLambdaFunction(config2) {
|
|
|
29822
30194
|
return eventProcessor;
|
|
29823
30195
|
}
|
|
29824
30196
|
async function deploySMSStack(config2) {
|
|
29825
|
-
const identity = await
|
|
30197
|
+
const identity = await aws19.getCallerIdentity();
|
|
29826
30198
|
const accountId = identity.accountId;
|
|
29827
30199
|
let oidcProvider;
|
|
29828
30200
|
if (config2.provider === "vercel" && config2.vercel) {
|
|
@@ -29904,7 +30276,12 @@ async function createSMSPhonePoolWithSDK(phoneNumberArn, region) {
|
|
|
29904
30276
|
}
|
|
29905
30277
|
}
|
|
29906
30278
|
}
|
|
29907
|
-
} catch {
|
|
30279
|
+
} catch (error) {
|
|
30280
|
+
const name = error instanceof Error ? error.name : "";
|
|
30281
|
+
if (name === "ResourceNotFoundException") {
|
|
30282
|
+
} else {
|
|
30283
|
+
throw error;
|
|
30284
|
+
}
|
|
29908
30285
|
}
|
|
29909
30286
|
try {
|
|
29910
30287
|
const response = await client.send(
|
|
@@ -29920,8 +30297,12 @@ async function createSMSPhonePoolWithSDK(phoneNumberArn, region) {
|
|
|
29920
30297
|
})
|
|
29921
30298
|
);
|
|
29922
30299
|
return response.PoolId;
|
|
29923
|
-
} catch {
|
|
29924
|
-
|
|
30300
|
+
} catch (error) {
|
|
30301
|
+
const name = error instanceof Error ? error.name : "";
|
|
30302
|
+
if (name === "ConflictException") {
|
|
30303
|
+
return;
|
|
30304
|
+
}
|
|
30305
|
+
throw error;
|
|
29925
30306
|
}
|
|
29926
30307
|
}
|
|
29927
30308
|
async function createSMSEventDestinationWithSDK(configurationSetName, snsTopicArn, region) {
|
|
@@ -29947,7 +30328,12 @@ async function createSMSEventDestinationWithSDK(configurationSetName, snsTopicAr
|
|
|
29947
30328
|
return existingDest.EventDestinationName;
|
|
29948
30329
|
}
|
|
29949
30330
|
}
|
|
29950
|
-
} catch {
|
|
30331
|
+
} catch (error) {
|
|
30332
|
+
const name = error instanceof Error ? error.name : "";
|
|
30333
|
+
if (name === "ResourceNotFoundException") {
|
|
30334
|
+
} else {
|
|
30335
|
+
throw error;
|
|
30336
|
+
}
|
|
29951
30337
|
}
|
|
29952
30338
|
try {
|
|
29953
30339
|
const response = await client.send(
|
|
@@ -29961,8 +30347,12 @@ async function createSMSEventDestinationWithSDK(configurationSetName, snsTopicAr
|
|
|
29961
30347
|
})
|
|
29962
30348
|
);
|
|
29963
30349
|
return response.EventDestination?.EventDestinationName || eventDestinationName;
|
|
29964
|
-
} catch {
|
|
29965
|
-
|
|
30350
|
+
} catch (error) {
|
|
30351
|
+
const name = error instanceof Error ? error.name : "";
|
|
30352
|
+
if (name === "ConflictException") {
|
|
30353
|
+
return eventDestinationName;
|
|
30354
|
+
}
|
|
30355
|
+
throw error;
|
|
29966
30356
|
}
|
|
29967
30357
|
}
|
|
29968
30358
|
async function deleteSMSPhonePoolWithSDK(region) {
|
|
@@ -29991,7 +30381,12 @@ async function deleteSMSPhonePoolWithSDK(region) {
|
|
|
29991
30381
|
}
|
|
29992
30382
|
}
|
|
29993
30383
|
}
|
|
29994
|
-
} catch {
|
|
30384
|
+
} catch (error) {
|
|
30385
|
+
const name = error instanceof Error ? error.name : "";
|
|
30386
|
+
if (name === "ResourceNotFoundException") {
|
|
30387
|
+
} else {
|
|
30388
|
+
throw error;
|
|
30389
|
+
}
|
|
29995
30390
|
}
|
|
29996
30391
|
}
|
|
29997
30392
|
async function deleteSMSEventDestinationWithSDK(configurationSetName, region) {
|
|
@@ -30005,7 +30400,12 @@ async function deleteSMSEventDestinationWithSDK(configurationSetName, region) {
|
|
|
30005
30400
|
EventDestinationName: eventDestinationName
|
|
30006
30401
|
})
|
|
30007
30402
|
);
|
|
30008
|
-
} catch {
|
|
30403
|
+
} catch (error) {
|
|
30404
|
+
const name = error instanceof Error ? error.name : "";
|
|
30405
|
+
if (name === "ResourceNotFoundException") {
|
|
30406
|
+
} else {
|
|
30407
|
+
throw error;
|
|
30408
|
+
}
|
|
30009
30409
|
}
|
|
30010
30410
|
}
|
|
30011
30411
|
async function createSMSProtectConfigurationWithSDK(configurationSetName, region, options) {
|
|
@@ -30040,7 +30440,12 @@ async function createSMSProtectConfigurationWithSDK(configurationSetName, region
|
|
|
30040
30440
|
break;
|
|
30041
30441
|
}
|
|
30042
30442
|
}
|
|
30043
|
-
} catch {
|
|
30443
|
+
} catch (error) {
|
|
30444
|
+
const name = error instanceof Error ? error.name : "";
|
|
30445
|
+
if (name === "ResourceNotFoundException") {
|
|
30446
|
+
} else {
|
|
30447
|
+
throw error;
|
|
30448
|
+
}
|
|
30044
30449
|
}
|
|
30045
30450
|
try {
|
|
30046
30451
|
let protectConfigId = existingProtectConfigId;
|
|
@@ -30106,8 +30511,12 @@ async function createSMSProtectConfigurationWithSDK(configurationSetName, region
|
|
|
30106
30511
|
);
|
|
30107
30512
|
}
|
|
30108
30513
|
return protectConfigId;
|
|
30109
|
-
} catch {
|
|
30110
|
-
|
|
30514
|
+
} catch (error) {
|
|
30515
|
+
const name = error instanceof Error ? error.name : "";
|
|
30516
|
+
if (name === "ConflictException") {
|
|
30517
|
+
return;
|
|
30518
|
+
}
|
|
30519
|
+
throw error;
|
|
30111
30520
|
}
|
|
30112
30521
|
}
|
|
30113
30522
|
async function deleteSMSProtectConfigurationWithSDK(region) {
|
|
@@ -30144,7 +30553,12 @@ async function deleteSMSProtectConfigurationWithSDK(region) {
|
|
|
30144
30553
|
}
|
|
30145
30554
|
}
|
|
30146
30555
|
}
|
|
30147
|
-
} catch {
|
|
30556
|
+
} catch (error) {
|
|
30557
|
+
const name = error instanceof Error ? error.name : "";
|
|
30558
|
+
if (name === "ResourceNotFoundException") {
|
|
30559
|
+
} else {
|
|
30560
|
+
throw error;
|
|
30561
|
+
}
|
|
30148
30562
|
}
|
|
30149
30563
|
}
|
|
30150
30564
|
|
|
@@ -30167,7 +30581,29 @@ async function smsDestroy(options) {
|
|
|
30167
30581
|
"Validating AWS credentials",
|
|
30168
30582
|
async () => validateAWSCredentials()
|
|
30169
30583
|
);
|
|
30170
|
-
|
|
30584
|
+
let region = options.region || await getAWSRegion();
|
|
30585
|
+
if (!(options.region || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION)) {
|
|
30586
|
+
const smsConnections = await findConnectionsWithService(
|
|
30587
|
+
identity.accountId,
|
|
30588
|
+
"sms"
|
|
30589
|
+
);
|
|
30590
|
+
if (smsConnections.length === 1) {
|
|
30591
|
+
region = smsConnections[0].region;
|
|
30592
|
+
} else if (smsConnections.length > 1) {
|
|
30593
|
+
const selectedRegion = await clack38.select({
|
|
30594
|
+
message: "Multiple SMS deployments found. Which region to destroy?",
|
|
30595
|
+
options: smsConnections.map((conn) => ({
|
|
30596
|
+
value: conn.region,
|
|
30597
|
+
label: conn.region
|
|
30598
|
+
}))
|
|
30599
|
+
});
|
|
30600
|
+
if (clack38.isCancel(selectedRegion)) {
|
|
30601
|
+
clack38.cancel("Operation cancelled");
|
|
30602
|
+
process.exit(0);
|
|
30603
|
+
}
|
|
30604
|
+
region = selectedRegion;
|
|
30605
|
+
}
|
|
30606
|
+
}
|
|
30171
30607
|
const metadata = await loadConnectionMetadata(identity.accountId, region);
|
|
30172
30608
|
const smsService = metadata?.services?.sms;
|
|
30173
30609
|
const storedStackName = smsService?.pulumiStackName;
|
|
@@ -30246,6 +30682,7 @@ async function smsDestroy(options) {
|
|
|
30246
30682
|
await progress.execute("Cleaning up protect configuration", async () => {
|
|
30247
30683
|
await deleteSMSProtectConfigurationWithSDK(region);
|
|
30248
30684
|
});
|
|
30685
|
+
let destroyFailed = false;
|
|
30249
30686
|
try {
|
|
30250
30687
|
await progress.execute(
|
|
30251
30688
|
"Destroying SMS infrastructure (this may take 2-3 minutes)",
|
|
@@ -30261,8 +30698,14 @@ async function smsDestroy(options) {
|
|
|
30261
30698
|
} catch (_error) {
|
|
30262
30699
|
throw new Error("No SMS infrastructure found to destroy");
|
|
30263
30700
|
}
|
|
30264
|
-
await stack.
|
|
30701
|
+
await stack.refresh({ onOutput: () => {
|
|
30265
30702
|
} });
|
|
30703
|
+
await withTimeout(
|
|
30704
|
+
stack.destroy({ onOutput: () => {
|
|
30705
|
+
}, continueOnError: true }),
|
|
30706
|
+
DEFAULT_PULUMI_TIMEOUT_MS,
|
|
30707
|
+
"Pulumi destroy"
|
|
30708
|
+
);
|
|
30266
30709
|
await stack.workspace.removeStack(stackName);
|
|
30267
30710
|
}
|
|
30268
30711
|
);
|
|
@@ -30282,21 +30725,29 @@ async function smsDestroy(options) {
|
|
|
30282
30725
|
throw errors.stackLocked();
|
|
30283
30726
|
}
|
|
30284
30727
|
trackError("DESTROY_FAILED", "sms destroy", { step: "destroy" });
|
|
30285
|
-
|
|
30286
|
-
|
|
30728
|
+
destroyFailed = true;
|
|
30729
|
+
clack38.log.warn(
|
|
30730
|
+
"Some resources may not have been fully removed. You can re-run this command or clean up manually in the AWS console."
|
|
30731
|
+
);
|
|
30287
30732
|
}
|
|
30288
30733
|
if (metadata) {
|
|
30289
30734
|
removeServiceFromConnection(metadata, "sms");
|
|
30290
30735
|
await saveConnectionMetadata(metadata);
|
|
30291
30736
|
}
|
|
30292
30737
|
progress.stop();
|
|
30293
|
-
|
|
30294
|
-
|
|
30738
|
+
if (destroyFailed) {
|
|
30739
|
+
clack38.outro(
|
|
30740
|
+
pc41.yellow("SMS infrastructure partially removed. Metadata cleaned up.")
|
|
30741
|
+
);
|
|
30742
|
+
} else {
|
|
30743
|
+
clack38.outro(pc41.green("SMS infrastructure has been removed"));
|
|
30744
|
+
console.log(`
|
|
30295
30745
|
${pc41.bold("Cleaned up:")}`);
|
|
30296
|
-
|
|
30297
|
-
|
|
30298
|
-
|
|
30299
|
-
|
|
30746
|
+
console.log(` ${pc41.green("\u2713")} Phone number released`);
|
|
30747
|
+
console.log(` ${pc41.green("\u2713")} Configuration set deleted`);
|
|
30748
|
+
console.log(` ${pc41.green("\u2713")} Event processing infrastructure removed`);
|
|
30749
|
+
console.log(` ${pc41.green("\u2713")} IAM role deleted`);
|
|
30750
|
+
}
|
|
30300
30751
|
console.log(
|
|
30301
30752
|
`
|
|
30302
30753
|
Run ${pc41.cyan("wraps sms init")} to deploy infrastructure again.
|
|
@@ -30304,6 +30755,7 @@ Run ${pc41.cyan("wraps sms init")} to deploy infrastructure again.
|
|
|
30304
30755
|
);
|
|
30305
30756
|
trackServiceRemoved("sms", {
|
|
30306
30757
|
reason: "user_initiated",
|
|
30758
|
+
partial_failure: destroyFailed,
|
|
30307
30759
|
duration_ms: Date.now() - startTime
|
|
30308
30760
|
});
|
|
30309
30761
|
}
|
|
@@ -31007,31 +31459,64 @@ ${pc42.yellow(pc42.bold("Important Notes:"))}`);
|
|
|
31007
31459
|
};
|
|
31008
31460
|
}
|
|
31009
31461
|
);
|
|
31462
|
+
let sdkResourceWarning = false;
|
|
31010
31463
|
if (outputs.phoneNumberArn) {
|
|
31011
|
-
|
|
31012
|
-
await
|
|
31013
|
-
|
|
31464
|
+
try {
|
|
31465
|
+
await progress.execute("Creating phone pool", async () => {
|
|
31466
|
+
await createSMSPhonePoolWithSDK(outputs.phoneNumberArn, region);
|
|
31467
|
+
});
|
|
31468
|
+
} catch (sdkError) {
|
|
31469
|
+
sdkResourceWarning = true;
|
|
31470
|
+
const msg = sdkError instanceof Error ? sdkError.message : String(sdkError);
|
|
31471
|
+
clack39.log.warn(`Phone pool creation failed: ${msg}`);
|
|
31472
|
+
clack39.log.info(
|
|
31473
|
+
`Run ${pc42.cyan("wraps sms sync")} to retry SDK resource creation.`
|
|
31474
|
+
);
|
|
31475
|
+
}
|
|
31014
31476
|
}
|
|
31015
31477
|
if (smsConfig.eventTracking?.enabled && outputs.configSetName && outputs.snsTopicArn) {
|
|
31016
|
-
|
|
31017
|
-
await
|
|
31018
|
-
|
|
31019
|
-
|
|
31020
|
-
|
|
31478
|
+
try {
|
|
31479
|
+
await progress.execute("Configuring event destination", async () => {
|
|
31480
|
+
await createSMSEventDestinationWithSDK(
|
|
31481
|
+
outputs.configSetName,
|
|
31482
|
+
outputs.snsTopicArn,
|
|
31483
|
+
region
|
|
31484
|
+
);
|
|
31485
|
+
});
|
|
31486
|
+
} catch (sdkError) {
|
|
31487
|
+
sdkResourceWarning = true;
|
|
31488
|
+
const msg = sdkError instanceof Error ? sdkError.message : String(sdkError);
|
|
31489
|
+
clack39.log.warn(`Event destination creation failed: ${msg}`);
|
|
31490
|
+
clack39.log.info(
|
|
31491
|
+
`Run ${pc42.cyan("wraps sms sync")} to retry SDK resource creation.`
|
|
31021
31492
|
);
|
|
31022
|
-
}
|
|
31493
|
+
}
|
|
31023
31494
|
}
|
|
31024
31495
|
if (smsConfig.protectConfiguration?.enabled && outputs.configSetName) {
|
|
31025
|
-
|
|
31026
|
-
await
|
|
31027
|
-
|
|
31028
|
-
|
|
31029
|
-
|
|
31030
|
-
|
|
31031
|
-
|
|
31032
|
-
|
|
31496
|
+
try {
|
|
31497
|
+
await progress.execute("Configuring fraud protection", async () => {
|
|
31498
|
+
await createSMSProtectConfigurationWithSDK(
|
|
31499
|
+
outputs.configSetName,
|
|
31500
|
+
region,
|
|
31501
|
+
{
|
|
31502
|
+
allowedCountries: smsConfig.protectConfiguration?.allowedCountries,
|
|
31503
|
+
aitFiltering: smsConfig.protectConfiguration?.aitFiltering
|
|
31504
|
+
}
|
|
31505
|
+
);
|
|
31506
|
+
});
|
|
31507
|
+
} catch (sdkError) {
|
|
31508
|
+
sdkResourceWarning = true;
|
|
31509
|
+
const msg = sdkError instanceof Error ? sdkError.message : String(sdkError);
|
|
31510
|
+
clack39.log.warn(`Protect configuration creation failed: ${msg}`);
|
|
31511
|
+
clack39.log.info(
|
|
31512
|
+
`Run ${pc42.cyan("wraps sms sync")} to retry SDK resource creation.`
|
|
31033
31513
|
);
|
|
31034
|
-
}
|
|
31514
|
+
}
|
|
31515
|
+
}
|
|
31516
|
+
if (sdkResourceWarning) {
|
|
31517
|
+
clack39.log.warn(
|
|
31518
|
+
"Some SDK resources failed to create. Core infrastructure is deployed."
|
|
31519
|
+
);
|
|
31035
31520
|
}
|
|
31036
31521
|
} catch (error) {
|
|
31037
31522
|
trackServiceInit("sms", false, {
|
|
@@ -31160,7 +31645,8 @@ async function getPhoneNumberDetails(region) {
|
|
|
31160
31645
|
}
|
|
31161
31646
|
return null;
|
|
31162
31647
|
} catch (error) {
|
|
31163
|
-
|
|
31648
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
31649
|
+
clack40.log.error(`Error fetching phone number: ${errorMessage}`);
|
|
31164
31650
|
return null;
|
|
31165
31651
|
}
|
|
31166
31652
|
}
|
|
@@ -31554,7 +32040,7 @@ Run ${pc45.cyan("wraps sms init")} to deploy SMS infrastructure first.
|
|
|
31554
32040
|
smsConfig.protectConfiguration = {
|
|
31555
32041
|
enabled: true,
|
|
31556
32042
|
allowedCountries: ["US"],
|
|
31557
|
-
aitFiltering:
|
|
32043
|
+
aitFiltering: false
|
|
31558
32044
|
};
|
|
31559
32045
|
}
|
|
31560
32046
|
}
|
|
@@ -31604,9 +32090,14 @@ import {
|
|
|
31604
32090
|
} from "@aws-sdk/client-pinpoint-sms-voice-v2";
|
|
31605
32091
|
import * as clack43 from "@clack/prompts";
|
|
31606
32092
|
import pc46 from "picocolors";
|
|
32093
|
+
|
|
32094
|
+
// src/utils/sms/validation.ts
|
|
32095
|
+
init_esm_shims();
|
|
31607
32096
|
function isValidPhoneNumber(phone) {
|
|
31608
32097
|
return /^\+[1-9]\d{1,14}$/.test(phone);
|
|
31609
32098
|
}
|
|
32099
|
+
|
|
32100
|
+
// src/commands/sms/test.ts
|
|
31610
32101
|
var SIMULATOR_DESTINATIONS = [
|
|
31611
32102
|
{ number: "+14254147755", country: "United States (US)" },
|
|
31612
32103
|
{ number: "+447860019066", country: "United Kingdom (GB)" },
|
|
@@ -31776,18 +32267,39 @@ Run ${pc46.cyan("wraps sms init")} to deploy SMS infrastructure.
|
|
|
31776
32267
|
error: errorMessage,
|
|
31777
32268
|
duration_ms: Date.now() - startTime
|
|
31778
32269
|
});
|
|
31779
|
-
|
|
32270
|
+
const errorName = error instanceof Error ? error.name : "";
|
|
32271
|
+
if (errorName === "ConflictException" || errorMessage.includes("opt-out")) {
|
|
32272
|
+
clack43.log.error("Destination number has opted out of messages");
|
|
32273
|
+
console.log(
|
|
32274
|
+
`
|
|
32275
|
+
The recipient has opted out of receiving SMS messages.
|
|
32276
|
+
`
|
|
32277
|
+
);
|
|
32278
|
+
} else if (errorName === "ThrottlingException" || errorMessage.includes("spending limit")) {
|
|
32279
|
+
clack43.log.error("SMS rate or spending limit reached");
|
|
32280
|
+
console.log(
|
|
32281
|
+
`
|
|
32282
|
+
Check your AWS account SMS spending limits in the console.
|
|
32283
|
+
`
|
|
32284
|
+
);
|
|
32285
|
+
} else if (errorName === "ValidationException") {
|
|
32286
|
+
clack43.log.error(`Invalid request: ${errorMessage}`);
|
|
32287
|
+
} else if (errorMessage.includes("not verified") || errorMessage.includes("not registered")) {
|
|
31780
32288
|
clack43.log.error("Toll-free number registration is not complete");
|
|
31781
32289
|
console.log(
|
|
31782
32290
|
`
|
|
31783
|
-
Run ${pc46.cyan("wraps sms register")} to
|
|
32291
|
+
Run ${pc46.cyan("wraps sms register")} to check registration status.
|
|
32292
|
+
`
|
|
32293
|
+
);
|
|
32294
|
+
} else if (errorName === "AccessDeniedException") {
|
|
32295
|
+
clack43.log.error(
|
|
32296
|
+
"Permission denied \u2014 IAM role may be missing SMS send permissions"
|
|
32297
|
+
);
|
|
32298
|
+
console.log(
|
|
32299
|
+
`
|
|
32300
|
+
Run ${pc46.cyan("wraps sms upgrade")} to update IAM policies.
|
|
31784
32301
|
`
|
|
31785
32302
|
);
|
|
31786
|
-
} else if (errorMessage.includes("opt-out")) {
|
|
31787
|
-
clack43.log.error("Destination number has opted out of messages");
|
|
31788
|
-
} else if (errorMessage.includes("spending limit")) {
|
|
31789
|
-
clack43.log.error("AWS SMS spending limit reached");
|
|
31790
|
-
console.log("\nVisit the AWS console to increase your spending limit.\n");
|
|
31791
32303
|
} else {
|
|
31792
32304
|
clack43.log.error(`Failed to send SMS: ${errorMessage}`);
|
|
31793
32305
|
}
|
|
@@ -32740,9 +33252,6 @@ import {
|
|
|
32740
33252
|
} from "@aws-sdk/client-pinpoint-sms-voice-v2";
|
|
32741
33253
|
import * as clack45 from "@clack/prompts";
|
|
32742
33254
|
import pc48 from "picocolors";
|
|
32743
|
-
function isValidPhoneNumber2(phone) {
|
|
32744
|
-
return /^\+[1-9]\d{1,14}$/.test(phone);
|
|
32745
|
-
}
|
|
32746
33255
|
async function smsVerifyNumber(options) {
|
|
32747
33256
|
const startTime = Date.now();
|
|
32748
33257
|
const progress = new DeploymentProgress();
|
|
@@ -32863,7 +33372,7 @@ Usage: ${pc48.cyan("wraps sms verify-number --delete --phone-number +14155551234
|
|
|
32863
33372
|
if (!value) {
|
|
32864
33373
|
return "Phone number is required";
|
|
32865
33374
|
}
|
|
32866
|
-
if (!
|
|
33375
|
+
if (!isValidPhoneNumber(value)) {
|
|
32867
33376
|
return "Please enter a valid phone number in E.164 format (e.g., +14155551234)";
|
|
32868
33377
|
}
|
|
32869
33378
|
return;
|
|
@@ -32874,7 +33383,7 @@ Usage: ${pc48.cyan("wraps sms verify-number --delete --phone-number +14155551234
|
|
|
32874
33383
|
process.exit(0);
|
|
32875
33384
|
}
|
|
32876
33385
|
phoneNumber = result;
|
|
32877
|
-
} else if (!
|
|
33386
|
+
} else if (!isValidPhoneNumber(phoneNumber)) {
|
|
32878
33387
|
progress.stop();
|
|
32879
33388
|
clack45.log.error(
|
|
32880
33389
|
`Invalid phone number format: ${phoneNumber}. Use E.164 format (e.g., +14155551234)`
|
|
@@ -34140,7 +34649,8 @@ Run ${pc51.cyan("wraps --help")} for available commands.
|
|
|
34140
34649
|
case "destroy":
|
|
34141
34650
|
await smsDestroy({
|
|
34142
34651
|
force: flags.force,
|
|
34143
|
-
preview: flags.preview
|
|
34652
|
+
preview: flags.preview,
|
|
34653
|
+
region: flags.region
|
|
34144
34654
|
});
|
|
34145
34655
|
break;
|
|
34146
34656
|
case "verify-number":
|