@wraps.dev/cli 2.19.8 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js
CHANGED
|
@@ -6057,7 +6057,9 @@ function getAllTrackedDomains(metadata) {
|
|
|
6057
6057
|
managed: true,
|
|
6058
6058
|
purpose: d.purpose,
|
|
6059
6059
|
mailFromDomain: d.mailFromDomain,
|
|
6060
|
-
addedAt: d.addedAt
|
|
6060
|
+
addedAt: d.addedAt,
|
|
6061
|
+
configSetName: d.configSetName,
|
|
6062
|
+
trackingConfig: d.trackingConfig
|
|
6061
6063
|
});
|
|
6062
6064
|
}
|
|
6063
6065
|
return result;
|
|
@@ -7255,6 +7257,34 @@ var init_dist = __esm({
|
|
|
7255
7257
|
}
|
|
7256
7258
|
});
|
|
7257
7259
|
|
|
7260
|
+
// src/utils/email/config-set-slug.ts
|
|
7261
|
+
var config_set_slug_exports = {};
|
|
7262
|
+
__export(config_set_slug_exports, {
|
|
7263
|
+
domainToConfigSetName: () => domainToConfigSetName
|
|
7264
|
+
});
|
|
7265
|
+
import { createHash } from "crypto";
|
|
7266
|
+
function domainToConfigSetName(domain) {
|
|
7267
|
+
const slug = domain.toLowerCase().replace(/\./g, "-");
|
|
7268
|
+
const needsHash = domain.includes("-") || slug.length > MAX_SLUG;
|
|
7269
|
+
if (!needsHash) {
|
|
7270
|
+
return `${PREFIX}${slug}`;
|
|
7271
|
+
}
|
|
7272
|
+
const hash = createHash("sha256").update(domain).digest("hex").slice(0, 8);
|
|
7273
|
+
const slugPart = slug.length > MAX_SLUG_WITH_HASH ? slug.slice(0, MAX_SLUG_WITH_HASH) : slug;
|
|
7274
|
+
return `${PREFIX}${slugPart}-${hash}`;
|
|
7275
|
+
}
|
|
7276
|
+
var PREFIX, MAX_LENGTH, MAX_SLUG, MAX_SLUG_WITH_HASH;
|
|
7277
|
+
var init_config_set_slug = __esm({
|
|
7278
|
+
"src/utils/email/config-set-slug.ts"() {
|
|
7279
|
+
"use strict";
|
|
7280
|
+
init_esm_shims();
|
|
7281
|
+
PREFIX = "wraps-email-";
|
|
7282
|
+
MAX_LENGTH = 64;
|
|
7283
|
+
MAX_SLUG = MAX_LENGTH - PREFIX.length;
|
|
7284
|
+
MAX_SLUG_WITH_HASH = MAX_SLUG - 9;
|
|
7285
|
+
}
|
|
7286
|
+
});
|
|
7287
|
+
|
|
7258
7288
|
// src/infrastructure/resources/eventbridge-user-webhook.ts
|
|
7259
7289
|
var eventbridge_user_webhook_exports = {};
|
|
7260
7290
|
__export(eventbridge_user_webhook_exports, {
|
|
@@ -7929,20 +7959,20 @@ async function createMailManagerArchive(config2) {
|
|
|
7929
7959
|
let archiveId;
|
|
7930
7960
|
let archiveArn;
|
|
7931
7961
|
try {
|
|
7932
|
-
const listResult = await mailManagerClient.send(
|
|
7962
|
+
const listResult = await mailManagerClient.send(
|
|
7963
|
+
new ListArchivesCommand({})
|
|
7964
|
+
);
|
|
7933
7965
|
const existing = listResult.Archives?.find(
|
|
7934
7966
|
(a) => a.ArchiveState === ArchiveState.ACTIVE && a.ArchiveName !== void 0 && namePattern.test(a.ArchiveName)
|
|
7935
7967
|
);
|
|
7936
7968
|
if (existing?.ArchiveId) {
|
|
7937
|
-
console.log(`Using existing Mail Manager archive: ${existing.ArchiveName}`);
|
|
7938
7969
|
archiveId = existing.ArchiveId;
|
|
7939
7970
|
const getResult = await mailManagerClient.send(
|
|
7940
7971
|
new GetArchiveCommand({ ArchiveId: archiveId })
|
|
7941
7972
|
);
|
|
7942
7973
|
archiveArn = getResult.ArchiveArn;
|
|
7943
7974
|
}
|
|
7944
|
-
} catch
|
|
7945
|
-
console.log("Error checking for existing archive:", error);
|
|
7975
|
+
} catch {
|
|
7946
7976
|
}
|
|
7947
7977
|
if (!archiveId) {
|
|
7948
7978
|
for (let attempt = 1; attempt <= MAX_NAME_ATTEMPTS; attempt++) {
|
|
@@ -7966,13 +7996,9 @@ async function createMailManagerArchive(config2) {
|
|
|
7966
7996
|
"Failed to create Mail Manager Archive: No ArchiveId returned"
|
|
7967
7997
|
);
|
|
7968
7998
|
}
|
|
7969
|
-
console.log(`Created new Mail Manager archive: ${archiveName}`);
|
|
7970
7999
|
break;
|
|
7971
8000
|
} catch (error) {
|
|
7972
8001
|
if (error instanceof Error && error.name === "ConflictException" && attempt < MAX_NAME_ATTEMPTS) {
|
|
7973
|
-
console.log(
|
|
7974
|
-
`Archive '${archiveName}' is unavailable, trying '${baseArchiveName}-${attempt + 1}'...`
|
|
7975
|
-
);
|
|
7976
8002
|
continue;
|
|
7977
8003
|
}
|
|
7978
8004
|
throw error;
|
|
@@ -9572,7 +9598,7 @@ Run ${pc27.cyan(`wraps email domains verify --domain ${domain} --wait`)} to keep
|
|
|
9572
9598
|
}
|
|
9573
9599
|
}
|
|
9574
9600
|
},
|
|
9575
|
-
ConfigurationSetName:
|
|
9601
|
+
ConfigurationSetName: domainToConfigSetName(domain)
|
|
9576
9602
|
})
|
|
9577
9603
|
);
|
|
9578
9604
|
return response.MessageId;
|
|
@@ -9686,6 +9712,7 @@ var init_test = __esm({
|
|
|
9686
9712
|
init_events();
|
|
9687
9713
|
init_ses_simulator();
|
|
9688
9714
|
init_verification();
|
|
9715
|
+
init_config_set_slug();
|
|
9689
9716
|
init_aws();
|
|
9690
9717
|
init_errors();
|
|
9691
9718
|
init_json_output();
|
|
@@ -17403,6 +17430,7 @@ import pc18 from "picocolors";
|
|
|
17403
17430
|
// src/infrastructure/email-stack.ts
|
|
17404
17431
|
init_esm_shims();
|
|
17405
17432
|
init_dist();
|
|
17433
|
+
init_config_set_slug();
|
|
17406
17434
|
import * as aws19 from "@pulumi/aws";
|
|
17407
17435
|
|
|
17408
17436
|
// src/infrastructure/resources/alerting.ts
|
|
@@ -17963,6 +17991,7 @@ init_lambda();
|
|
|
17963
17991
|
|
|
17964
17992
|
// src/infrastructure/resources/ses.ts
|
|
17965
17993
|
init_esm_shims();
|
|
17994
|
+
init_config_set_slug();
|
|
17966
17995
|
import * as aws9 from "@pulumi/aws";
|
|
17967
17996
|
async function configurationSetExists(configSetName, region) {
|
|
17968
17997
|
try {
|
|
@@ -18014,8 +18043,9 @@ async function emailIdentityExists(emailIdentity, region) {
|
|
|
18014
18043
|
}
|
|
18015
18044
|
}
|
|
18016
18045
|
async function createSESResources(config2) {
|
|
18046
|
+
const configSetName = config2.domain ? domainToConfigSetName(config2.domain) : "wraps-email-tracking";
|
|
18017
18047
|
const configSetOptions = {
|
|
18018
|
-
configurationSetName:
|
|
18048
|
+
configurationSetName: configSetName,
|
|
18019
18049
|
deliveryOptions: config2.tlsRequired ? {
|
|
18020
18050
|
tlsPolicy: "REQUIRE"
|
|
18021
18051
|
// Require TLS 1.2+ for all emails
|
|
@@ -18040,7 +18070,6 @@ async function createSESResources(config2) {
|
|
|
18040
18070
|
httpsPolicy: config2.trackingConfig.httpsEnabled ? "REQUIRE" : "OPTIONAL"
|
|
18041
18071
|
};
|
|
18042
18072
|
}
|
|
18043
|
-
const configSetName = "wraps-email-tracking";
|
|
18044
18073
|
const exists = await configurationSetExists(configSetName, config2.region);
|
|
18045
18074
|
const configSet = exists && !config2.skipResourceImports ? new aws9.sesv2.ConfigurationSet(configSetName, configSetOptions, {
|
|
18046
18075
|
import: configSetName
|
|
@@ -18050,6 +18079,20 @@ async function createSESResources(config2) {
|
|
|
18050
18079
|
});
|
|
18051
18080
|
if (config2.eventTrackingEnabled) {
|
|
18052
18081
|
const eventDestName = "wraps-email-eventbridge";
|
|
18082
|
+
const opensEnabled = config2.trackingConfig?.opens ?? true;
|
|
18083
|
+
const clicksEnabled = config2.trackingConfig?.clicks ?? true;
|
|
18084
|
+
const matchingEventTypes = [
|
|
18085
|
+
"SEND",
|
|
18086
|
+
"DELIVERY",
|
|
18087
|
+
...opensEnabled ? ["OPEN"] : [],
|
|
18088
|
+
...clicksEnabled ? ["CLICK"] : [],
|
|
18089
|
+
"BOUNCE",
|
|
18090
|
+
"COMPLAINT",
|
|
18091
|
+
"REJECT",
|
|
18092
|
+
"RENDERING_FAILURE",
|
|
18093
|
+
"DELIVERY_DELAY",
|
|
18094
|
+
"SUBSCRIPTION"
|
|
18095
|
+
];
|
|
18053
18096
|
new aws9.sesv2.ConfigurationSetEventDestination(
|
|
18054
18097
|
"wraps-email-all-events",
|
|
18055
18098
|
{
|
|
@@ -18057,18 +18100,7 @@ async function createSESResources(config2) {
|
|
|
18057
18100
|
eventDestinationName: eventDestName,
|
|
18058
18101
|
eventDestination: {
|
|
18059
18102
|
enabled: true,
|
|
18060
|
-
matchingEventTypes
|
|
18061
|
-
"SEND",
|
|
18062
|
-
"DELIVERY",
|
|
18063
|
-
"OPEN",
|
|
18064
|
-
"CLICK",
|
|
18065
|
-
"BOUNCE",
|
|
18066
|
-
"COMPLAINT",
|
|
18067
|
-
"REJECT",
|
|
18068
|
-
"RENDERING_FAILURE",
|
|
18069
|
-
"DELIVERY_DELAY",
|
|
18070
|
-
"SUBSCRIPTION"
|
|
18071
|
-
],
|
|
18103
|
+
matchingEventTypes,
|
|
18072
18104
|
eventBridgeDestination: {
|
|
18073
18105
|
// SES requires default bus - cannot use custom bus
|
|
18074
18106
|
eventBusArn: defaultEventBus.arn
|
|
@@ -18078,7 +18110,7 @@ async function createSESResources(config2) {
|
|
|
18078
18110
|
{
|
|
18079
18111
|
// Import existing resource if it already exists in AWS but not in Pulumi state.
|
|
18080
18112
|
// Skip when skipResourceImports is true (resource already tracked in state).
|
|
18081
|
-
import: config2.importExistingEventDestination && !config2.skipResourceImports ?
|
|
18113
|
+
import: config2.importExistingEventDestination && !config2.skipResourceImports ? `${configSetName}|${eventDestName}` : void 0
|
|
18082
18114
|
}
|
|
18083
18115
|
);
|
|
18084
18116
|
}
|
|
@@ -18349,7 +18381,7 @@ async function deployEmailStack(config2) {
|
|
|
18349
18381
|
let sesResources;
|
|
18350
18382
|
if (emailConfig.tracking?.enabled || emailConfig.eventTracking?.enabled) {
|
|
18351
18383
|
const shouldImportEventDest = !config2.skipResourceImports && emailConfig.eventTracking?.enabled && await eventDestinationExists(
|
|
18352
|
-
"
|
|
18384
|
+
domainToConfigSetName(emailConfig.domain ?? ""),
|
|
18353
18385
|
"wraps-email-eventbridge",
|
|
18354
18386
|
config2.region
|
|
18355
18387
|
);
|
|
@@ -18433,7 +18465,7 @@ async function deployEmailStack(config2) {
|
|
|
18433
18465
|
let smtpResources;
|
|
18434
18466
|
if (emailConfig.smtpCredentials?.enabled && sesResources) {
|
|
18435
18467
|
smtpResources = await createSMTPCredentials({
|
|
18436
|
-
configSetName: "
|
|
18468
|
+
configSetName: domainToConfigSetName(emailConfig.domain ?? ""),
|
|
18437
18469
|
region: config2.region
|
|
18438
18470
|
});
|
|
18439
18471
|
}
|
|
@@ -19222,17 +19254,18 @@ ${pc19.dim("\u2500\u2500\u2500 end Pulumi output \u2500\u2500\u2500")}
|
|
|
19222
19254
|
tableName: outputs.tableName
|
|
19223
19255
|
});
|
|
19224
19256
|
if (selectedIdentities.length > 0 && emailConfig.tracking?.enabled) {
|
|
19257
|
+
const displayConfigSetName = outputs.configSetName ?? "wraps-email-<your-domain>";
|
|
19225
19258
|
console.log(`
|
|
19226
19259
|
${pc19.bold("Next Steps:")}
|
|
19227
19260
|
`);
|
|
19228
19261
|
console.log(
|
|
19229
|
-
`Update your code to use configuration set: ${pc19.cyan(
|
|
19262
|
+
`Update your code to use configuration set: ${pc19.cyan(displayConfigSetName)}`
|
|
19230
19263
|
);
|
|
19231
19264
|
console.log(`
|
|
19232
19265
|
${pc19.dim("Example:")}`);
|
|
19233
19266
|
console.log(
|
|
19234
19267
|
pc19.gray(` await ses.sendEmail({
|
|
19235
|
-
ConfigurationSetName: '
|
|
19268
|
+
ConfigurationSetName: '${displayConfigSetName}',
|
|
19236
19269
|
// ... other parameters
|
|
19237
19270
|
});`)
|
|
19238
19271
|
);
|
|
@@ -19550,6 +19583,24 @@ async function emailDestroy(options) {
|
|
|
19550
19583
|
"Some resources may not have been fully removed. You can re-run this command or clean up manually in the AWS console."
|
|
19551
19584
|
);
|
|
19552
19585
|
}
|
|
19586
|
+
if (destroyFailed) {
|
|
19587
|
+
try {
|
|
19588
|
+
const { SQSClient, GetQueueUrlCommand, DeleteQueueCommand } = await import("@aws-sdk/client-sqs");
|
|
19589
|
+
const sqsClient = new SQSClient({ region });
|
|
19590
|
+
for (const queueName of ["wraps-email-events", "wraps-email-events-dlq"]) {
|
|
19591
|
+
try {
|
|
19592
|
+
const { QueueUrl } = await sqsClient.send(
|
|
19593
|
+
new GetQueueUrlCommand({ QueueName: queueName })
|
|
19594
|
+
);
|
|
19595
|
+
if (QueueUrl) {
|
|
19596
|
+
await sqsClient.send(new DeleteQueueCommand({ QueueUrl }));
|
|
19597
|
+
}
|
|
19598
|
+
} catch {
|
|
19599
|
+
}
|
|
19600
|
+
}
|
|
19601
|
+
} catch {
|
|
19602
|
+
}
|
|
19603
|
+
}
|
|
19553
19604
|
await deleteConnectionMetadata(identity.accountId, region);
|
|
19554
19605
|
progress.stop();
|
|
19555
19606
|
if (isJsonMode()) {
|
|
@@ -19923,8 +19974,14 @@ init_json_output();
|
|
|
19923
19974
|
init_metadata();
|
|
19924
19975
|
init_output();
|
|
19925
19976
|
init_prompts();
|
|
19977
|
+
init_config_set_slug();
|
|
19926
19978
|
import { Resolver as Resolver2 } from "dns/promises";
|
|
19927
|
-
import {
|
|
19979
|
+
import {
|
|
19980
|
+
CreateConfigurationSetCommand,
|
|
19981
|
+
CreateConfigurationSetEventDestinationCommand,
|
|
19982
|
+
GetEmailIdentityCommand as GetEmailIdentityCommand2,
|
|
19983
|
+
SESv2Client as SESv2Client4
|
|
19984
|
+
} from "@aws-sdk/client-sesv2";
|
|
19928
19985
|
import * as clack21 from "@clack/prompts";
|
|
19929
19986
|
import pc23 from "picocolors";
|
|
19930
19987
|
async function checkVerification(domain, sesClient, region) {
|
|
@@ -20382,13 +20439,57 @@ Run ${pc23.cyan("wraps email init")} first to deploy email infrastructure.
|
|
|
20382
20439
|
if (!options.yes) {
|
|
20383
20440
|
purpose = await promptDomainPurpose();
|
|
20384
20441
|
}
|
|
20442
|
+
const configSetName = domainToConfigSetName(domain);
|
|
20443
|
+
const trackingConfig = {
|
|
20444
|
+
opens: purpose === "marketing" || purpose === "notifications",
|
|
20445
|
+
clicks: purpose === "marketing" || purpose === "notifications"
|
|
20446
|
+
};
|
|
20447
|
+
const baseEventTypes = [
|
|
20448
|
+
"SEND",
|
|
20449
|
+
"DELIVERY",
|
|
20450
|
+
"BOUNCE",
|
|
20451
|
+
"COMPLAINT",
|
|
20452
|
+
"REJECT",
|
|
20453
|
+
"RENDERING_FAILURE",
|
|
20454
|
+
"DELIVERY_DELAY",
|
|
20455
|
+
"SUBSCRIPTION"
|
|
20456
|
+
];
|
|
20457
|
+
const matchingEventTypes = trackingConfig.opens ? [...baseEventTypes, "OPEN", "CLICK"] : [...baseEventTypes];
|
|
20458
|
+
const eventBusArn = `arn:aws:events:${region}:${identity.accountId}:event-bus/default`;
|
|
20459
|
+
await progress.execute("Creating tracking configuration", async () => {
|
|
20460
|
+
try {
|
|
20461
|
+
await sesClient.send(
|
|
20462
|
+
new CreateConfigurationSetCommand({
|
|
20463
|
+
ConfigurationSetName: configSetName,
|
|
20464
|
+
SuppressionOptions: { SuppressedReasons: ["BOUNCE", "COMPLAINT"] }
|
|
20465
|
+
})
|
|
20466
|
+
);
|
|
20467
|
+
} catch (err) {
|
|
20468
|
+
if (err.name !== "AlreadyExistsException") throw err;
|
|
20469
|
+
}
|
|
20470
|
+
try {
|
|
20471
|
+
await sesClient.send(
|
|
20472
|
+
new CreateConfigurationSetEventDestinationCommand({
|
|
20473
|
+
ConfigurationSetName: configSetName,
|
|
20474
|
+
EventDestinationName: "wraps-email-eventbridge",
|
|
20475
|
+
EventDestination: {
|
|
20476
|
+
Enabled: true,
|
|
20477
|
+
MatchingEventTypes: matchingEventTypes,
|
|
20478
|
+
EventBridgeDestination: { EventBusArn: eventBusArn }
|
|
20479
|
+
}
|
|
20480
|
+
})
|
|
20481
|
+
);
|
|
20482
|
+
} catch (err) {
|
|
20483
|
+
if (err.name !== "AlreadyExistsException") throw err;
|
|
20484
|
+
}
|
|
20485
|
+
});
|
|
20385
20486
|
if (domainAlreadyExists) {
|
|
20386
20487
|
const { PutEmailIdentityConfigurationSetAttributesCommand } = await import("@aws-sdk/client-sesv2");
|
|
20387
20488
|
await progress.execute("Associating tracking configuration", async () => {
|
|
20388
20489
|
await sesClient.send(
|
|
20389
20490
|
new PutEmailIdentityConfigurationSetAttributesCommand({
|
|
20390
20491
|
EmailIdentity: domain,
|
|
20391
|
-
ConfigurationSetName:
|
|
20492
|
+
ConfigurationSetName: configSetName
|
|
20392
20493
|
})
|
|
20393
20494
|
);
|
|
20394
20495
|
});
|
|
@@ -20398,7 +20499,7 @@ Run ${pc23.cyan("wraps email init")} first to deploy email infrastructure.
|
|
|
20398
20499
|
await sesClient.send(
|
|
20399
20500
|
new CreateEmailIdentityCommand({
|
|
20400
20501
|
EmailIdentity: domain,
|
|
20401
|
-
ConfigurationSetName:
|
|
20502
|
+
ConfigurationSetName: configSetName,
|
|
20402
20503
|
DkimSigningAttributes: {
|
|
20403
20504
|
NextSigningKeyLength: "RSA_2048_BIT"
|
|
20404
20505
|
}
|
|
@@ -20511,6 +20612,8 @@ Run ${pc23.cyan("wraps email init")} first to deploy email infrastructure.
|
|
|
20511
20612
|
domain,
|
|
20512
20613
|
mailFromDomain,
|
|
20513
20614
|
purpose,
|
|
20615
|
+
configSetName,
|
|
20616
|
+
trackingConfig,
|
|
20514
20617
|
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
20515
20618
|
};
|
|
20516
20619
|
addDomainToMetadata(metadata, entry);
|
|
@@ -21424,7 +21527,7 @@ Deploy first: ${pc24.cyan("wraps email inbound init")}
|
|
|
21424
21527
|
}
|
|
21425
21528
|
const emailService = metadata.services.email;
|
|
21426
21529
|
const inboundConfig = emailService.config.inbound;
|
|
21427
|
-
if (!options.force) {
|
|
21530
|
+
if (!(options.force || options.preview)) {
|
|
21428
21531
|
clack22.log.warn(
|
|
21429
21532
|
`This will remove inbound email for ${pc24.cyan(inboundConfig.receivingDomain || "")}`
|
|
21430
21533
|
);
|
|
@@ -21437,17 +21540,6 @@ Deploy first: ${pc24.cyan("wraps email inbound init")}
|
|
|
21437
21540
|
process.exit(0);
|
|
21438
21541
|
}
|
|
21439
21542
|
}
|
|
21440
|
-
await progress.execute("Removing SES receipt rules", async () => {
|
|
21441
|
-
await deleteReceiptRule(region);
|
|
21442
|
-
await deleteReceiptRuleSet(region);
|
|
21443
|
-
});
|
|
21444
|
-
await progress.execute(
|
|
21445
|
-
"Preparing workspace",
|
|
21446
|
-
async () => ensurePulumiWorkDir({
|
|
21447
|
-
accountId: identity.accountId,
|
|
21448
|
-
region
|
|
21449
|
-
})
|
|
21450
|
-
);
|
|
21451
21543
|
const pulumiWorkDir = getPulumiWorkDir();
|
|
21452
21544
|
const stackName = emailService.pulumiStackName || `wraps-${identity.accountId}-${region}`;
|
|
21453
21545
|
const updatedEmailConfig = {
|
|
@@ -21458,7 +21550,8 @@ Deploy first: ${pc24.cyan("wraps email inbound init")}
|
|
|
21458
21550
|
const stackConfig = buildEmailStackConfig(metadata, region, {
|
|
21459
21551
|
emailConfig: updatedEmailConfig
|
|
21460
21552
|
});
|
|
21461
|
-
|
|
21553
|
+
const createStack = async () => {
|
|
21554
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
21462
21555
|
const stack = await pulumi18.automation.LocalWorkspace.createOrSelectStack(
|
|
21463
21556
|
{
|
|
21464
21557
|
stackName,
|
|
@@ -21473,6 +21566,39 @@ Deploy first: ${pc24.cyan("wraps email inbound init")}
|
|
|
21473
21566
|
}
|
|
21474
21567
|
);
|
|
21475
21568
|
await stack.setConfig("aws:region", { value: region });
|
|
21569
|
+
return stack;
|
|
21570
|
+
};
|
|
21571
|
+
if (options.preview) {
|
|
21572
|
+
const previewResult = await progress.execute(
|
|
21573
|
+
"Generating infrastructure preview",
|
|
21574
|
+
async () => {
|
|
21575
|
+
const stack = await createStack();
|
|
21576
|
+
return previewWithResourceChanges(stack, { diff: true });
|
|
21577
|
+
}
|
|
21578
|
+
);
|
|
21579
|
+
displayPreview({
|
|
21580
|
+
changeSummary: previewResult.changeSummary,
|
|
21581
|
+
resourceChanges: previewResult.resourceChanges,
|
|
21582
|
+
commandName: "wraps email inbound destroy"
|
|
21583
|
+
});
|
|
21584
|
+
clack22.outro(
|
|
21585
|
+
pc24.green("Preview complete. Run without --preview to destroy.")
|
|
21586
|
+
);
|
|
21587
|
+
return;
|
|
21588
|
+
}
|
|
21589
|
+
await progress.execute("Removing SES receipt rules", async () => {
|
|
21590
|
+
await deleteReceiptRule(region);
|
|
21591
|
+
await deleteReceiptRuleSet(region);
|
|
21592
|
+
});
|
|
21593
|
+
await progress.execute(
|
|
21594
|
+
"Preparing workspace",
|
|
21595
|
+
async () => ensurePulumiWorkDir({
|
|
21596
|
+
accountId: identity.accountId,
|
|
21597
|
+
region
|
|
21598
|
+
})
|
|
21599
|
+
);
|
|
21600
|
+
await progress.execute("Removing inbound infrastructure", async () => {
|
|
21601
|
+
const stack = await createStack();
|
|
21476
21602
|
await withLockRetry(
|
|
21477
21603
|
() => withTimeout(
|
|
21478
21604
|
stack.up({ onOutput: () => {
|
|
@@ -22502,6 +22628,36 @@ async function init2(options) {
|
|
|
22502
22628
|
emailConfig.mailFromSubdomain = mailFromFull.endsWith(suffix) ? mailFromFull.slice(0, -suffix.length) || "mail" : "mail";
|
|
22503
22629
|
}
|
|
22504
22630
|
}
|
|
22631
|
+
if (!options.quick && preset !== "custom" && emailConfig.tracking?.enabled) {
|
|
22632
|
+
const purpose = await promptDomainPurpose();
|
|
22633
|
+
if (purpose === "transactional") {
|
|
22634
|
+
emailConfig.tracking = { ...emailConfig.tracking, opens: false, clicks: false };
|
|
22635
|
+
} else if (purpose === "marketing" || purpose === "notifications") {
|
|
22636
|
+
emailConfig.tracking = { ...emailConfig.tracking, opens: true, clicks: true };
|
|
22637
|
+
} else {
|
|
22638
|
+
const trackOpens = await clack26.confirm({
|
|
22639
|
+
message: "Track email opens?",
|
|
22640
|
+
initialValue: emailConfig.tracking.opens ?? true
|
|
22641
|
+
});
|
|
22642
|
+
if (clack26.isCancel(trackOpens)) {
|
|
22643
|
+
clack26.cancel("Operation cancelled.");
|
|
22644
|
+
process.exit(0);
|
|
22645
|
+
}
|
|
22646
|
+
const trackClicks = await clack26.confirm({
|
|
22647
|
+
message: "Track link clicks?",
|
|
22648
|
+
initialValue: emailConfig.tracking.clicks ?? true
|
|
22649
|
+
});
|
|
22650
|
+
if (clack26.isCancel(trackClicks)) {
|
|
22651
|
+
clack26.cancel("Operation cancelled.");
|
|
22652
|
+
process.exit(0);
|
|
22653
|
+
}
|
|
22654
|
+
emailConfig.tracking = {
|
|
22655
|
+
...emailConfig.tracking,
|
|
22656
|
+
opens: trackOpens,
|
|
22657
|
+
clicks: trackClicks
|
|
22658
|
+
};
|
|
22659
|
+
}
|
|
22660
|
+
}
|
|
22505
22661
|
let costSummary;
|
|
22506
22662
|
if (!options.quick) {
|
|
22507
22663
|
const estimatedVolume = await promptEstimatedVolume();
|
|
@@ -23372,6 +23528,59 @@ async function replyInit(options) {
|
|
|
23372
23528
|
);
|
|
23373
23529
|
}
|
|
23374
23530
|
const stackName = emailService.pulumiStackName || `wraps-${identity.accountId}-${region}`;
|
|
23531
|
+
if (options.preview) {
|
|
23532
|
+
const previewResult = await progress.execute(
|
|
23533
|
+
"Generating infrastructure preview",
|
|
23534
|
+
async () => {
|
|
23535
|
+
const previewMetadata = JSON.parse(
|
|
23536
|
+
JSON.stringify(metadata)
|
|
23537
|
+
);
|
|
23538
|
+
const previewEmailConfig = previewMetadata.services.email.config;
|
|
23539
|
+
const rt = previewEmailConfig.replyThreading ?? {
|
|
23540
|
+
enabled: false,
|
|
23541
|
+
domains: []
|
|
23542
|
+
};
|
|
23543
|
+
for (const domain of targetDomains) {
|
|
23544
|
+
const filtered = rt.domains.filter((d) => d.domain !== domain);
|
|
23545
|
+
filtered.push({
|
|
23546
|
+
domain,
|
|
23547
|
+
initialSecret: randomBytes5(32).toString("base64"),
|
|
23548
|
+
currentKid: 1,
|
|
23549
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
23550
|
+
});
|
|
23551
|
+
rt.domains = filtered;
|
|
23552
|
+
}
|
|
23553
|
+
previewEmailConfig.replyThreading = {
|
|
23554
|
+
enabled: true,
|
|
23555
|
+
domains: rt.domains
|
|
23556
|
+
};
|
|
23557
|
+
const stackConfig = buildEmailStackConfig(previewMetadata, region);
|
|
23558
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
23559
|
+
const stack = await pulumi20.automation.LocalWorkspace.createOrSelectStack(
|
|
23560
|
+
{
|
|
23561
|
+
stackName,
|
|
23562
|
+
projectName: "wraps-email",
|
|
23563
|
+
program: async () => {
|
|
23564
|
+
const result = await deployEmailStack(stackConfig);
|
|
23565
|
+
return result;
|
|
23566
|
+
}
|
|
23567
|
+
},
|
|
23568
|
+
{
|
|
23569
|
+
workDir: getPulumiWorkDir()
|
|
23570
|
+
}
|
|
23571
|
+
);
|
|
23572
|
+
await stack.setConfig("aws:region", { value: region });
|
|
23573
|
+
return previewWithResourceChanges(stack, { diff: true });
|
|
23574
|
+
}
|
|
23575
|
+
);
|
|
23576
|
+
displayPreview({
|
|
23577
|
+
changeSummary: previewResult.changeSummary,
|
|
23578
|
+
resourceChanges: previewResult.resourceChanges,
|
|
23579
|
+
commandName: "wraps email reply init"
|
|
23580
|
+
});
|
|
23581
|
+
clack27.outro(pc29.green("Preview complete. Run without --preview to deploy."));
|
|
23582
|
+
return;
|
|
23583
|
+
}
|
|
23375
23584
|
const results = [];
|
|
23376
23585
|
for (const domain of targetDomains) {
|
|
23377
23586
|
const fresh = await loadConnectionMetadata(identity.accountId, region);
|
|
@@ -23713,7 +23922,7 @@ async function replyDestroy(options) {
|
|
|
23713
23922
|
"https://wraps.dev/docs/guides/reply-threading"
|
|
23714
23923
|
);
|
|
23715
23924
|
}
|
|
23716
|
-
if (!(options.force || isJsonMode())) {
|
|
23925
|
+
if (!(options.force || options.preview || isJsonMode())) {
|
|
23717
23926
|
const confirmed = await clack27.confirm({
|
|
23718
23927
|
message: `Remove reply threading for ${targets.join(", ")}?`,
|
|
23719
23928
|
initialValue: false
|
|
@@ -23723,10 +23932,55 @@ async function replyDestroy(options) {
|
|
|
23723
23932
|
return;
|
|
23724
23933
|
}
|
|
23725
23934
|
}
|
|
23935
|
+
const emailService = metadata.services.email;
|
|
23936
|
+
if (options.preview) {
|
|
23937
|
+
if (emailService) {
|
|
23938
|
+
const previewMetadata = JSON.parse(
|
|
23939
|
+
JSON.stringify(metadata)
|
|
23940
|
+
);
|
|
23941
|
+
for (const domain of targets) {
|
|
23942
|
+
stripDomainFromReplyThreadingMetadata({
|
|
23943
|
+
domain,
|
|
23944
|
+
metadata: previewMetadata
|
|
23945
|
+
});
|
|
23946
|
+
}
|
|
23947
|
+
const stackName = emailService.pulumiStackName || `wraps-${identity.accountId}-${region}`;
|
|
23948
|
+
const stackConfig = buildEmailStackConfig(previewMetadata, region);
|
|
23949
|
+
const previewResult = await progress.execute(
|
|
23950
|
+
"Generating infrastructure preview",
|
|
23951
|
+
async () => {
|
|
23952
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
23953
|
+
const stack = await pulumi20.automation.LocalWorkspace.createOrSelectStack(
|
|
23954
|
+
{
|
|
23955
|
+
stackName,
|
|
23956
|
+
projectName: "wraps-email",
|
|
23957
|
+
program: async () => {
|
|
23958
|
+
const result = await deployEmailStack(stackConfig);
|
|
23959
|
+
return result;
|
|
23960
|
+
}
|
|
23961
|
+
},
|
|
23962
|
+
{
|
|
23963
|
+
workDir: getPulumiWorkDir()
|
|
23964
|
+
}
|
|
23965
|
+
);
|
|
23966
|
+
await stack.setConfig("aws:region", { value: region });
|
|
23967
|
+
return previewWithResourceChanges(stack, { diff: true });
|
|
23968
|
+
}
|
|
23969
|
+
);
|
|
23970
|
+
displayPreview({
|
|
23971
|
+
changeSummary: previewResult.changeSummary,
|
|
23972
|
+
resourceChanges: previewResult.resourceChanges,
|
|
23973
|
+
commandName: "wraps email reply destroy"
|
|
23974
|
+
});
|
|
23975
|
+
}
|
|
23976
|
+
clack27.outro(
|
|
23977
|
+
pc29.green("Preview complete. Run without --preview to destroy.")
|
|
23978
|
+
);
|
|
23979
|
+
return;
|
|
23980
|
+
}
|
|
23726
23981
|
for (const domain of targets) {
|
|
23727
23982
|
stripDomainFromReplyThreadingMetadata({ domain, metadata });
|
|
23728
23983
|
}
|
|
23729
|
-
const emailService = metadata.services.email;
|
|
23730
23984
|
if (emailService) {
|
|
23731
23985
|
const stackName = emailService.pulumiStackName || `wraps-${identity.accountId}-${region}`;
|
|
23732
23986
|
const stackConfig = buildEmailStackConfig(metadata, region);
|
|
@@ -23819,6 +24073,7 @@ async function replyDecode(addressInput, options) {
|
|
|
23819
24073
|
// src/commands/email/restore.ts
|
|
23820
24074
|
init_esm_shims();
|
|
23821
24075
|
init_events();
|
|
24076
|
+
init_config_set_slug();
|
|
23822
24077
|
init_aws();
|
|
23823
24078
|
init_errors();
|
|
23824
24079
|
init_fs();
|
|
@@ -23882,7 +24137,10 @@ ${pc30.bold("The following Wraps resources will be removed:")}
|
|
|
23882
24137
|
`
|
|
23883
24138
|
);
|
|
23884
24139
|
if (metadata.services.email?.config.tracking?.enabled) {
|
|
23885
|
-
|
|
24140
|
+
const configSetName = domainToConfigSetName(
|
|
24141
|
+
metadata.services.email.config.domain ?? ""
|
|
24142
|
+
);
|
|
24143
|
+
console.log(` ${pc30.cyan("\u2713")} Configuration Set (${configSetName})`);
|
|
23886
24144
|
}
|
|
23887
24145
|
if (metadata.services.email?.config.eventTracking?.dynamoDBHistory) {
|
|
23888
24146
|
console.log(` ${pc30.cyan("\u2713")} DynamoDB Table (wraps-email-history)`);
|
|
@@ -25440,7 +25698,7 @@ function renderErrorPage(err) {
|
|
|
25440
25698
|
// src/commands/email/templates/push.ts
|
|
25441
25699
|
init_esm_shims();
|
|
25442
25700
|
init_events();
|
|
25443
|
-
import { createHash } from "crypto";
|
|
25701
|
+
import { createHash as createHash2 } from "crypto";
|
|
25444
25702
|
import { existsSync as existsSync13 } from "fs";
|
|
25445
25703
|
import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile8 } from "fs/promises";
|
|
25446
25704
|
import { join as join15 } from "path";
|
|
@@ -26077,7 +26335,7 @@ async function pushToAPI(templates, token, progress, force) {
|
|
|
26077
26335
|
return results;
|
|
26078
26336
|
}
|
|
26079
26337
|
function sha256(content) {
|
|
26080
|
-
return
|
|
26338
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
26081
26339
|
}
|
|
26082
26340
|
|
|
26083
26341
|
// src/cli.ts
|
|
@@ -26301,6 +26559,11 @@ ${pc35.bold("Current Configuration:")}
|
|
|
26301
26559
|
value: "hosting-provider",
|
|
26302
26560
|
label: "Change hosting provider",
|
|
26303
26561
|
hint: metadata.provider === "vercel" ? `Currently: Vercel (${metadata.vercel?.teamSlug || "configured"})` : `Currently: ${metadata.provider} \u2192 Switch to Vercel OIDC, etc.`
|
|
26562
|
+
},
|
|
26563
|
+
{
|
|
26564
|
+
value: "per-domain-config-sets",
|
|
26565
|
+
label: "Per-domain configuration sets",
|
|
26566
|
+
hint: "Create dedicated SES config sets for each additional domain"
|
|
26304
26567
|
}
|
|
26305
26568
|
);
|
|
26306
26569
|
if (options.action) {
|
|
@@ -27433,6 +27696,131 @@ ${pc35.bold("SMTP Credentials for Legacy Systems")}
|
|
|
27433
27696
|
}
|
|
27434
27697
|
break;
|
|
27435
27698
|
}
|
|
27699
|
+
case "per-domain-config-sets": {
|
|
27700
|
+
const {
|
|
27701
|
+
SESv2Client: SESv2Client9,
|
|
27702
|
+
CreateConfigurationSetCommand: CreateConfigurationSetCommand2,
|
|
27703
|
+
CreateConfigurationSetEventDestinationCommand: CreateConfigurationSetEventDestinationCommand2,
|
|
27704
|
+
PutEmailIdentityConfigurationSetAttributesCommand,
|
|
27705
|
+
EventType
|
|
27706
|
+
} = await import("@aws-sdk/client-sesv2");
|
|
27707
|
+
const { domainToConfigSetName: domainToConfigSetName2 } = await Promise.resolve().then(() => (init_config_set_slug(), config_set_slug_exports));
|
|
27708
|
+
const sesClient = new SESv2Client9({ region });
|
|
27709
|
+
const accountId = identity.accountId;
|
|
27710
|
+
const additionalDomains = metadata.services.email?.config.additionalDomains ?? [];
|
|
27711
|
+
const unmigratedDomains = additionalDomains.filter(
|
|
27712
|
+
(d) => !d.configSetName
|
|
27713
|
+
);
|
|
27714
|
+
if (unmigratedDomains.length === 0) {
|
|
27715
|
+
clack34.log.info(
|
|
27716
|
+
"All additional domains are already migrated to per-domain config sets."
|
|
27717
|
+
);
|
|
27718
|
+
clack34.outro(pc35.green("\u2713 Nothing to migrate"));
|
|
27719
|
+
trackServiceUpgrade("email", {
|
|
27720
|
+
action: "per-domain-config-sets",
|
|
27721
|
+
duration_ms: Date.now() - startTime
|
|
27722
|
+
});
|
|
27723
|
+
return;
|
|
27724
|
+
}
|
|
27725
|
+
clack34.log.info(
|
|
27726
|
+
`Migrating ${unmigratedDomains.length} domain(s) to per-domain configuration sets...`
|
|
27727
|
+
);
|
|
27728
|
+
const eventBusArn = `arn:aws:events:${region}:${accountId}:event-bus/default`;
|
|
27729
|
+
for (let i = 0; i < unmigratedDomains.length; i++) {
|
|
27730
|
+
const d = unmigratedDomains[i];
|
|
27731
|
+
const configSetName = domainToConfigSetName2(d.domain);
|
|
27732
|
+
clack34.log.step(
|
|
27733
|
+
`Migrating ${pc35.cyan(d.domain)} \u2192 ${pc35.dim(configSetName)}`
|
|
27734
|
+
);
|
|
27735
|
+
await progress.execute(
|
|
27736
|
+
`Creating config set for ${d.domain}`,
|
|
27737
|
+
async () => {
|
|
27738
|
+
await sesClient.send(
|
|
27739
|
+
new CreateConfigurationSetCommand2({
|
|
27740
|
+
ConfigurationSetName: configSetName,
|
|
27741
|
+
SuppressionOptions: {
|
|
27742
|
+
SuppressedReasons: ["BOUNCE", "COMPLAINT"]
|
|
27743
|
+
}
|
|
27744
|
+
})
|
|
27745
|
+
);
|
|
27746
|
+
}
|
|
27747
|
+
);
|
|
27748
|
+
const allEvents = [
|
|
27749
|
+
EventType.SEND,
|
|
27750
|
+
EventType.DELIVERY,
|
|
27751
|
+
EventType.OPEN,
|
|
27752
|
+
EventType.CLICK,
|
|
27753
|
+
EventType.BOUNCE,
|
|
27754
|
+
EventType.COMPLAINT,
|
|
27755
|
+
EventType.REJECT,
|
|
27756
|
+
EventType.RENDERING_FAILURE,
|
|
27757
|
+
EventType.DELIVERY_DELAY,
|
|
27758
|
+
EventType.SUBSCRIPTION
|
|
27759
|
+
];
|
|
27760
|
+
const matchingEventTypes = d.trackingConfig != null ? allEvents.filter((evt) => {
|
|
27761
|
+
if (evt === EventType.OPEN) return d.trackingConfig.opens;
|
|
27762
|
+
if (evt === EventType.CLICK) return d.trackingConfig.clicks;
|
|
27763
|
+
return true;
|
|
27764
|
+
}) : [...allEvents];
|
|
27765
|
+
await progress.execute(
|
|
27766
|
+
`Adding EventBridge destination for ${d.domain}`,
|
|
27767
|
+
async () => {
|
|
27768
|
+
await sesClient.send(
|
|
27769
|
+
new CreateConfigurationSetEventDestinationCommand2({
|
|
27770
|
+
ConfigurationSetName: configSetName,
|
|
27771
|
+
EventDestinationName: "wraps-eventbridge",
|
|
27772
|
+
EventDestination: {
|
|
27773
|
+
Enabled: true,
|
|
27774
|
+
MatchingEventTypes: matchingEventTypes,
|
|
27775
|
+
EventBridgeDestination: { EventBusArn: eventBusArn }
|
|
27776
|
+
}
|
|
27777
|
+
})
|
|
27778
|
+
);
|
|
27779
|
+
}
|
|
27780
|
+
);
|
|
27781
|
+
d.configSetName = configSetName;
|
|
27782
|
+
await saveConnectionMetadata(metadata);
|
|
27783
|
+
try {
|
|
27784
|
+
await progress.execute(
|
|
27785
|
+
`Reassigning identity ${d.domain}`,
|
|
27786
|
+
async () => {
|
|
27787
|
+
await sesClient.send(
|
|
27788
|
+
new PutEmailIdentityConfigurationSetAttributesCommand({
|
|
27789
|
+
EmailIdentity: d.domain,
|
|
27790
|
+
ConfigurationSetName: configSetName
|
|
27791
|
+
})
|
|
27792
|
+
);
|
|
27793
|
+
}
|
|
27794
|
+
);
|
|
27795
|
+
} catch (err) {
|
|
27796
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
27797
|
+
clack34.log.warn(
|
|
27798
|
+
`Failed to reassign identity for ${pc35.cyan(d.domain)}: ${msg}`
|
|
27799
|
+
);
|
|
27800
|
+
clack34.log.info(
|
|
27801
|
+
pc35.dim(
|
|
27802
|
+
`Config set was saved \u2014 re-run to retry identity reassignment for ${d.domain}`
|
|
27803
|
+
)
|
|
27804
|
+
);
|
|
27805
|
+
}
|
|
27806
|
+
if (i < unmigratedDomains.length - 1) {
|
|
27807
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
27808
|
+
}
|
|
27809
|
+
}
|
|
27810
|
+
clack34.log.info(
|
|
27811
|
+
`For the primary domain (${pc35.cyan(config2.domain ?? "")}) config set rename: re-run ${pc35.cyan("wraps email upgrade")} with a Pulumi stack update.`
|
|
27812
|
+
);
|
|
27813
|
+
clack34.outro(
|
|
27814
|
+
pc35.green(
|
|
27815
|
+
`\u2713 Per-domain config sets migration complete (${unmigratedDomains.length} domain(s))`
|
|
27816
|
+
)
|
|
27817
|
+
);
|
|
27818
|
+
trackServiceUpgrade("email", {
|
|
27819
|
+
action: "per-domain-config-sets",
|
|
27820
|
+
duration_ms: Date.now() - startTime
|
|
27821
|
+
});
|
|
27822
|
+
return;
|
|
27823
|
+
}
|
|
27436
27824
|
}
|
|
27437
27825
|
const newCostData = calculateCosts(updatedConfig, 5e4);
|
|
27438
27826
|
const costDiff = newCostData.total.monthly - currentCostData.total.monthly;
|
|
@@ -28813,7 +29201,7 @@ function assignPositions(steps, transitions) {
|
|
|
28813
29201
|
|
|
28814
29202
|
// src/utils/email/workflow-ts.ts
|
|
28815
29203
|
init_esm_shims();
|
|
28816
|
-
import { createHash as
|
|
29204
|
+
import { createHash as createHash3 } from "crypto";
|
|
28817
29205
|
import { existsSync as existsSync15 } from "fs";
|
|
28818
29206
|
import { mkdir as mkdir8, readdir as readdir4, readFile as readFile8, writeFile as writeFile10 } from "fs/promises";
|
|
28819
29207
|
import { basename, join as join17 } from "path";
|
|
@@ -28839,7 +29227,7 @@ async function discoverWorkflows(dir, filter) {
|
|
|
28839
29227
|
async function parseWorkflowTs(filePath, wrapsDir) {
|
|
28840
29228
|
const { build: build2 } = await import("esbuild");
|
|
28841
29229
|
const source = await readFile8(filePath, "utf-8");
|
|
28842
|
-
const sourceHash =
|
|
29230
|
+
const sourceHash = createHash3("sha256").update(source).digest("hex");
|
|
28843
29231
|
const slug = basename(filePath, ".ts");
|
|
28844
29232
|
const shimDir = join17(wrapsDir, ".wraps", "_shims");
|
|
28845
29233
|
await mkdir8(shimDir, { recursive: true });
|
|
@@ -30494,7 +30882,7 @@ import {
|
|
|
30494
30882
|
IAMClient as IAMClient3,
|
|
30495
30883
|
PutRolePolicyCommand
|
|
30496
30884
|
} from "@aws-sdk/client-iam";
|
|
30497
|
-
import { confirm as confirm18, intro as intro34, isCancel as isCancel25, log as log36, outro as
|
|
30885
|
+
import { confirm as confirm18, intro as intro34, isCancel as isCancel25, log as log36, outro as outro22, select as select17 } from "@clack/prompts";
|
|
30498
30886
|
import * as pulumi24 from "@pulumi/pulumi";
|
|
30499
30887
|
import pc41 from "picocolors";
|
|
30500
30888
|
init_events();
|
|
@@ -30856,7 +31244,7 @@ async function resolveOrganization() {
|
|
|
30856
31244
|
}))
|
|
30857
31245
|
});
|
|
30858
31246
|
if (isCancel25(selected)) {
|
|
30859
|
-
|
|
31247
|
+
outro22("Operation cancelled");
|
|
30860
31248
|
process.exit(0);
|
|
30861
31249
|
}
|
|
30862
31250
|
return orgs.find((o) => o.id === selected) || null;
|
|
@@ -30922,7 +31310,7 @@ async function authenticatedConnect(token, options) {
|
|
|
30922
31310
|
initialValue: true
|
|
30923
31311
|
});
|
|
30924
31312
|
if (isCancel25(enableTracking) || !enableTracking) {
|
|
30925
|
-
|
|
31313
|
+
outro22("Platform connection cancelled.");
|
|
30926
31314
|
process.exit(0);
|
|
30927
31315
|
}
|
|
30928
31316
|
metadata.services.email.config = {
|
|
@@ -31013,7 +31401,7 @@ You can try the manual flow: ${pc41.cyan("wraps auth logout")} then ${pc41.cyan(
|
|
|
31013
31401
|
webhookConnected: true
|
|
31014
31402
|
});
|
|
31015
31403
|
} else {
|
|
31016
|
-
|
|
31404
|
+
outro22(pc41.green("Platform connection complete!"));
|
|
31017
31405
|
console.log();
|
|
31018
31406
|
console.log(
|
|
31019
31407
|
pc41.dim(
|
|
@@ -31117,7 +31505,7 @@ Run ${pc41.cyan("wraps email init")} or ${pc41.cyan("wraps sms init")} first.
|
|
|
31117
31505
|
initialValue: true
|
|
31118
31506
|
});
|
|
31119
31507
|
if (isCancel25(enableEventTracking) || !enableEventTracking) {
|
|
31120
|
-
|
|
31508
|
+
outro22("Platform connection cancelled.");
|
|
31121
31509
|
process.exit(0);
|
|
31122
31510
|
}
|
|
31123
31511
|
metadata.services.email.config = {
|
|
@@ -31165,7 +31553,7 @@ Run ${pc41.cyan("wraps email init")} or ${pc41.cyan("wraps sms init")} first.
|
|
|
31165
31553
|
]
|
|
31166
31554
|
});
|
|
31167
31555
|
if (isCancel25(action)) {
|
|
31168
|
-
|
|
31556
|
+
outro22("Operation cancelled");
|
|
31169
31557
|
process.exit(0);
|
|
31170
31558
|
}
|
|
31171
31559
|
if (action === "keep") {
|
|
@@ -31176,7 +31564,7 @@ Run ${pc41.cyan("wraps email init")} or ${pc41.cyan("wraps sms init")} first.
|
|
|
31176
31564
|
initialValue: false
|
|
31177
31565
|
});
|
|
31178
31566
|
if (isCancel25(confirmDisconnect) || !confirmDisconnect) {
|
|
31179
|
-
|
|
31567
|
+
outro22("Disconnect cancelled");
|
|
31180
31568
|
process.exit(0);
|
|
31181
31569
|
}
|
|
31182
31570
|
metadata.services.email.webhookSecret = void 0;
|
|
@@ -31274,7 +31662,7 @@ Run ${pc41.cyan("wraps email init")} or ${pc41.cyan("wraps sms init")} first.
|
|
|
31274
31662
|
}
|
|
31275
31663
|
await saveConnectionMetadata(metadata);
|
|
31276
31664
|
progress.stop();
|
|
31277
|
-
|
|
31665
|
+
outro22(pc41.green("Platform connection complete!"));
|
|
31278
31666
|
if (webhookSecret && needsDeployment) {
|
|
31279
31667
|
console.log(`
|
|
31280
31668
|
${pc41.bold("Webhook Secret")} ${pc41.dim("(save this!)")}`);
|
|
@@ -31387,7 +31775,7 @@ import {
|
|
|
31387
31775
|
GetRoleCommand as GetRoleCommand2,
|
|
31388
31776
|
IAMClient as IAMClient4
|
|
31389
31777
|
} from "@aws-sdk/client-iam";
|
|
31390
|
-
import { confirm as confirm19, intro as intro36, isCancel as isCancel26, log as log37, outro as
|
|
31778
|
+
import { confirm as confirm19, intro as intro36, isCancel as isCancel26, log as log37, outro as outro23 } from "@clack/prompts";
|
|
31391
31779
|
import pc43 from "picocolors";
|
|
31392
31780
|
async function updateRole(options) {
|
|
31393
31781
|
const startTime = Date.now();
|
|
@@ -31457,7 +31845,7 @@ Run ${pc43.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
31457
31845
|
initialValue: true
|
|
31458
31846
|
});
|
|
31459
31847
|
if (isCancel26(shouldContinue) || !shouldContinue) {
|
|
31460
|
-
|
|
31848
|
+
outro23(`${actionLabel} cancelled`);
|
|
31461
31849
|
process.exit(0);
|
|
31462
31850
|
}
|
|
31463
31851
|
}
|
|
@@ -31537,7 +31925,7 @@ Run ${pc43.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
31537
31925
|
});
|
|
31538
31926
|
return;
|
|
31539
31927
|
}
|
|
31540
|
-
|
|
31928
|
+
outro23(pc43.green(`\u2713 Platform access role ${actionVerb} successfully`));
|
|
31541
31929
|
console.log(`
|
|
31542
31930
|
${pc43.bold("Permissions:")}`);
|
|
31543
31931
|
console.log(`
|
|
@@ -33096,6 +33484,7 @@ function createMetricsRouter(config2) {
|
|
|
33096
33484
|
|
|
33097
33485
|
// src/console/routes/settings.ts
|
|
33098
33486
|
init_esm_shims();
|
|
33487
|
+
init_config_set_slug();
|
|
33099
33488
|
init_metadata();
|
|
33100
33489
|
import dns4 from "dns/promises";
|
|
33101
33490
|
import { Router as createRouter6 } from "express";
|
|
@@ -33232,8 +33621,8 @@ function createSettingsRouter(config2) {
|
|
|
33232
33621
|
error: "No Wraps infrastructure found for this account and region"
|
|
33233
33622
|
});
|
|
33234
33623
|
}
|
|
33235
|
-
const configSetName = "wraps-email-tracking";
|
|
33236
33624
|
const domain = metadata.services.email?.config.domain;
|
|
33625
|
+
const configSetName = domainToConfigSetName(domain ?? "");
|
|
33237
33626
|
const settings = await fetchEmailSettings(
|
|
33238
33627
|
config2.roleArn,
|
|
33239
33628
|
config2.region,
|
|
@@ -33335,7 +33724,9 @@ function createSettingsRouter(config2) {
|
|
|
33335
33724
|
error: "No Wraps infrastructure found for this account and region"
|
|
33336
33725
|
});
|
|
33337
33726
|
}
|
|
33338
|
-
const configSetName =
|
|
33727
|
+
const configSetName = domainToConfigSetName(
|
|
33728
|
+
metadata.services.email?.config.domain ?? ""
|
|
33729
|
+
);
|
|
33339
33730
|
console.log(
|
|
33340
33731
|
`[Settings] Updating sending options for ${configSetName}: ${enabled}`
|
|
33341
33732
|
);
|
|
@@ -33372,7 +33763,9 @@ function createSettingsRouter(config2) {
|
|
|
33372
33763
|
error: "No Wraps infrastructure found for this account and region"
|
|
33373
33764
|
});
|
|
33374
33765
|
}
|
|
33375
|
-
const configSetName =
|
|
33766
|
+
const configSetName = domainToConfigSetName(
|
|
33767
|
+
metadata.services.email?.config.domain ?? ""
|
|
33768
|
+
);
|
|
33376
33769
|
console.log(
|
|
33377
33770
|
`[Settings] Updating reputation options for ${configSetName}: ${enabled}`
|
|
33378
33771
|
);
|
|
@@ -33415,7 +33808,9 @@ function createSettingsRouter(config2) {
|
|
|
33415
33808
|
error: "No Wraps infrastructure found for this account and region"
|
|
33416
33809
|
});
|
|
33417
33810
|
}
|
|
33418
|
-
const configSetName =
|
|
33811
|
+
const configSetName = domainToConfigSetName(
|
|
33812
|
+
metadata.services.email?.config.domain ?? ""
|
|
33813
|
+
);
|
|
33419
33814
|
console.log(
|
|
33420
33815
|
`[Settings] Updating tracking domain for ${configSetName}: ${domain}`
|
|
33421
33816
|
);
|
|
@@ -36464,7 +36859,7 @@ ${pc48.yellow(pc48.bold("Important Notes:"))}`);
|
|
|
36464
36859
|
clack45.log.warn(warning);
|
|
36465
36860
|
}
|
|
36466
36861
|
}
|
|
36467
|
-
if (!options.yes) {
|
|
36862
|
+
if (!(options.yes || options.preview)) {
|
|
36468
36863
|
const confirmed = await confirmDeploy();
|
|
36469
36864
|
if (!confirmed) {
|
|
36470
36865
|
clack45.cancel("Deployment cancelled.");
|
|
@@ -36477,43 +36872,77 @@ ${pc48.yellow(pc48.bold("Important Notes:"))}`);
|
|
|
36477
36872
|
vercel: vercelConfig,
|
|
36478
36873
|
smsConfig
|
|
36479
36874
|
};
|
|
36875
|
+
const createStack = async () => {
|
|
36876
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
36877
|
+
const stack = await pulumi29.automation.LocalWorkspace.createOrSelectStack(
|
|
36878
|
+
{
|
|
36879
|
+
stackName: `wraps-sms-${identity.accountId}-${region}`,
|
|
36880
|
+
projectName: "wraps-sms",
|
|
36881
|
+
program: async () => {
|
|
36882
|
+
const result = await deploySMSStack(stackConfig);
|
|
36883
|
+
return {
|
|
36884
|
+
roleArn: result.roleArn,
|
|
36885
|
+
phoneNumber: result.phoneNumber,
|
|
36886
|
+
phoneNumberArn: result.phoneNumberArn,
|
|
36887
|
+
configSetName: result.configSetName,
|
|
36888
|
+
tableName: result.tableName,
|
|
36889
|
+
region: result.region,
|
|
36890
|
+
lambdaFunctions: result.lambdaFunctions,
|
|
36891
|
+
snsTopicArn: result.snsTopicArn,
|
|
36892
|
+
queueUrl: result.queueUrl,
|
|
36893
|
+
dlqUrl: result.dlqUrl,
|
|
36894
|
+
optOutListArn: result.optOutListArn
|
|
36895
|
+
};
|
|
36896
|
+
}
|
|
36897
|
+
},
|
|
36898
|
+
{
|
|
36899
|
+
workDir: getPulumiWorkDir(),
|
|
36900
|
+
envVars: {
|
|
36901
|
+
PULUMI_CONFIG_PASSPHRASE: "",
|
|
36902
|
+
AWS_REGION: region
|
|
36903
|
+
},
|
|
36904
|
+
secretsProvider: "passphrase"
|
|
36905
|
+
}
|
|
36906
|
+
);
|
|
36907
|
+
await stack.setConfig("aws:region", { value: region });
|
|
36908
|
+
return stack;
|
|
36909
|
+
};
|
|
36910
|
+
if (options.preview) {
|
|
36911
|
+
try {
|
|
36912
|
+
const previewResult = await progress.execute(
|
|
36913
|
+
"Generating infrastructure preview",
|
|
36914
|
+
async () => {
|
|
36915
|
+
const stack = await createStack();
|
|
36916
|
+
return previewWithResourceChanges(stack, { diff: true });
|
|
36917
|
+
}
|
|
36918
|
+
);
|
|
36919
|
+
displayPreview({
|
|
36920
|
+
changeSummary: previewResult.changeSummary,
|
|
36921
|
+
resourceChanges: previewResult.resourceChanges,
|
|
36922
|
+
costEstimate: getSMSCostSummary(smsConfig, 0),
|
|
36923
|
+
commandName: "wraps sms init"
|
|
36924
|
+
});
|
|
36925
|
+
clack45.outro(
|
|
36926
|
+
pc48.green("Preview complete. Run without --preview to deploy.")
|
|
36927
|
+
);
|
|
36928
|
+
trackServiceInit("sms", true, {
|
|
36929
|
+
provider,
|
|
36930
|
+
region,
|
|
36931
|
+
preview: true,
|
|
36932
|
+
duration_ms: Date.now() - startTime
|
|
36933
|
+
});
|
|
36934
|
+
} catch (error) {
|
|
36935
|
+
trackError("PREVIEW_FAILED", "sms:init", { step: "preview" });
|
|
36936
|
+
throw error;
|
|
36937
|
+
}
|
|
36938
|
+
return;
|
|
36939
|
+
}
|
|
36480
36940
|
let outputs;
|
|
36481
36941
|
try {
|
|
36482
36942
|
outputs = await progress.execute(
|
|
36483
36943
|
"Deploying SMS infrastructure (this may take 2-3 minutes)",
|
|
36484
36944
|
async () => {
|
|
36485
|
-
|
|
36486
|
-
const stack = await pulumi29.automation.LocalWorkspace.createOrSelectStack(
|
|
36487
|
-
{
|
|
36488
|
-
stackName: `wraps-sms-${identity.accountId}-${region}`,
|
|
36489
|
-
projectName: "wraps-sms",
|
|
36490
|
-
program: async () => {
|
|
36491
|
-
const result = await deploySMSStack(stackConfig);
|
|
36492
|
-
return {
|
|
36493
|
-
roleArn: result.roleArn,
|
|
36494
|
-
phoneNumber: result.phoneNumber,
|
|
36495
|
-
phoneNumberArn: result.phoneNumberArn,
|
|
36496
|
-
configSetName: result.configSetName,
|
|
36497
|
-
tableName: result.tableName,
|
|
36498
|
-
region: result.region,
|
|
36499
|
-
lambdaFunctions: result.lambdaFunctions,
|
|
36500
|
-
snsTopicArn: result.snsTopicArn,
|
|
36501
|
-
queueUrl: result.queueUrl,
|
|
36502
|
-
dlqUrl: result.dlqUrl,
|
|
36503
|
-
optOutListArn: result.optOutListArn
|
|
36504
|
-
};
|
|
36505
|
-
}
|
|
36506
|
-
},
|
|
36507
|
-
{
|
|
36508
|
-
workDir: getPulumiWorkDir(),
|
|
36509
|
-
envVars: {
|
|
36510
|
-
PULUMI_CONFIG_PASSPHRASE: "",
|
|
36511
|
-
AWS_REGION: region
|
|
36512
|
-
},
|
|
36513
|
-
secretsProvider: "passphrase"
|
|
36514
|
-
}
|
|
36515
|
-
);
|
|
36516
|
-
await stack.setConfig("aws:region", { value: region });
|
|
36945
|
+
const stack = await createStack();
|
|
36517
36946
|
const upResult = await withLockRetry(
|
|
36518
36947
|
() => stack.up({ onOutput: console.log }),
|
|
36519
36948
|
{ accountId: identity.accountId, region, autoConfirm: options.yes }
|
|
@@ -38180,7 +38609,7 @@ ${pc53.bold("Cost Impact:")}`);
|
|
|
38180
38609
|
);
|
|
38181
38610
|
}
|
|
38182
38611
|
console.log("");
|
|
38183
|
-
if (!options.yes) {
|
|
38612
|
+
if (!(options.yes || options.preview)) {
|
|
38184
38613
|
const confirmed = await clack50.confirm({
|
|
38185
38614
|
message: "Proceed with upgrade?",
|
|
38186
38615
|
initialValue: true
|
|
@@ -38202,46 +38631,79 @@ ${pc53.bold("Cost Impact:")}`);
|
|
|
38202
38631
|
vercel: vercelConfig,
|
|
38203
38632
|
smsConfig: updatedConfig
|
|
38204
38633
|
};
|
|
38634
|
+
const stackName = metadata.services.sms?.pulumiStackName || `wraps-sms-${identity.accountId}-${region}`;
|
|
38635
|
+
const createStack = async () => {
|
|
38636
|
+
await ensurePulumiWorkDir({ accountId: identity.accountId, region });
|
|
38637
|
+
const stack = await pulumi32.automation.LocalWorkspace.createOrSelectStack(
|
|
38638
|
+
{
|
|
38639
|
+
stackName,
|
|
38640
|
+
projectName: "wraps-sms",
|
|
38641
|
+
program: async () => {
|
|
38642
|
+
const result = await deploySMSStack(stackConfig);
|
|
38643
|
+
return {
|
|
38644
|
+
roleArn: result.roleArn,
|
|
38645
|
+
phoneNumber: result.phoneNumber,
|
|
38646
|
+
phoneNumberArn: result.phoneNumberArn,
|
|
38647
|
+
configSetName: result.configSetName,
|
|
38648
|
+
tableName: result.tableName,
|
|
38649
|
+
region: result.region,
|
|
38650
|
+
lambdaFunctions: result.lambdaFunctions,
|
|
38651
|
+
snsTopicArn: result.snsTopicArn,
|
|
38652
|
+
queueUrl: result.queueUrl,
|
|
38653
|
+
dlqUrl: result.dlqUrl,
|
|
38654
|
+
optOutListArn: result.optOutListArn
|
|
38655
|
+
};
|
|
38656
|
+
}
|
|
38657
|
+
},
|
|
38658
|
+
{
|
|
38659
|
+
workDir: getPulumiWorkDir(),
|
|
38660
|
+
envVars: {
|
|
38661
|
+
PULUMI_CONFIG_PASSPHRASE: "",
|
|
38662
|
+
AWS_REGION: region
|
|
38663
|
+
},
|
|
38664
|
+
secretsProvider: "passphrase"
|
|
38665
|
+
}
|
|
38666
|
+
);
|
|
38667
|
+
await stack.workspace.selectStack(stackName);
|
|
38668
|
+
await stack.setConfig("aws:region", { value: region });
|
|
38669
|
+
return stack;
|
|
38670
|
+
};
|
|
38671
|
+
if (options.preview) {
|
|
38672
|
+
try {
|
|
38673
|
+
const previewResult = await progress.execute(
|
|
38674
|
+
"Generating infrastructure preview",
|
|
38675
|
+
async () => {
|
|
38676
|
+
const stack = await createStack();
|
|
38677
|
+
await stack.refresh({ onOutput: () => {
|
|
38678
|
+
} });
|
|
38679
|
+
return previewWithResourceChanges(stack, { diff: true });
|
|
38680
|
+
}
|
|
38681
|
+
);
|
|
38682
|
+
displayPreview({
|
|
38683
|
+
changeSummary: previewResult.changeSummary,
|
|
38684
|
+
resourceChanges: previewResult.resourceChanges,
|
|
38685
|
+
commandName: "wraps sms upgrade"
|
|
38686
|
+
});
|
|
38687
|
+
clack50.outro(
|
|
38688
|
+
pc53.green("Preview complete. Run without --preview to upgrade.")
|
|
38689
|
+
);
|
|
38690
|
+
trackServiceUpgrade("sms", {
|
|
38691
|
+
region,
|
|
38692
|
+
preview: true,
|
|
38693
|
+
duration_ms: Date.now() - startTime
|
|
38694
|
+
});
|
|
38695
|
+
} catch (error) {
|
|
38696
|
+
trackError("PREVIEW_FAILED", "sms:upgrade", { step: "preview" });
|
|
38697
|
+
throw error;
|
|
38698
|
+
}
|
|
38699
|
+
return;
|
|
38700
|
+
}
|
|
38205
38701
|
let outputs;
|
|
38206
38702
|
try {
|
|
38207
38703
|
outputs = await progress.execute(
|
|
38208
38704
|
"Updating SMS infrastructure (this may take 2-3 minutes)",
|
|
38209
38705
|
async () => {
|
|
38210
|
-
|
|
38211
|
-
const stack = await pulumi32.automation.LocalWorkspace.createOrSelectStack(
|
|
38212
|
-
{
|
|
38213
|
-
stackName: metadata.services.sms?.pulumiStackName || `wraps-sms-${identity.accountId}-${region}`,
|
|
38214
|
-
projectName: "wraps-sms",
|
|
38215
|
-
program: async () => {
|
|
38216
|
-
const result = await deploySMSStack(stackConfig);
|
|
38217
|
-
return {
|
|
38218
|
-
roleArn: result.roleArn,
|
|
38219
|
-
phoneNumber: result.phoneNumber,
|
|
38220
|
-
phoneNumberArn: result.phoneNumberArn,
|
|
38221
|
-
configSetName: result.configSetName,
|
|
38222
|
-
tableName: result.tableName,
|
|
38223
|
-
region: result.region,
|
|
38224
|
-
lambdaFunctions: result.lambdaFunctions,
|
|
38225
|
-
snsTopicArn: result.snsTopicArn,
|
|
38226
|
-
queueUrl: result.queueUrl,
|
|
38227
|
-
dlqUrl: result.dlqUrl,
|
|
38228
|
-
optOutListArn: result.optOutListArn
|
|
38229
|
-
};
|
|
38230
|
-
}
|
|
38231
|
-
},
|
|
38232
|
-
{
|
|
38233
|
-
workDir: getPulumiWorkDir(),
|
|
38234
|
-
envVars: {
|
|
38235
|
-
PULUMI_CONFIG_PASSPHRASE: "",
|
|
38236
|
-
AWS_REGION: region
|
|
38237
|
-
},
|
|
38238
|
-
secretsProvider: "passphrase"
|
|
38239
|
-
}
|
|
38240
|
-
);
|
|
38241
|
-
await stack.workspace.selectStack(
|
|
38242
|
-
metadata.services.sms?.pulumiStackName || `wraps-sms-${identity.accountId}-${region}`
|
|
38243
|
-
);
|
|
38244
|
-
await stack.setConfig("aws:region", { value: region });
|
|
38706
|
+
const stack = await createStack();
|
|
38245
38707
|
await stack.refresh({ onOutput: () => {
|
|
38246
38708
|
} });
|
|
38247
38709
|
const upResult = await stack.up({ onOutput: () => {
|
|
@@ -39567,7 +40029,8 @@ if (!primaryCommand) {
|
|
|
39567
40029
|
provider: flags.provider,
|
|
39568
40030
|
region: flags.region,
|
|
39569
40031
|
preset: flags.preset,
|
|
39570
|
-
yes: flags.yes
|
|
40032
|
+
yes: flags.yes,
|
|
40033
|
+
preview: flags.preview
|
|
39571
40034
|
});
|
|
39572
40035
|
break;
|
|
39573
40036
|
case "cdn-init":
|
|
@@ -39752,6 +40215,7 @@ Usage: ${pc59.cyan("wraps email verify --domain yourapp.com")}
|
|
|
39752
40215
|
await inboundDestroy({
|
|
39753
40216
|
region: flags.region,
|
|
39754
40217
|
force: flags.force,
|
|
40218
|
+
preview: flags.preview,
|
|
39755
40219
|
json: flags.json
|
|
39756
40220
|
});
|
|
39757
40221
|
break;
|
|
@@ -39816,6 +40280,7 @@ Available commands: ${pc59.cyan("init")}, ${pc59.cyan("destroy")}, ${pc59.cyan("
|
|
|
39816
40280
|
domain: flags.domain,
|
|
39817
40281
|
all: flags.all,
|
|
39818
40282
|
yes: flags.yes,
|
|
40283
|
+
preview: flags.preview,
|
|
39819
40284
|
json: flags.json
|
|
39820
40285
|
});
|
|
39821
40286
|
break;
|
|
@@ -39839,6 +40304,7 @@ Available commands: ${pc59.cyan("init")}, ${pc59.cyan("destroy")}, ${pc59.cyan("
|
|
|
39839
40304
|
domain: flags.domain,
|
|
39840
40305
|
all: flags.all,
|
|
39841
40306
|
force: flags.force,
|
|
40307
|
+
preview: flags.preview,
|
|
39842
40308
|
json: flags.json
|
|
39843
40309
|
});
|
|
39844
40310
|
break;
|
|
@@ -40065,6 +40531,7 @@ Run ${pc59.cyan("wraps --help")} for available commands.
|
|
|
40065
40531
|
region: flags.region,
|
|
40066
40532
|
preset: flags.preset,
|
|
40067
40533
|
yes: flags.yes,
|
|
40534
|
+
preview: flags.preview,
|
|
40068
40535
|
json: flags.json
|
|
40069
40536
|
});
|
|
40070
40537
|
break;
|
|
@@ -40087,6 +40554,7 @@ Run ${pc59.cyan("wraps --help")} for available commands.
|
|
|
40087
40554
|
await smsUpgrade({
|
|
40088
40555
|
region: flags.region,
|
|
40089
40556
|
yes: flags.yes,
|
|
40557
|
+
preview: flags.preview,
|
|
40090
40558
|
json: flags.json
|
|
40091
40559
|
});
|
|
40092
40560
|
break;
|