@wraps.dev/cli 2.23.2 → 2.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-lambda.zip +0 -0
- package/dist/cli.js +633 -45
- package/dist/cli.js.map +1 -1
- package/dist/console/assets/{index-e_Wzcxes.js → index-5HGEQ1Ul.js} +58 -58
- package/dist/console/assets/index-BP4FpEpn.css +1 -0
- package/dist/console/index.html +2 -2
- package/dist/lambda/event-processor/.bundled +1 -1
- package/dist/lambda/event-processor/index.js +3 -2
- package/dist/lambda/inbound-processor/.bundled +1 -1
- package/dist/lambda/inbound-processor/index.js +60 -67
- package/dist/lambda/sms-event-processor/.bundled +1 -1
- package/dist/lambda/sms-event-processor/index.js +3 -2
- package/dist/selfhost-migrations/0059_message_send_claimed_at.sql +1 -0
- package/dist/selfhost-migrations/0060_harsh_nehzno.sql +1 -0
- package/dist/selfhost-migrations/0061_flashy_spirit.sql +1 -0
- package/dist/selfhost-migrations/0062_next_timeslip.sql +2 -0
- package/dist/selfhost-migrations/CLAUDE.md +49 -0
- package/dist/selfhost-migrations/meta/0055_snapshot.json +39 -3
- package/dist/selfhost-migrations/meta/0056_snapshot.json +39 -3
- package/dist/selfhost-migrations/meta/0057_snapshot.json +34 -2
- package/dist/selfhost-migrations/meta/0059_snapshot.json +6764 -0
- package/dist/selfhost-migrations/meta/0060_snapshot.json +7125 -0
- package/dist/selfhost-migrations/meta/0061_snapshot.json +7131 -0
- package/dist/selfhost-migrations/meta/0062_snapshot.json +7143 -0
- package/dist/selfhost-migrations/meta/_journal.json +29 -1
- package/package.json +35 -34
- package/dist/cli.d.ts +0 -1
- package/dist/console/assets/index-CXXcMD9d.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -9,12 +9,12 @@ var __export = (target, all6) => {
|
|
|
9
9
|
__defProp(target, name, { get: all6[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.
|
|
12
|
+
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.15_tsx@4.20.6_typescript@5.9.3_yaml@2.8.4/node_modules/tsup/assets/esm_shims.js
|
|
13
13
|
import path from "path";
|
|
14
14
|
import { fileURLToPath } from "url";
|
|
15
15
|
var getFilename, getDirname, __dirname;
|
|
16
16
|
var init_esm_shims = __esm({
|
|
17
|
-
"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.
|
|
17
|
+
"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.15_tsx@4.20.6_typescript@5.9.3_yaml@2.8.4/node_modules/tsup/assets/esm_shims.js"() {
|
|
18
18
|
"use strict";
|
|
19
19
|
getFilename = () => fileURLToPath(import.meta.url);
|
|
20
20
|
getDirname = () => path.dirname(getFilename());
|
|
@@ -5583,7 +5583,7 @@ __export(metadata_exports, {
|
|
|
5583
5583
|
});
|
|
5584
5584
|
import { randomBytes } from "crypto";
|
|
5585
5585
|
import { existsSync as existsSync5 } from "fs";
|
|
5586
|
-
import { readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
5586
|
+
import { chmod as chmod2, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
5587
5587
|
import { join as join6 } from "path";
|
|
5588
5588
|
function getConnectionsDir() {
|
|
5589
5589
|
return join6(getWrapsDir(), "connections");
|
|
@@ -5695,6 +5695,7 @@ async function saveConnectionMetadataLocal(metadata) {
|
|
|
5695
5695
|
const metadataPath = getMetadataPath(metadata.accountId, metadata.region);
|
|
5696
5696
|
const content = JSON.stringify(metadata, null, 2);
|
|
5697
5697
|
await writeFile3(metadataPath, content, "utf-8");
|
|
5698
|
+
await chmod2(metadataPath, 384);
|
|
5698
5699
|
}
|
|
5699
5700
|
async function saveConnectionMetadata(metadata) {
|
|
5700
5701
|
await ensureConnectionsDir();
|
|
@@ -5702,6 +5703,7 @@ async function saveConnectionMetadata(metadata) {
|
|
|
5702
5703
|
try {
|
|
5703
5704
|
const content = JSON.stringify(metadata, null, 2);
|
|
5704
5705
|
await writeFile3(metadataPath, content, "utf-8");
|
|
5706
|
+
await chmod2(metadataPath, 384);
|
|
5705
5707
|
} catch (error) {
|
|
5706
5708
|
console.error(
|
|
5707
5709
|
"Error saving connection metadata:",
|
|
@@ -8038,7 +8040,11 @@ async function createMailManagerArchive(config2) {
|
|
|
8038
8040
|
);
|
|
8039
8041
|
archiveArn = getResult.ArchiveArn;
|
|
8040
8042
|
}
|
|
8041
|
-
} catch {
|
|
8043
|
+
} catch (error) {
|
|
8044
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
8045
|
+
throw new Error(
|
|
8046
|
+
`Failed to list existing Mail Manager archives in ${region}: ${detail}`
|
|
8047
|
+
);
|
|
8042
8048
|
}
|
|
8043
8049
|
if (!archiveId) {
|
|
8044
8050
|
for (let attempt = 1; attempt <= MAX_NAME_ATTEMPTS; attempt++) {
|
|
@@ -8064,7 +8070,8 @@ async function createMailManagerArchive(config2) {
|
|
|
8064
8070
|
}
|
|
8065
8071
|
break;
|
|
8066
8072
|
} catch (error) {
|
|
8067
|
-
|
|
8073
|
+
const isConflict = error instanceof Error && (error.name === "ConflictException" || error.message.includes("ConflictException"));
|
|
8074
|
+
if (isConflict && attempt < MAX_NAME_ATTEMPTS) {
|
|
8068
8075
|
continue;
|
|
8069
8076
|
}
|
|
8070
8077
|
throw error;
|
|
@@ -17832,8 +17839,19 @@ async function createEventBridgeResources(config2) {
|
|
|
17832
17839
|
const target = new aws7.cloudwatch.EventTarget("wraps-email-events-target", {
|
|
17833
17840
|
rule: rule.name,
|
|
17834
17841
|
eventBusName,
|
|
17835
|
-
arn: config2.queueArn
|
|
17842
|
+
arn: config2.queueArn,
|
|
17843
|
+
deadLetterConfig: { arn: config2.dlqArn }
|
|
17836
17844
|
});
|
|
17845
|
+
const archive = new aws7.cloudwatch.EventArchive(
|
|
17846
|
+
"wraps-email-events-archive",
|
|
17847
|
+
{
|
|
17848
|
+
name: "wraps-email-events-archive",
|
|
17849
|
+
eventSourceArn: config2.eventBusArn,
|
|
17850
|
+
description: "Wraps: 30-day archive of SES events for outage replay (see wraps email doctor)",
|
|
17851
|
+
retentionDays: 30,
|
|
17852
|
+
eventPattern: JSON.stringify({ source: ["aws.ses"] })
|
|
17853
|
+
}
|
|
17854
|
+
);
|
|
17837
17855
|
let webhookConnection;
|
|
17838
17856
|
let webhookApiDestination;
|
|
17839
17857
|
let webhookTarget;
|
|
@@ -17904,7 +17922,8 @@ async function createEventBridgeResources(config2) {
|
|
|
17904
17922
|
rule: rule.name,
|
|
17905
17923
|
eventBusName,
|
|
17906
17924
|
arn: webhookApiDestination.arn,
|
|
17907
|
-
roleArn: webhookRole.arn
|
|
17925
|
+
roleArn: webhookRole.arn,
|
|
17926
|
+
deadLetterConfig: { arn: config2.dlqArn }
|
|
17908
17927
|
});
|
|
17909
17928
|
}
|
|
17910
17929
|
let userWebhookConnection;
|
|
@@ -17922,9 +17941,33 @@ async function createEventBridgeResources(config2) {
|
|
|
17922
17941
|
userWebhookApiDestination = userWebhookResources.apiDestination;
|
|
17923
17942
|
userWebhookTarget = userWebhookResources.target;
|
|
17924
17943
|
}
|
|
17944
|
+
const alarmActions = config2.alertTopicArn ? [config2.alertTopicArn] : void 0;
|
|
17945
|
+
const failedInvocationsAlarm = new aws7.cloudwatch.MetricAlarm(
|
|
17946
|
+
"wraps-email-events-delivery-failures",
|
|
17947
|
+
{
|
|
17948
|
+
name: "wraps-email-events-delivery-failures",
|
|
17949
|
+
namespace: "AWS/Events",
|
|
17950
|
+
metricName: "FailedInvocations",
|
|
17951
|
+
dimensions: { RuleName: "wraps-email-events-to-sqs" },
|
|
17952
|
+
statistic: "Sum",
|
|
17953
|
+
period: 300,
|
|
17954
|
+
evaluationPeriods: 3,
|
|
17955
|
+
threshold: 1,
|
|
17956
|
+
comparisonOperator: "GreaterThanOrEqualToThreshold",
|
|
17957
|
+
treatMissingData: "notBreaching",
|
|
17958
|
+
alarmDescription: "SES event delivery from EventBridge is failing (dead target or deauthorized webhook). Event history is being lost \u2014 run `wraps email doctor`.",
|
|
17959
|
+
...alarmActions ? { alarmActions, okActions: alarmActions } : {},
|
|
17960
|
+
tags: {
|
|
17961
|
+
ManagedBy: "wraps-cli",
|
|
17962
|
+
Service: "email"
|
|
17963
|
+
}
|
|
17964
|
+
}
|
|
17965
|
+
);
|
|
17925
17966
|
return {
|
|
17926
17967
|
rule,
|
|
17927
17968
|
target,
|
|
17969
|
+
failedInvocationsAlarm,
|
|
17970
|
+
archive,
|
|
17928
17971
|
webhookConnection,
|
|
17929
17972
|
webhookApiDestination,
|
|
17930
17973
|
webhookTarget,
|
|
@@ -18369,6 +18412,30 @@ async function createSQSResources(config2) {
|
|
|
18369
18412
|
...SQS_TIMEOUTS,
|
|
18370
18413
|
...dlqUrl ? { import: dlqUrl } : {}
|
|
18371
18414
|
});
|
|
18415
|
+
const ruleArn = `arn:aws:events:${config2.region}:${config2.accountId}:rule/wraps-email-events-to-sqs`;
|
|
18416
|
+
new aws11.sqs.QueuePolicy("wraps-email-events-dlq-policy", {
|
|
18417
|
+
queueUrl: dlq.url,
|
|
18418
|
+
policy: dlq.arn.apply(
|
|
18419
|
+
(dlqArn) => JSON.stringify({
|
|
18420
|
+
Version: "2012-10-17",
|
|
18421
|
+
Statement: [
|
|
18422
|
+
{
|
|
18423
|
+
Effect: "Allow",
|
|
18424
|
+
Principal: {
|
|
18425
|
+
Service: "events.amazonaws.com"
|
|
18426
|
+
},
|
|
18427
|
+
Action: "sqs:SendMessage",
|
|
18428
|
+
Resource: dlqArn,
|
|
18429
|
+
Condition: {
|
|
18430
|
+
ArnEquals: {
|
|
18431
|
+
"aws:SourceArn": ruleArn
|
|
18432
|
+
}
|
|
18433
|
+
}
|
|
18434
|
+
}
|
|
18435
|
+
]
|
|
18436
|
+
})
|
|
18437
|
+
)
|
|
18438
|
+
});
|
|
18372
18439
|
const queueConfig = {
|
|
18373
18440
|
name: queueName,
|
|
18374
18441
|
visibilityTimeoutSeconds: 300,
|
|
@@ -18498,13 +18565,27 @@ async function deployEmailStack(config2) {
|
|
|
18498
18565
|
}
|
|
18499
18566
|
let sqsResources;
|
|
18500
18567
|
if (emailConfig.eventTracking?.enabled) {
|
|
18501
|
-
sqsResources = await createSQSResources({
|
|
18568
|
+
sqsResources = await createSQSResources({
|
|
18569
|
+
region: config2.region,
|
|
18570
|
+
accountId
|
|
18571
|
+
});
|
|
18572
|
+
}
|
|
18573
|
+
let alertingResources;
|
|
18574
|
+
if (emailConfig.alerts?.enabled) {
|
|
18575
|
+
alertingResources = await createAlertingResources({
|
|
18576
|
+
alertConfig: emailConfig.alerts,
|
|
18577
|
+
configSetName: sesResources?.configSet.configurationSetName,
|
|
18578
|
+
dlqName: sqsResources ? "wraps-email-events-dlq" : void 0,
|
|
18579
|
+
region: config2.region
|
|
18580
|
+
});
|
|
18502
18581
|
}
|
|
18503
18582
|
if (emailConfig.eventTracking?.enabled && sesResources && sqsResources) {
|
|
18504
18583
|
await createEventBridgeResources({
|
|
18505
18584
|
eventBusArn: sesResources.eventBus.arn,
|
|
18506
18585
|
queueArn: sqsResources.queue.arn,
|
|
18507
18586
|
queueUrl: sqsResources.queue.url,
|
|
18587
|
+
dlqArn: sqsResources.dlq.arn,
|
|
18588
|
+
alertTopicArn: alertingResources?.topic.arn,
|
|
18508
18589
|
// Include webhook config if provided (for Wraps platform integration)
|
|
18509
18590
|
webhook: config2.webhook,
|
|
18510
18591
|
// Include user webhook config if provided
|
|
@@ -18545,15 +18626,6 @@ async function deployEmailStack(config2) {
|
|
|
18545
18626
|
region: config2.region
|
|
18546
18627
|
});
|
|
18547
18628
|
}
|
|
18548
|
-
let alertingResources;
|
|
18549
|
-
if (emailConfig.alerts?.enabled) {
|
|
18550
|
-
alertingResources = await createAlertingResources({
|
|
18551
|
-
alertConfig: emailConfig.alerts,
|
|
18552
|
-
configSetName: sesResources?.configSet.configurationSetName,
|
|
18553
|
-
dlqName: sqsResources ? "wraps-email-events-dlq" : void 0,
|
|
18554
|
-
region: config2.region
|
|
18555
|
-
});
|
|
18556
|
-
}
|
|
18557
18629
|
let inboundResources;
|
|
18558
18630
|
let replySecretsOutput;
|
|
18559
18631
|
if (emailConfig.inbound?.enabled) {
|
|
@@ -19502,6 +19574,26 @@ async function emailDestroy(options) {
|
|
|
19502
19574
|
const domain = emailConfig?.domain;
|
|
19503
19575
|
const storedStackName = emailService?.pulumiStackName;
|
|
19504
19576
|
if (!(options.force || options.preview)) {
|
|
19577
|
+
const additionalDomainNames = (emailConfig?.additionalDomains ?? []).map((d) => d.domain).filter(Boolean);
|
|
19578
|
+
const domainList = [domain, ...additionalDomainNames].filter(Boolean);
|
|
19579
|
+
const summaryLines = [
|
|
19580
|
+
domainList.length > 0 ? `Domain(s): ${domainList.join(", ")}` : "Domain(s): none configured",
|
|
19581
|
+
"SES configuration sets and event destinations for the above domain(s)",
|
|
19582
|
+
"Event pipeline: wraps-email-events / wraps-email-events-dlq queues, wraps-email-event-processor Lambda, EventBridge rule wraps-email-events-to-sqs",
|
|
19583
|
+
"",
|
|
19584
|
+
pc20.yellow(
|
|
19585
|
+
"Event streaming stops: the Wraps dashboard email timeline and event history for THIS ENTIRE AWS ACCOUNT stop receiving events. Historical events in DynamoDB are destroyed with the table. This cannot be undone."
|
|
19586
|
+
)
|
|
19587
|
+
];
|
|
19588
|
+
if (emailService?.webhookSecret) {
|
|
19589
|
+
summaryLines.push(
|
|
19590
|
+
"",
|
|
19591
|
+
pc20.yellow(
|
|
19592
|
+
"This account is connected to the Wraps platform \u2014 the dashboard will stop receiving events for it."
|
|
19593
|
+
)
|
|
19594
|
+
);
|
|
19595
|
+
}
|
|
19596
|
+
clack18.note(summaryLines.join("\n"), "What will be destroyed");
|
|
19505
19597
|
const confirmed = await clack18.confirm({
|
|
19506
19598
|
message: pc20.red(
|
|
19507
19599
|
"Are you sure you want to destroy all email infrastructure?"
|
|
@@ -19512,6 +19604,19 @@ async function emailDestroy(options) {
|
|
|
19512
19604
|
clack18.cancel("Destruction cancelled.");
|
|
19513
19605
|
process.exit(0);
|
|
19514
19606
|
}
|
|
19607
|
+
if (emailService?.webhookSecret) {
|
|
19608
|
+
const typed = await clack18.text({
|
|
19609
|
+
message: `Type the AWS account id (${identity.accountId}) to confirm destruction of a platform-connected account:`
|
|
19610
|
+
});
|
|
19611
|
+
if (clack18.isCancel(typed) || typed !== identity.accountId) {
|
|
19612
|
+
clack18.cancel("Destruction cancelled \u2014 account id did not match.");
|
|
19613
|
+
process.exit(0);
|
|
19614
|
+
}
|
|
19615
|
+
}
|
|
19616
|
+
} else if (options.force && emailService?.webhookSecret) {
|
|
19617
|
+
clack18.log.warn(
|
|
19618
|
+
`--force skipped the typed account-id confirmation for platform-connected account ${identity.accountId}.`
|
|
19619
|
+
);
|
|
19515
19620
|
}
|
|
19516
19621
|
let shouldCleanDNS = false;
|
|
19517
19622
|
let hostedZone = null;
|
|
@@ -19757,12 +19862,6 @@ Run ${pc20.cyan("wraps email init")} to deploy infrastructure again.
|
|
|
19757
19862
|
// src/commands/email/doctor.ts
|
|
19758
19863
|
init_esm_shims();
|
|
19759
19864
|
init_events();
|
|
19760
|
-
init_aws();
|
|
19761
|
-
init_fs();
|
|
19762
|
-
init_json_output();
|
|
19763
|
-
init_metadata();
|
|
19764
|
-
init_output();
|
|
19765
|
-
init_scanner();
|
|
19766
19865
|
import { DeleteTableCommand, DynamoDBClient as DynamoDBClient2 } from "@aws-sdk/client-dynamodb";
|
|
19767
19866
|
import {
|
|
19768
19867
|
DeleteRoleCommand,
|
|
@@ -19778,6 +19877,458 @@ import { DeleteTopicCommand, SNSClient as SNSClient2 } from "@aws-sdk/client-sns
|
|
|
19778
19877
|
import * as clack19 from "@clack/prompts";
|
|
19779
19878
|
import * as pulumi17 from "@pulumi/pulumi";
|
|
19780
19879
|
import pc21 from "picocolors";
|
|
19880
|
+
|
|
19881
|
+
// src/utils/email/event-pipeline-check.ts
|
|
19882
|
+
init_esm_shims();
|
|
19883
|
+
init_errors();
|
|
19884
|
+
init_config_set_slug();
|
|
19885
|
+
var EVENT_DESTINATION_NAME = "wraps-email-eventbridge";
|
|
19886
|
+
var CONFIG_SET_FALLBACK = "wraps-email-tracking";
|
|
19887
|
+
var RULE_NAME = "wraps-email-events-to-sqs";
|
|
19888
|
+
var QUEUE_NAME = "wraps-email-events";
|
|
19889
|
+
var DLQ_NAME = "wraps-email-events-dlq";
|
|
19890
|
+
var LAMBDA_FUNCTION_NAME = "wraps-email-event-processor";
|
|
19891
|
+
var WEBHOOK_DESTINATION_NAME = "wraps-webhook-destination";
|
|
19892
|
+
var WEBHOOK_CONNECTION_NAME = "wraps-webhook-connection";
|
|
19893
|
+
var HISTORY_TABLE_NAME = "wraps-email-history";
|
|
19894
|
+
var UPGRADE_HINT = "wraps email upgrade";
|
|
19895
|
+
function summarizeError(error) {
|
|
19896
|
+
return error instanceof Error ? error.message : String(error);
|
|
19897
|
+
}
|
|
19898
|
+
function isQueueNotFoundError(error) {
|
|
19899
|
+
return isAWSNotFoundError(error) || error instanceof Error && error.name === "QueueDoesNotExist";
|
|
19900
|
+
}
|
|
19901
|
+
async function checkConfigSet(configSetName, region, optional) {
|
|
19902
|
+
const hop = `SES config set ${configSetName}`;
|
|
19903
|
+
try {
|
|
19904
|
+
const { SESv2Client: SESv2Client10, GetConfigurationSetEventDestinationsCommand: GetConfigurationSetEventDestinationsCommand2 } = await import("@aws-sdk/client-sesv2");
|
|
19905
|
+
const client = new SESv2Client10({ region });
|
|
19906
|
+
const response = await client.send(
|
|
19907
|
+
new GetConfigurationSetEventDestinationsCommand2({
|
|
19908
|
+
ConfigurationSetName: configSetName
|
|
19909
|
+
})
|
|
19910
|
+
);
|
|
19911
|
+
const destination = response.EventDestinations?.find(
|
|
19912
|
+
(d) => d.Name === EVENT_DESTINATION_NAME
|
|
19913
|
+
);
|
|
19914
|
+
if (destination?.Enabled) {
|
|
19915
|
+
return {
|
|
19916
|
+
hop,
|
|
19917
|
+
status: "pass",
|
|
19918
|
+
details: `${EVENT_DESTINATION_NAME} enabled`
|
|
19919
|
+
};
|
|
19920
|
+
}
|
|
19921
|
+
return {
|
|
19922
|
+
hop,
|
|
19923
|
+
status: "fail",
|
|
19924
|
+
details: `SES emits no events for ${configSetName} \u2014 redeploy with \`${UPGRADE_HINT}\``
|
|
19925
|
+
};
|
|
19926
|
+
} catch (error) {
|
|
19927
|
+
if (isAWSNotFoundError(error)) {
|
|
19928
|
+
if (optional) {
|
|
19929
|
+
return null;
|
|
19930
|
+
}
|
|
19931
|
+
return {
|
|
19932
|
+
hop,
|
|
19933
|
+
status: "fail",
|
|
19934
|
+
details: `Configuration set not found \u2014 redeploy with \`${UPGRADE_HINT}\``
|
|
19935
|
+
};
|
|
19936
|
+
}
|
|
19937
|
+
return {
|
|
19938
|
+
hop,
|
|
19939
|
+
status: "warn",
|
|
19940
|
+
details: `Could not check event destinations: ${summarizeError(error)}`
|
|
19941
|
+
};
|
|
19942
|
+
}
|
|
19943
|
+
}
|
|
19944
|
+
async function checkRule(region) {
|
|
19945
|
+
const hop = `EventBridge rule ${RULE_NAME}`;
|
|
19946
|
+
try {
|
|
19947
|
+
const { EventBridgeClient, DescribeRuleCommand } = await import("@aws-sdk/client-eventbridge");
|
|
19948
|
+
const client = new EventBridgeClient({ region });
|
|
19949
|
+
const response = await client.send(
|
|
19950
|
+
new DescribeRuleCommand({ Name: RULE_NAME })
|
|
19951
|
+
);
|
|
19952
|
+
if (response.State === "ENABLED") {
|
|
19953
|
+
return {
|
|
19954
|
+
exists: true,
|
|
19955
|
+
check: { hop, status: "pass", details: "Rule enabled" }
|
|
19956
|
+
};
|
|
19957
|
+
}
|
|
19958
|
+
return {
|
|
19959
|
+
exists: true,
|
|
19960
|
+
check: {
|
|
19961
|
+
hop,
|
|
19962
|
+
status: "fail",
|
|
19963
|
+
details: `Rule is ${response.State ?? "in an unknown state"} \u2014 run \`${UPGRADE_HINT}\` to re-enable`
|
|
19964
|
+
}
|
|
19965
|
+
};
|
|
19966
|
+
} catch (error) {
|
|
19967
|
+
if (isAWSNotFoundError(error)) {
|
|
19968
|
+
return {
|
|
19969
|
+
exists: false,
|
|
19970
|
+
check: {
|
|
19971
|
+
hop,
|
|
19972
|
+
status: "fail",
|
|
19973
|
+
details: `Rule not found \u2014 SES events have nowhere to go; run \`${UPGRADE_HINT}\``
|
|
19974
|
+
}
|
|
19975
|
+
};
|
|
19976
|
+
}
|
|
19977
|
+
return {
|
|
19978
|
+
exists: false,
|
|
19979
|
+
check: {
|
|
19980
|
+
hop,
|
|
19981
|
+
status: "warn",
|
|
19982
|
+
details: `Could not check rule: ${summarizeError(error)}`
|
|
19983
|
+
}
|
|
19984
|
+
};
|
|
19985
|
+
}
|
|
19986
|
+
}
|
|
19987
|
+
async function checkQueueExists(queueName, region, failDetails) {
|
|
19988
|
+
const hop = `SQS queue ${queueName}`;
|
|
19989
|
+
try {
|
|
19990
|
+
const { SQSClient, GetQueueUrlCommand } = await import("@aws-sdk/client-sqs");
|
|
19991
|
+
const client = new SQSClient({ region });
|
|
19992
|
+
await client.send(new GetQueueUrlCommand({ QueueName: queueName }));
|
|
19993
|
+
return { hop, status: "pass", details: "Queue exists" };
|
|
19994
|
+
} catch (error) {
|
|
19995
|
+
if (isQueueNotFoundError(error)) {
|
|
19996
|
+
return { hop, status: "fail", details: failDetails };
|
|
19997
|
+
}
|
|
19998
|
+
return {
|
|
19999
|
+
hop,
|
|
20000
|
+
status: "warn",
|
|
20001
|
+
details: `Could not check queue: ${summarizeError(error)}`
|
|
20002
|
+
};
|
|
20003
|
+
}
|
|
20004
|
+
}
|
|
20005
|
+
function findDuplicateTargetChecks(targets) {
|
|
20006
|
+
const idsByArn = /* @__PURE__ */ new Map();
|
|
20007
|
+
for (const target of targets) {
|
|
20008
|
+
if (!target.Arn) {
|
|
20009
|
+
continue;
|
|
20010
|
+
}
|
|
20011
|
+
const ids = idsByArn.get(target.Arn) ?? [];
|
|
20012
|
+
ids.push(target.Id ?? "unknown");
|
|
20013
|
+
idsByArn.set(target.Arn, ids);
|
|
20014
|
+
}
|
|
20015
|
+
const checks = [];
|
|
20016
|
+
for (const [arn, ids] of idsByArn) {
|
|
20017
|
+
if (ids.length > 1) {
|
|
20018
|
+
checks.push({
|
|
20019
|
+
hop: "EventBridge rule targets",
|
|
20020
|
+
status: "warn",
|
|
20021
|
+
details: `Duplicate targets for ${arn}: ${ids.join(", ")}`
|
|
20022
|
+
});
|
|
20023
|
+
}
|
|
20024
|
+
}
|
|
20025
|
+
return checks;
|
|
20026
|
+
}
|
|
20027
|
+
function checkSqsTargetOnRule(targets, region) {
|
|
20028
|
+
const failDetails = `events are being dropped \u2014 run \`${UPGRADE_HINT}\` to recreate the queue`;
|
|
20029
|
+
const sqsTarget = targets.find((t) => t.Arn?.split(":")[2] === "sqs");
|
|
20030
|
+
if (!sqsTarget) {
|
|
20031
|
+
return Promise.resolve({
|
|
20032
|
+
hop: `SQS queue ${QUEUE_NAME}`,
|
|
20033
|
+
status: "fail",
|
|
20034
|
+
details: `No SQS target on rule \u2014 ${failDetails}`
|
|
20035
|
+
});
|
|
20036
|
+
}
|
|
20037
|
+
return checkQueueExists(QUEUE_NAME, region, failDetails);
|
|
20038
|
+
}
|
|
20039
|
+
function checkPlatformWebhookTargetPresence(webhookTargetPresent, expectPlatformWebhook) {
|
|
20040
|
+
if (expectPlatformWebhook && !webhookTargetPresent) {
|
|
20041
|
+
return {
|
|
20042
|
+
hop: "Platform webhook target",
|
|
20043
|
+
status: "fail",
|
|
20044
|
+
details: `platform webhook target missing but metadata says connected \u2014 dashboard receives no events; run \`${UPGRADE_HINT}\``
|
|
20045
|
+
};
|
|
20046
|
+
}
|
|
20047
|
+
if (!expectPlatformWebhook && webhookTargetPresent) {
|
|
20048
|
+
return {
|
|
20049
|
+
hop: "Platform webhook target",
|
|
20050
|
+
status: "warn",
|
|
20051
|
+
details: "Webhook target exists on the rule but metadata has no webhookSecret \u2014 stack/metadata mismatch, possibly a lost metadata file"
|
|
20052
|
+
};
|
|
20053
|
+
}
|
|
20054
|
+
if (expectPlatformWebhook && webhookTargetPresent) {
|
|
20055
|
+
return {
|
|
20056
|
+
hop: "Platform webhook target",
|
|
20057
|
+
status: "pass",
|
|
20058
|
+
details: "Platform webhook target present"
|
|
20059
|
+
};
|
|
20060
|
+
}
|
|
20061
|
+
return null;
|
|
20062
|
+
}
|
|
20063
|
+
async function checkRuleTargets(region, expectPlatformWebhook) {
|
|
20064
|
+
try {
|
|
20065
|
+
const { EventBridgeClient, ListTargetsByRuleCommand } = await import("@aws-sdk/client-eventbridge");
|
|
20066
|
+
const client = new EventBridgeClient({ region });
|
|
20067
|
+
const response = await client.send(
|
|
20068
|
+
new ListTargetsByRuleCommand({ Rule: RULE_NAME })
|
|
20069
|
+
);
|
|
20070
|
+
const targets = response.Targets ?? [];
|
|
20071
|
+
const checks = [...findDuplicateTargetChecks(targets)];
|
|
20072
|
+
checks.push(await checkSqsTargetOnRule(targets, region));
|
|
20073
|
+
const webhookTargetPresent = targets.some(
|
|
20074
|
+
(t) => t.Arn?.includes(WEBHOOK_DESTINATION_NAME)
|
|
20075
|
+
);
|
|
20076
|
+
const webhookCheck = checkPlatformWebhookTargetPresence(
|
|
20077
|
+
webhookTargetPresent,
|
|
20078
|
+
expectPlatformWebhook
|
|
20079
|
+
);
|
|
20080
|
+
if (webhookCheck) {
|
|
20081
|
+
checks.push(webhookCheck);
|
|
20082
|
+
}
|
|
20083
|
+
return { checks, webhookTargetPresent };
|
|
20084
|
+
} catch (error) {
|
|
20085
|
+
return {
|
|
20086
|
+
checks: [
|
|
20087
|
+
{
|
|
20088
|
+
hop: "EventBridge rule targets",
|
|
20089
|
+
status: "warn",
|
|
20090
|
+
details: `Could not list rule targets: ${summarizeError(error)}`
|
|
20091
|
+
}
|
|
20092
|
+
],
|
|
20093
|
+
webhookTargetPresent: false
|
|
20094
|
+
};
|
|
20095
|
+
}
|
|
20096
|
+
}
|
|
20097
|
+
async function checkWebhookDestination(region) {
|
|
20098
|
+
const hop = `EventBridge API destination ${WEBHOOK_DESTINATION_NAME}`;
|
|
20099
|
+
try {
|
|
20100
|
+
const { EventBridgeClient, DescribeApiDestinationCommand } = await import("@aws-sdk/client-eventbridge");
|
|
20101
|
+
const client = new EventBridgeClient({ region });
|
|
20102
|
+
const response = await client.send(
|
|
20103
|
+
new DescribeApiDestinationCommand({ Name: WEBHOOK_DESTINATION_NAME })
|
|
20104
|
+
);
|
|
20105
|
+
if (response.ApiDestinationState === "ACTIVE") {
|
|
20106
|
+
return { hop, status: "pass", details: "Active" };
|
|
20107
|
+
}
|
|
20108
|
+
return {
|
|
20109
|
+
hop,
|
|
20110
|
+
status: "fail",
|
|
20111
|
+
details: `API destination is ${response.ApiDestinationState ?? "in an unknown state"} \u2014 re-run \`${UPGRADE_HINT}\` to re-authorize`
|
|
20112
|
+
};
|
|
20113
|
+
} catch (error) {
|
|
20114
|
+
if (isAWSNotFoundError(error)) {
|
|
20115
|
+
return {
|
|
20116
|
+
hop,
|
|
20117
|
+
status: "fail",
|
|
20118
|
+
details: `API destination not found \u2014 re-run \`${UPGRADE_HINT}\``
|
|
20119
|
+
};
|
|
20120
|
+
}
|
|
20121
|
+
return {
|
|
20122
|
+
hop,
|
|
20123
|
+
status: "warn",
|
|
20124
|
+
details: `Could not check API destination: ${summarizeError(error)}`
|
|
20125
|
+
};
|
|
20126
|
+
}
|
|
20127
|
+
}
|
|
20128
|
+
async function checkWebhookConnection(region) {
|
|
20129
|
+
const hop = `EventBridge connection ${WEBHOOK_CONNECTION_NAME}`;
|
|
20130
|
+
try {
|
|
20131
|
+
const { EventBridgeClient, DescribeConnectionCommand } = await import("@aws-sdk/client-eventbridge");
|
|
20132
|
+
const client = new EventBridgeClient({ region });
|
|
20133
|
+
const response = await client.send(
|
|
20134
|
+
new DescribeConnectionCommand({ Name: WEBHOOK_CONNECTION_NAME })
|
|
20135
|
+
);
|
|
20136
|
+
if (response.ConnectionState === "AUTHORIZED") {
|
|
20137
|
+
return { hop, status: "pass", details: "Authorized" };
|
|
20138
|
+
}
|
|
20139
|
+
return {
|
|
20140
|
+
hop,
|
|
20141
|
+
status: "fail",
|
|
20142
|
+
details: `Connection is ${response.ConnectionState ?? "in an unknown state"} \u2014 re-run \`${UPGRADE_HINT}\` to re-authorize`
|
|
20143
|
+
};
|
|
20144
|
+
} catch (error) {
|
|
20145
|
+
if (isAWSNotFoundError(error)) {
|
|
20146
|
+
return {
|
|
20147
|
+
hop,
|
|
20148
|
+
status: "fail",
|
|
20149
|
+
details: `Connection not found \u2014 re-run \`${UPGRADE_HINT}\``
|
|
20150
|
+
};
|
|
20151
|
+
}
|
|
20152
|
+
return {
|
|
20153
|
+
hop,
|
|
20154
|
+
status: "warn",
|
|
20155
|
+
details: `Could not check connection: ${summarizeError(error)}`
|
|
20156
|
+
};
|
|
20157
|
+
}
|
|
20158
|
+
}
|
|
20159
|
+
async function checkDLQ(region) {
|
|
20160
|
+
const hop = `SQS DLQ ${DLQ_NAME}`;
|
|
20161
|
+
try {
|
|
20162
|
+
const { SQSClient, GetQueueUrlCommand, GetQueueAttributesCommand } = await import("@aws-sdk/client-sqs");
|
|
20163
|
+
const client = new SQSClient({ region });
|
|
20164
|
+
const urlResponse = await client.send(
|
|
20165
|
+
new GetQueueUrlCommand({ QueueName: DLQ_NAME })
|
|
20166
|
+
);
|
|
20167
|
+
const attrResponse = await client.send(
|
|
20168
|
+
new GetQueueAttributesCommand({
|
|
20169
|
+
QueueUrl: urlResponse.QueueUrl,
|
|
20170
|
+
AttributeNames: ["ApproximateNumberOfMessages"]
|
|
20171
|
+
})
|
|
20172
|
+
);
|
|
20173
|
+
const count = Number(
|
|
20174
|
+
attrResponse.Attributes?.ApproximateNumberOfMessages ?? "0"
|
|
20175
|
+
);
|
|
20176
|
+
if (count > 0) {
|
|
20177
|
+
return {
|
|
20178
|
+
hop,
|
|
20179
|
+
status: "warn",
|
|
20180
|
+
details: `${count} dead-lettered event(s) \u2014 investigate before they age out`
|
|
20181
|
+
};
|
|
20182
|
+
}
|
|
20183
|
+
return { hop, status: "pass", details: "Empty" };
|
|
20184
|
+
} catch (error) {
|
|
20185
|
+
if (isQueueNotFoundError(error)) {
|
|
20186
|
+
return {
|
|
20187
|
+
hop,
|
|
20188
|
+
status: "fail",
|
|
20189
|
+
details: `Dead-letter queue missing \u2014 run \`${UPGRADE_HINT}\` to recreate it`
|
|
20190
|
+
};
|
|
20191
|
+
}
|
|
20192
|
+
return {
|
|
20193
|
+
hop,
|
|
20194
|
+
status: "warn",
|
|
20195
|
+
details: `Could not check DLQ: ${summarizeError(error)}`
|
|
20196
|
+
};
|
|
20197
|
+
}
|
|
20198
|
+
}
|
|
20199
|
+
async function checkEventSourceMapping(region) {
|
|
20200
|
+
const hop = `Lambda event source mapping ${LAMBDA_FUNCTION_NAME}`;
|
|
20201
|
+
try {
|
|
20202
|
+
const { LambdaClient: LambdaClient3, ListEventSourceMappingsCommand } = await import("@aws-sdk/client-lambda");
|
|
20203
|
+
const client = new LambdaClient3({ region });
|
|
20204
|
+
const response = await client.send(
|
|
20205
|
+
new ListEventSourceMappingsCommand({ FunctionName: LAMBDA_FUNCTION_NAME })
|
|
20206
|
+
);
|
|
20207
|
+
const mapping = response.EventSourceMappings?.find(
|
|
20208
|
+
(m) => m.EventSourceArn?.endsWith(`:${QUEUE_NAME}`)
|
|
20209
|
+
);
|
|
20210
|
+
if (!mapping) {
|
|
20211
|
+
return {
|
|
20212
|
+
hop,
|
|
20213
|
+
status: "fail",
|
|
20214
|
+
details: `No event source mapping from ${QUEUE_NAME} \u2014 Lambda never runs; run \`${UPGRADE_HINT}\``
|
|
20215
|
+
};
|
|
20216
|
+
}
|
|
20217
|
+
if (mapping.State === "Enabled") {
|
|
20218
|
+
return { hop, status: "pass", details: "Mapping enabled" };
|
|
20219
|
+
}
|
|
20220
|
+
return {
|
|
20221
|
+
hop,
|
|
20222
|
+
status: "fail",
|
|
20223
|
+
details: `Mapping is ${mapping.State ?? "in an unknown state"} \u2014 run \`${UPGRADE_HINT}\` to re-enable`
|
|
20224
|
+
};
|
|
20225
|
+
} catch (error) {
|
|
20226
|
+
if (isAWSNotFoundError(error)) {
|
|
20227
|
+
return {
|
|
20228
|
+
hop,
|
|
20229
|
+
status: "fail",
|
|
20230
|
+
details: `Lambda function not found \u2014 run \`${UPGRADE_HINT}\``
|
|
20231
|
+
};
|
|
20232
|
+
}
|
|
20233
|
+
return {
|
|
20234
|
+
hop,
|
|
20235
|
+
status: "warn",
|
|
20236
|
+
details: `Could not check event source mapping: ${summarizeError(error)}`
|
|
20237
|
+
};
|
|
20238
|
+
}
|
|
20239
|
+
}
|
|
20240
|
+
async function checkHistoryTable(region) {
|
|
20241
|
+
const hop = `DynamoDB table ${HISTORY_TABLE_NAME}`;
|
|
20242
|
+
try {
|
|
20243
|
+
const { DynamoDBClient: DynamoDBClient7, DescribeTableCommand: DescribeTableCommand2 } = await import("@aws-sdk/client-dynamodb");
|
|
20244
|
+
const client = new DynamoDBClient7({ region });
|
|
20245
|
+
const response = await client.send(
|
|
20246
|
+
new DescribeTableCommand2({ TableName: HISTORY_TABLE_NAME })
|
|
20247
|
+
);
|
|
20248
|
+
const status2 = response.Table?.TableStatus;
|
|
20249
|
+
if (status2 === "ACTIVE") {
|
|
20250
|
+
return { hop, status: "pass", details: "Active" };
|
|
20251
|
+
}
|
|
20252
|
+
return {
|
|
20253
|
+
hop,
|
|
20254
|
+
status: "fail",
|
|
20255
|
+
details: `Table status is ${status2 ?? "unknown"} \u2014 event log may be unavailable; run \`${UPGRADE_HINT}\``
|
|
20256
|
+
};
|
|
20257
|
+
} catch (error) {
|
|
20258
|
+
if (isAWSNotFoundError(error)) {
|
|
20259
|
+
return {
|
|
20260
|
+
hop,
|
|
20261
|
+
status: "fail",
|
|
20262
|
+
details: `Table not found \u2014 event history is not being recorded; run \`${UPGRADE_HINT}\``
|
|
20263
|
+
};
|
|
20264
|
+
}
|
|
20265
|
+
return {
|
|
20266
|
+
hop,
|
|
20267
|
+
status: "warn",
|
|
20268
|
+
details: `Could not check table: ${summarizeError(error)}`
|
|
20269
|
+
};
|
|
20270
|
+
}
|
|
20271
|
+
}
|
|
20272
|
+
async function checkEventPipeline(params) {
|
|
20273
|
+
const { region, domains, expectPlatformWebhook } = params;
|
|
20274
|
+
const checks = [];
|
|
20275
|
+
const uniqueConfigSetNames = Array.from(
|
|
20276
|
+
new Set(domains.map((d) => domainToConfigSetName(d)))
|
|
20277
|
+
);
|
|
20278
|
+
if (uniqueConfigSetNames.length === 0) {
|
|
20279
|
+
const check2 = await checkConfigSet(CONFIG_SET_FALLBACK, region, false);
|
|
20280
|
+
if (check2) {
|
|
20281
|
+
checks.push(check2);
|
|
20282
|
+
}
|
|
20283
|
+
} else {
|
|
20284
|
+
for (const name of uniqueConfigSetNames) {
|
|
20285
|
+
const check2 = await checkConfigSet(name, region, false);
|
|
20286
|
+
if (check2) {
|
|
20287
|
+
checks.push(check2);
|
|
20288
|
+
}
|
|
20289
|
+
}
|
|
20290
|
+
const fallbackCheck = await checkConfigSet(
|
|
20291
|
+
CONFIG_SET_FALLBACK,
|
|
20292
|
+
region,
|
|
20293
|
+
true
|
|
20294
|
+
);
|
|
20295
|
+
if (fallbackCheck) {
|
|
20296
|
+
checks.push(fallbackCheck);
|
|
20297
|
+
}
|
|
20298
|
+
}
|
|
20299
|
+
const ruleResult = await checkRule(region);
|
|
20300
|
+
checks.push(ruleResult.check);
|
|
20301
|
+
let webhookTargetPresent = false;
|
|
20302
|
+
if (ruleResult.exists) {
|
|
20303
|
+
const targetsResult = await checkRuleTargets(region, expectPlatformWebhook);
|
|
20304
|
+
checks.push(...targetsResult.checks);
|
|
20305
|
+
webhookTargetPresent = targetsResult.webhookTargetPresent;
|
|
20306
|
+
}
|
|
20307
|
+
if (webhookTargetPresent) {
|
|
20308
|
+
checks.push(await checkWebhookDestination(region));
|
|
20309
|
+
checks.push(await checkWebhookConnection(region));
|
|
20310
|
+
}
|
|
20311
|
+
checks.push(await checkDLQ(region));
|
|
20312
|
+
checks.push(await checkEventSourceMapping(region));
|
|
20313
|
+
checks.push(await checkHistoryTable(region));
|
|
20314
|
+
return checks;
|
|
20315
|
+
}
|
|
20316
|
+
|
|
20317
|
+
// src/commands/email/doctor.ts
|
|
20318
|
+
init_aws();
|
|
20319
|
+
init_fs();
|
|
20320
|
+
init_json_output();
|
|
20321
|
+
init_metadata();
|
|
20322
|
+
init_output();
|
|
20323
|
+
init_scanner();
|
|
20324
|
+
function pipelineChecksToDoctorResults(pipelineChecks) {
|
|
20325
|
+
return pipelineChecks.map((check2) => ({
|
|
20326
|
+
status: check2.status,
|
|
20327
|
+
category: "Event Pipeline",
|
|
20328
|
+
name: check2.hop,
|
|
20329
|
+
details: check2.details
|
|
20330
|
+
}));
|
|
20331
|
+
}
|
|
19781
20332
|
function runResourceDiagnostics(wrapsResources, hasStack) {
|
|
19782
20333
|
const results = [];
|
|
19783
20334
|
const orphanSuffix = hasStack ? void 0 : " (orphan \u2014 no Pulumi state)";
|
|
@@ -19873,11 +20424,11 @@ async function emailDoctor(options) {
|
|
|
19873
20424
|
async () => validateAWSCredentials()
|
|
19874
20425
|
);
|
|
19875
20426
|
let region = options.region || await getAWSRegion();
|
|
20427
|
+
const emailConnections = await findConnectionsWithService(
|
|
20428
|
+
identity.accountId,
|
|
20429
|
+
"email"
|
|
20430
|
+
);
|
|
19876
20431
|
if (!(options.region || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION)) {
|
|
19877
|
-
const emailConnections = await findConnectionsWithService(
|
|
19878
|
-
identity.accountId,
|
|
19879
|
-
"email"
|
|
19880
|
-
);
|
|
19881
20432
|
if (emailConnections.length === 1) {
|
|
19882
20433
|
region = emailConnections[0].region;
|
|
19883
20434
|
} else if (emailConnections.length > 1 && !isJsonMode()) {
|
|
@@ -19912,8 +20463,23 @@ async function emailDoctor(options) {
|
|
|
19912
20463
|
} catch (_error) {
|
|
19913
20464
|
hasStack = false;
|
|
19914
20465
|
}
|
|
20466
|
+
const emailConnection = emailConnections.find((c) => c.region === region);
|
|
20467
|
+
let pipelineResults = [];
|
|
20468
|
+
if (emailConnection) {
|
|
20469
|
+
const emailService = emailConnection.services.email;
|
|
20470
|
+
const domains = emailService?.config ? getAllTrackedDomains(emailConnection).map((d) => d.domain) : [];
|
|
20471
|
+
const expectPlatformWebhook = Boolean(emailService?.webhookSecret);
|
|
20472
|
+
const pipelineChecks = await progress.execute(
|
|
20473
|
+
"Checking event pipeline",
|
|
20474
|
+
async () => checkEventPipeline({ region, domains, expectPlatformWebhook })
|
|
20475
|
+
);
|
|
20476
|
+
pipelineResults = pipelineChecksToDoctorResults(pipelineChecks);
|
|
20477
|
+
}
|
|
19915
20478
|
progress.stop();
|
|
19916
|
-
const results =
|
|
20479
|
+
const results = [
|
|
20480
|
+
...runResourceDiagnostics(wrapsResources, hasStack),
|
|
20481
|
+
...pipelineResults
|
|
20482
|
+
];
|
|
19917
20483
|
if (isJsonMode()) {
|
|
19918
20484
|
jsonSuccess("email.doctor", {
|
|
19919
20485
|
region,
|
|
@@ -19928,7 +20494,7 @@ async function emailDoctor(options) {
|
|
|
19928
20494
|
});
|
|
19929
20495
|
return;
|
|
19930
20496
|
}
|
|
19931
|
-
if (
|
|
20497
|
+
if (results.length === 0) {
|
|
19932
20498
|
clack19.log.info("No wraps-* resources found in this region.");
|
|
19933
20499
|
clack19.outro(pc21.dim("Your AWS account is clean."));
|
|
19934
20500
|
} else {
|
|
@@ -21915,7 +22481,7 @@ import {
|
|
|
21915
22481
|
UpdateReceiptRuleCommand
|
|
21916
22482
|
} from "@aws-sdk/client-ses";
|
|
21917
22483
|
var RULE_SET_NAME = "wraps-inbound-rules";
|
|
21918
|
-
var
|
|
22484
|
+
var RULE_NAME2 = "wraps-inbound-catch-all";
|
|
21919
22485
|
function createSESClient(region) {
|
|
21920
22486
|
return new SESClient4({ region });
|
|
21921
22487
|
}
|
|
@@ -21940,13 +22506,13 @@ async function createReceiptRule(region, receivingDomain, s3BucketName) {
|
|
|
21940
22506
|
await ses.send(
|
|
21941
22507
|
new DescribeReceiptRuleCommand({
|
|
21942
22508
|
RuleSetName: RULE_SET_NAME,
|
|
21943
|
-
RuleName:
|
|
22509
|
+
RuleName: RULE_NAME2
|
|
21944
22510
|
})
|
|
21945
22511
|
);
|
|
21946
22512
|
await ses.send(
|
|
21947
22513
|
new DeleteReceiptRuleCommand({
|
|
21948
22514
|
RuleSetName: RULE_SET_NAME,
|
|
21949
|
-
RuleName:
|
|
22515
|
+
RuleName: RULE_NAME2
|
|
21950
22516
|
})
|
|
21951
22517
|
);
|
|
21952
22518
|
} catch (error) {
|
|
@@ -21958,7 +22524,7 @@ async function createReceiptRule(region, receivingDomain, s3BucketName) {
|
|
|
21958
22524
|
new CreateReceiptRuleCommand({
|
|
21959
22525
|
RuleSetName: RULE_SET_NAME,
|
|
21960
22526
|
Rule: {
|
|
21961
|
-
Name:
|
|
22527
|
+
Name: RULE_NAME2,
|
|
21962
22528
|
Enabled: true,
|
|
21963
22529
|
ScanEnabled: true,
|
|
21964
22530
|
// Enable spam and virus scanning
|
|
@@ -22006,7 +22572,7 @@ async function deleteReceiptRule(region) {
|
|
|
22006
22572
|
await ses.send(
|
|
22007
22573
|
new DeleteReceiptRuleCommand({
|
|
22008
22574
|
RuleSetName: RULE_SET_NAME,
|
|
22009
|
-
RuleName:
|
|
22575
|
+
RuleName: RULE_NAME2
|
|
22010
22576
|
})
|
|
22011
22577
|
);
|
|
22012
22578
|
} catch (error) {
|
|
@@ -22041,7 +22607,7 @@ async function addDomainToReceiptRule(region, domain, s3BucketName) {
|
|
|
22041
22607
|
const response = await ses.send(
|
|
22042
22608
|
new DescribeReceiptRuleCommand({
|
|
22043
22609
|
RuleSetName: RULE_SET_NAME,
|
|
22044
|
-
RuleName:
|
|
22610
|
+
RuleName: RULE_NAME2
|
|
22045
22611
|
})
|
|
22046
22612
|
);
|
|
22047
22613
|
const existingRecipients = response.Rule?.Recipients ?? [];
|
|
@@ -22052,7 +22618,7 @@ async function addDomainToReceiptRule(region, domain, s3BucketName) {
|
|
|
22052
22618
|
new UpdateReceiptRuleCommand({
|
|
22053
22619
|
RuleSetName: RULE_SET_NAME,
|
|
22054
22620
|
Rule: {
|
|
22055
|
-
Name:
|
|
22621
|
+
Name: RULE_NAME2,
|
|
22056
22622
|
Enabled: response.Rule?.Enabled,
|
|
22057
22623
|
ScanEnabled: response.Rule?.ScanEnabled,
|
|
22058
22624
|
TlsPolicy: response.Rule?.TlsPolicy,
|
|
@@ -22077,7 +22643,7 @@ async function removeDomainFromReceiptRule(region, domain) {
|
|
|
22077
22643
|
const response = await ses.send(
|
|
22078
22644
|
new DescribeReceiptRuleCommand({
|
|
22079
22645
|
RuleSetName: RULE_SET_NAME,
|
|
22080
|
-
RuleName:
|
|
22646
|
+
RuleName: RULE_NAME2
|
|
22081
22647
|
})
|
|
22082
22648
|
);
|
|
22083
22649
|
const existingRecipients = response.Rule?.Recipients ?? [];
|
|
@@ -22090,7 +22656,7 @@ async function removeDomainFromReceiptRule(region, domain) {
|
|
|
22090
22656
|
new UpdateReceiptRuleCommand({
|
|
22091
22657
|
RuleSetName: RULE_SET_NAME,
|
|
22092
22658
|
Rule: {
|
|
22093
|
-
Name:
|
|
22659
|
+
Name: RULE_NAME2,
|
|
22094
22660
|
Enabled: response.Rule?.Enabled,
|
|
22095
22661
|
ScanEnabled: response.Rule?.ScanEnabled,
|
|
22096
22662
|
TlsPolicy: response.Rule?.TlsPolicy,
|
|
@@ -22112,7 +22678,7 @@ async function getReceiptRuleDomains(region) {
|
|
|
22112
22678
|
const response = await ses.send(
|
|
22113
22679
|
new DescribeReceiptRuleCommand({
|
|
22114
22680
|
RuleSetName: RULE_SET_NAME,
|
|
22115
|
-
RuleName:
|
|
22681
|
+
RuleName: RULE_NAME2
|
|
22116
22682
|
})
|
|
22117
22683
|
);
|
|
22118
22684
|
return response.Rule?.Recipients ?? [];
|
|
@@ -26833,7 +27399,7 @@ init_esm_shims();
|
|
|
26833
27399
|
// src/utils/email/template-mustache-case.ts
|
|
26834
27400
|
init_esm_shims();
|
|
26835
27401
|
var HANDLEBARS_BLOCK_HELPERS = /* @__PURE__ */ new Set(["if", "unless", "each", "with"]);
|
|
26836
|
-
function normalizePlainTextMustaches(
|
|
27402
|
+
function normalizePlainTextMustaches(text13, canonicalVars) {
|
|
26837
27403
|
const canonicalByLower = /* @__PURE__ */ new Map();
|
|
26838
27404
|
for (const name of canonicalVars) {
|
|
26839
27405
|
canonicalByLower.set(name.toLowerCase(), name);
|
|
@@ -26841,7 +27407,7 @@ function normalizePlainTextMustaches(text12, canonicalVars) {
|
|
|
26841
27407
|
function restoreIdentifier(name) {
|
|
26842
27408
|
return canonicalByLower.get(name.toLowerCase()) ?? name;
|
|
26843
27409
|
}
|
|
26844
|
-
return
|
|
27410
|
+
return text13.replace(
|
|
26845
27411
|
/\{\{([#/]?)([A-Z][A-Z0-9_]*(?:\.[A-Z0-9_]+)*)((?:\s+[A-Z0-9_.]+)*)\s*\}\}/g,
|
|
26846
27412
|
(match, sigil, name, args) => {
|
|
26847
27413
|
const lower = name.toLowerCase();
|
|
@@ -26891,8 +27457,8 @@ async function renderTemplateWithProxy(Component) {
|
|
|
26891
27457
|
match = regex.exec(html);
|
|
26892
27458
|
}
|
|
26893
27459
|
const canonicalVars = /* @__PURE__ */ new Set([...htmlVarNames, ...accessedProps]);
|
|
26894
|
-
const
|
|
26895
|
-
return { html, text:
|
|
27460
|
+
const text13 = normalizePlainTextMustaches(rawText, canonicalVars);
|
|
27461
|
+
return { html, text: text13, accessedProps };
|
|
26896
27462
|
}
|
|
26897
27463
|
|
|
26898
27464
|
// src/commands/email/templates/push.ts
|
|
@@ -27131,11 +27697,11 @@ async function compileTemplate(filePath, slug, source, sourceHash, wrapsDir) {
|
|
|
27131
27697
|
"Template must have a default export (React component function)"
|
|
27132
27698
|
);
|
|
27133
27699
|
}
|
|
27134
|
-
const { html, text:
|
|
27700
|
+
const { html, text: text13, accessedProps } = await renderTemplateWithProxy(Component);
|
|
27135
27701
|
const variables = mergeVariables(extractVariables(html), accessedProps);
|
|
27136
27702
|
const sesSubject = transformVariablesForSes(subject);
|
|
27137
27703
|
const sesHtml = transformVariablesForSes(html);
|
|
27138
|
-
const sesText = transformVariablesForSes(
|
|
27704
|
+
const sesText = transformVariablesForSes(text13);
|
|
27139
27705
|
const sesTemplateName = slug.replace(/[^a-zA-Z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").substring(0, 64);
|
|
27140
27706
|
return {
|
|
27141
27707
|
slug,
|
|
@@ -29538,6 +30104,28 @@ ${pc37.green("\u2713")} ${pc37.bold("Upgrade complete!")}
|
|
|
29538
30104
|
}
|
|
29539
30105
|
}
|
|
29540
30106
|
await saveConnectionMetadata(metadata);
|
|
30107
|
+
try {
|
|
30108
|
+
const domains = getAllTrackedDomains(metadata).map((d) => d.domain);
|
|
30109
|
+
const pipelineChecks = await checkEventPipeline({
|
|
30110
|
+
region: outputs.region ?? region,
|
|
30111
|
+
domains,
|
|
30112
|
+
expectPlatformWebhook: Boolean(metadata.services.email?.webhookSecret)
|
|
30113
|
+
});
|
|
30114
|
+
const issues = pipelineChecks.filter((check2) => check2.status !== "pass");
|
|
30115
|
+
if (issues.length > 0 && !isJsonMode()) {
|
|
30116
|
+
for (const issue of issues) {
|
|
30117
|
+
clack36.log.warn(
|
|
30118
|
+
`Post-deploy pipeline check: ${issue.hop} \u2014 ${issue.details}`
|
|
30119
|
+
);
|
|
30120
|
+
}
|
|
30121
|
+
}
|
|
30122
|
+
} catch (error) {
|
|
30123
|
+
if (!isJsonMode()) {
|
|
30124
|
+
clack36.log.warn(
|
|
30125
|
+
`Post-deploy pipeline check failed to run: ${error instanceof Error ? error.message : String(error)}`
|
|
30126
|
+
);
|
|
30127
|
+
}
|
|
30128
|
+
}
|
|
29541
30129
|
if (isJsonMode()) {
|
|
29542
30130
|
jsonSuccess("email.upgrade", {
|
|
29543
30131
|
upgraded: true,
|