@wraps.dev/cli 2.11.4 → 2.11.6
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
|
@@ -1133,9 +1133,11 @@ var init_aws_detection = __esm({
|
|
|
1133
1133
|
var errors_exports = {};
|
|
1134
1134
|
__export(errors_exports, {
|
|
1135
1135
|
WrapsError: () => WrapsError,
|
|
1136
|
+
classifyDNSError: () => classifyDNSError,
|
|
1136
1137
|
errors: () => errors,
|
|
1137
1138
|
handleCLIError: () => handleCLIError,
|
|
1138
1139
|
isAWSError: () => isAWSError,
|
|
1140
|
+
isAWSNotFoundError: () => isAWSNotFoundError,
|
|
1139
1141
|
isPulumiError: () => isPulumiError,
|
|
1140
1142
|
parseAWSError: () => parseAWSError,
|
|
1141
1143
|
parsePulumiError: () => parsePulumiError,
|
|
@@ -1162,6 +1164,23 @@ function isAWSError(error) {
|
|
|
1162
1164
|
];
|
|
1163
1165
|
return awsErrorNames.includes(error.name) || "$metadata" in error;
|
|
1164
1166
|
}
|
|
1167
|
+
function classifyDNSError(error) {
|
|
1168
|
+
if (!(error instanceof Error)) {
|
|
1169
|
+
return "unknown";
|
|
1170
|
+
}
|
|
1171
|
+
const code = error.code;
|
|
1172
|
+
if (code === "ENOTFOUND" || code === "ENODATA") {
|
|
1173
|
+
return "missing";
|
|
1174
|
+
}
|
|
1175
|
+
if (code === "ETIMEOUT" || code === "ESERVFAIL" || code === "ECONNREFUSED") {
|
|
1176
|
+
return "network";
|
|
1177
|
+
}
|
|
1178
|
+
return "unknown";
|
|
1179
|
+
}
|
|
1180
|
+
function isAWSNotFoundError(error) {
|
|
1181
|
+
if (!(error instanceof Error)) return false;
|
|
1182
|
+
return error.name === "NotFoundException" || error.name === "NoSuchEntityException" || error.name === "NoSuchEntity" || error.name === "ResourceNotFoundException" || error.$metadata?.httpStatusCode === 404;
|
|
1183
|
+
}
|
|
1165
1184
|
function isPulumiError(error) {
|
|
1166
1185
|
if (!(error instanceof Error)) {
|
|
1167
1186
|
return false;
|
|
@@ -4444,6 +4463,100 @@ var init_metadata = __esm({
|
|
|
4444
4463
|
}
|
|
4445
4464
|
});
|
|
4446
4465
|
|
|
4466
|
+
// src/infrastructure/shared/resource-checks.ts
|
|
4467
|
+
async function roleExists(roleName) {
|
|
4468
|
+
try {
|
|
4469
|
+
const { IAMClient: IAMClient4, GetRoleCommand: GetRoleCommand3 } = await import("@aws-sdk/client-iam");
|
|
4470
|
+
const iam10 = new IAMClient4({
|
|
4471
|
+
region: process.env.AWS_REGION || "us-east-1"
|
|
4472
|
+
});
|
|
4473
|
+
await iam10.send(new GetRoleCommand3({ RoleName: roleName }));
|
|
4474
|
+
return true;
|
|
4475
|
+
} catch (error) {
|
|
4476
|
+
if (error instanceof Error && (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity")) {
|
|
4477
|
+
return false;
|
|
4478
|
+
}
|
|
4479
|
+
console.error("Error checking for existing IAM role:", error);
|
|
4480
|
+
return false;
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
async function tableExists(tableName) {
|
|
4484
|
+
try {
|
|
4485
|
+
const { DynamoDBClient: DynamoDBClient6, DescribeTableCommand: DescribeTableCommand2 } = await import("@aws-sdk/client-dynamodb");
|
|
4486
|
+
const dynamodb3 = new DynamoDBClient6({
|
|
4487
|
+
region: process.env.AWS_REGION || "us-east-1"
|
|
4488
|
+
});
|
|
4489
|
+
await dynamodb3.send(new DescribeTableCommand2({ TableName: tableName }));
|
|
4490
|
+
return true;
|
|
4491
|
+
} catch (error) {
|
|
4492
|
+
if (error instanceof Error && error.name === "ResourceNotFoundException") {
|
|
4493
|
+
return false;
|
|
4494
|
+
}
|
|
4495
|
+
console.error("Error checking for existing DynamoDB table:", error);
|
|
4496
|
+
return false;
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
async function sqsQueueExists(queueName) {
|
|
4500
|
+
try {
|
|
4501
|
+
const { SQSClient, GetQueueUrlCommand } = await import("@aws-sdk/client-sqs");
|
|
4502
|
+
const sqs5 = new SQSClient({
|
|
4503
|
+
region: process.env.AWS_REGION || "us-east-1"
|
|
4504
|
+
});
|
|
4505
|
+
const response = await sqs5.send(
|
|
4506
|
+
new GetQueueUrlCommand({ QueueName: queueName })
|
|
4507
|
+
);
|
|
4508
|
+
return response.QueueUrl || null;
|
|
4509
|
+
} catch {
|
|
4510
|
+
return null;
|
|
4511
|
+
}
|
|
4512
|
+
}
|
|
4513
|
+
async function snsTopicExists(topicName) {
|
|
4514
|
+
try {
|
|
4515
|
+
const { SNSClient: SNSClient2, ListTopicsCommand: ListTopicsCommand2 } = await import("@aws-sdk/client-sns");
|
|
4516
|
+
const sns3 = new SNSClient2({
|
|
4517
|
+
region: process.env.AWS_REGION || "us-east-1"
|
|
4518
|
+
});
|
|
4519
|
+
let nextToken;
|
|
4520
|
+
do {
|
|
4521
|
+
const response = await sns3.send(
|
|
4522
|
+
new ListTopicsCommand2({ NextToken: nextToken })
|
|
4523
|
+
);
|
|
4524
|
+
const found = response.Topics?.find(
|
|
4525
|
+
(t) => t.TopicArn?.endsWith(`:${topicName}`)
|
|
4526
|
+
);
|
|
4527
|
+
if (found?.TopicArn) {
|
|
4528
|
+
return found.TopicArn;
|
|
4529
|
+
}
|
|
4530
|
+
nextToken = response.NextToken;
|
|
4531
|
+
} while (nextToken);
|
|
4532
|
+
return null;
|
|
4533
|
+
} catch {
|
|
4534
|
+
return null;
|
|
4535
|
+
}
|
|
4536
|
+
}
|
|
4537
|
+
async function lambdaFunctionExists(functionName) {
|
|
4538
|
+
try {
|
|
4539
|
+
const { LambdaClient: LambdaClient2, GetFunctionCommand } = await import("@aws-sdk/client-lambda");
|
|
4540
|
+
const lambda4 = new LambdaClient2({
|
|
4541
|
+
region: process.env.AWS_REGION || "us-east-1"
|
|
4542
|
+
});
|
|
4543
|
+
await lambda4.send(new GetFunctionCommand({ FunctionName: functionName }));
|
|
4544
|
+
return true;
|
|
4545
|
+
} catch (error) {
|
|
4546
|
+
if (error instanceof Error && error.name === "ResourceNotFoundException") {
|
|
4547
|
+
return false;
|
|
4548
|
+
}
|
|
4549
|
+
console.error("Error checking for existing Lambda function:", error);
|
|
4550
|
+
return false;
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
var init_resource_checks = __esm({
|
|
4554
|
+
"src/infrastructure/shared/resource-checks.ts"() {
|
|
4555
|
+
"use strict";
|
|
4556
|
+
init_esm_shims();
|
|
4557
|
+
}
|
|
4558
|
+
});
|
|
4559
|
+
|
|
4447
4560
|
// ../core/dist/index.js
|
|
4448
4561
|
var dist_exports = {};
|
|
4449
4562
|
__export(dist_exports, {
|
|
@@ -4661,22 +4774,6 @@ function getPackageRoot() {
|
|
|
4661
4774
|
}
|
|
4662
4775
|
throw new Error("Could not find package.json");
|
|
4663
4776
|
}
|
|
4664
|
-
async function lambdaFunctionExists(functionName) {
|
|
4665
|
-
try {
|
|
4666
|
-
const { LambdaClient: LambdaClient2, GetFunctionCommand } = await import("@aws-sdk/client-lambda");
|
|
4667
|
-
const lambda4 = new LambdaClient2({
|
|
4668
|
-
region: process.env.AWS_REGION || "us-east-1"
|
|
4669
|
-
});
|
|
4670
|
-
await lambda4.send(new GetFunctionCommand({ FunctionName: functionName }));
|
|
4671
|
-
return true;
|
|
4672
|
-
} catch (error) {
|
|
4673
|
-
if (error.name === "ResourceNotFoundException") {
|
|
4674
|
-
return false;
|
|
4675
|
-
}
|
|
4676
|
-
console.error("Error checking for existing Lambda function:", error);
|
|
4677
|
-
return false;
|
|
4678
|
-
}
|
|
4679
|
-
}
|
|
4680
4777
|
async function findEventSourceMapping(functionName, queueArn) {
|
|
4681
4778
|
try {
|
|
4682
4779
|
const { LambdaClient: LambdaClient2, ListEventSourceMappingsCommand } = await import("@aws-sdk/client-lambda");
|
|
@@ -4873,6 +4970,7 @@ var init_lambda = __esm({
|
|
|
4873
4970
|
"src/infrastructure/resources/lambda.ts"() {
|
|
4874
4971
|
"use strict";
|
|
4875
4972
|
init_esm_shims();
|
|
4973
|
+
init_resource_checks();
|
|
4876
4974
|
nodeBuiltins = builtinModules.flatMap((m) => [m, `node:${m}`]);
|
|
4877
4975
|
}
|
|
4878
4976
|
});
|
|
@@ -7334,7 +7432,11 @@ async function login(options) {
|
|
|
7334
7432
|
client_id: "wraps-cli"
|
|
7335
7433
|
});
|
|
7336
7434
|
if (codeError || !codeData) {
|
|
7337
|
-
trackCommand("auth:login", {
|
|
7435
|
+
trackCommand("auth:login", {
|
|
7436
|
+
success: false,
|
|
7437
|
+
duration_ms: Date.now() - startTime,
|
|
7438
|
+
method: "device"
|
|
7439
|
+
});
|
|
7338
7440
|
trackError("DEVICE_AUTH_FAILED", "auth:login", { step: "request_code" });
|
|
7339
7441
|
clack.log.error("Failed to start device authorization.");
|
|
7340
7442
|
process.exit(1);
|
|
@@ -7410,7 +7512,11 @@ async function login(options) {
|
|
|
7410
7512
|
continue;
|
|
7411
7513
|
}
|
|
7412
7514
|
if (errorCode === "access_denied") {
|
|
7413
|
-
trackCommand("auth:login", {
|
|
7515
|
+
trackCommand("auth:login", {
|
|
7516
|
+
success: false,
|
|
7517
|
+
duration_ms: Date.now() - startTime,
|
|
7518
|
+
method: "device"
|
|
7519
|
+
});
|
|
7414
7520
|
trackError("ACCESS_DENIED", "auth:login", { step: "poll_token" });
|
|
7415
7521
|
spinner8.stop("Denied.");
|
|
7416
7522
|
clack.log.error("Authorization was denied.");
|
|
@@ -7421,7 +7527,11 @@ async function login(options) {
|
|
|
7421
7527
|
}
|
|
7422
7528
|
}
|
|
7423
7529
|
}
|
|
7424
|
-
trackCommand("auth:login", {
|
|
7530
|
+
trackCommand("auth:login", {
|
|
7531
|
+
success: false,
|
|
7532
|
+
duration_ms: Date.now() - startTime,
|
|
7533
|
+
method: "device"
|
|
7534
|
+
});
|
|
7425
7535
|
trackError("DEVICE_CODE_EXPIRED", "auth:login", { step: "poll_token" });
|
|
7426
7536
|
spinner8.stop("Expired.");
|
|
7427
7537
|
clack.log.error("Device code expired. Run `wraps auth login` to try again.");
|
|
@@ -9134,12 +9244,13 @@ async function cdnDestroy(options) {
|
|
|
9134
9244
|
return;
|
|
9135
9245
|
} catch (error) {
|
|
9136
9246
|
progress.stop();
|
|
9137
|
-
|
|
9247
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
9248
|
+
if (msg.includes("No CDN infrastructure found")) {
|
|
9138
9249
|
clack9.log.warn("No CDN infrastructure found to preview");
|
|
9139
9250
|
process.exit(0);
|
|
9140
9251
|
}
|
|
9141
9252
|
trackError("PREVIEW_FAILED", "storage destroy", { step: "preview" });
|
|
9142
|
-
throw new Error(`Preview failed: ${
|
|
9253
|
+
throw new Error(`Preview failed: ${msg}`);
|
|
9143
9254
|
}
|
|
9144
9255
|
}
|
|
9145
9256
|
if (shouldCleanDNS && hostedZone && customDomain) {
|
|
@@ -9151,7 +9262,8 @@ async function cdnDestroy(options) {
|
|
|
9151
9262
|
}
|
|
9152
9263
|
);
|
|
9153
9264
|
} catch (error) {
|
|
9154
|
-
|
|
9265
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
9266
|
+
clack9.log.warn(`Could not delete DNS records: ${msg}`);
|
|
9155
9267
|
clack9.log.info("You may need to delete them manually from Route53");
|
|
9156
9268
|
}
|
|
9157
9269
|
}
|
|
@@ -9164,7 +9276,8 @@ async function cdnDestroy(options) {
|
|
|
9164
9276
|
}
|
|
9165
9277
|
);
|
|
9166
9278
|
} catch (error) {
|
|
9167
|
-
|
|
9279
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
9280
|
+
clack9.log.info(`Note: ${msg}`);
|
|
9168
9281
|
}
|
|
9169
9282
|
try {
|
|
9170
9283
|
await progress.execute(
|
|
@@ -9188,7 +9301,8 @@ async function cdnDestroy(options) {
|
|
|
9188
9301
|
);
|
|
9189
9302
|
} catch (error) {
|
|
9190
9303
|
progress.stop();
|
|
9191
|
-
|
|
9304
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
9305
|
+
if (msg.includes("No CDN infrastructure found")) {
|
|
9192
9306
|
clack9.log.warn("No CDN infrastructure found");
|
|
9193
9307
|
if (metadata) {
|
|
9194
9308
|
removeServiceFromConnection(metadata, "cdn");
|
|
@@ -9196,7 +9310,7 @@ async function cdnDestroy(options) {
|
|
|
9196
9310
|
}
|
|
9197
9311
|
process.exit(0);
|
|
9198
9312
|
}
|
|
9199
|
-
if (
|
|
9313
|
+
if (msg.includes("stack is currently locked")) {
|
|
9200
9314
|
trackError("STACK_LOCKED", "storage destroy", { step: "destroy" });
|
|
9201
9315
|
throw errors.stackLocked();
|
|
9202
9316
|
}
|
|
@@ -9635,6 +9749,9 @@ async function createCdnACMCertificate(config2) {
|
|
|
9635
9749
|
};
|
|
9636
9750
|
}
|
|
9637
9751
|
|
|
9752
|
+
// src/infrastructure/cdn-stack.ts
|
|
9753
|
+
init_resource_checks();
|
|
9754
|
+
|
|
9638
9755
|
// src/infrastructure/vercel-oidc.ts
|
|
9639
9756
|
init_esm_shims();
|
|
9640
9757
|
import * as aws2 from "@pulumi/aws";
|
|
@@ -9696,22 +9813,6 @@ async function createVercelOIDC(config2) {
|
|
|
9696
9813
|
}
|
|
9697
9814
|
|
|
9698
9815
|
// src/infrastructure/cdn-stack.ts
|
|
9699
|
-
async function roleExists(roleName) {
|
|
9700
|
-
try {
|
|
9701
|
-
const { IAMClient: IAMClient4, GetRoleCommand: GetRoleCommand3 } = await import("@aws-sdk/client-iam");
|
|
9702
|
-
const iam10 = new IAMClient4({
|
|
9703
|
-
region: process.env.AWS_REGION || "us-east-1"
|
|
9704
|
-
});
|
|
9705
|
-
await iam10.send(new GetRoleCommand3({ RoleName: roleName }));
|
|
9706
|
-
return true;
|
|
9707
|
-
} catch (error) {
|
|
9708
|
-
if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity") {
|
|
9709
|
-
return false;
|
|
9710
|
-
}
|
|
9711
|
-
console.error("Error checking for existing IAM role:", error);
|
|
9712
|
-
return false;
|
|
9713
|
-
}
|
|
9714
|
-
}
|
|
9715
9816
|
async function createCdnIAMRole(config2) {
|
|
9716
9817
|
let assumeRolePolicy;
|
|
9717
9818
|
if (config2.provider === "vercel" && config2.oidcProvider) {
|
|
@@ -10480,10 +10581,11 @@ ${pc11.yellow(pc11.bold("Configuration Notes:"))}`);
|
|
|
10480
10581
|
return;
|
|
10481
10582
|
} catch (error) {
|
|
10482
10583
|
trackError("PREVIEW_FAILED", "storage:init", { step: "preview" });
|
|
10483
|
-
|
|
10584
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
10585
|
+
if (msg.includes("stack is currently locked")) {
|
|
10484
10586
|
throw errors.stackLocked();
|
|
10485
10587
|
}
|
|
10486
|
-
throw new Error(`Preview failed: ${
|
|
10588
|
+
throw new Error(`Preview failed: ${msg}`);
|
|
10487
10589
|
}
|
|
10488
10590
|
}
|
|
10489
10591
|
let outputs;
|
|
@@ -10547,18 +10649,19 @@ ${pc11.yellow(pc11.bold("Configuration Notes:"))}`);
|
|
|
10547
10649
|
}
|
|
10548
10650
|
);
|
|
10549
10651
|
} catch (error) {
|
|
10652
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
10550
10653
|
trackServiceInit("cdn", false, {
|
|
10551
10654
|
preset,
|
|
10552
10655
|
provider,
|
|
10553
10656
|
region,
|
|
10554
10657
|
duration_ms: Date.now() - startTime
|
|
10555
10658
|
});
|
|
10556
|
-
if (
|
|
10659
|
+
if (msg.includes("stack is currently locked")) {
|
|
10557
10660
|
trackError("STACK_LOCKED", "storage:init", { step: "deploy" });
|
|
10558
10661
|
throw errors.stackLocked();
|
|
10559
10662
|
}
|
|
10560
10663
|
trackError("DEPLOYMENT_FAILED", "storage:init", { step: "deploy" });
|
|
10561
|
-
throw new Error(`Pulumi deployment failed: ${
|
|
10664
|
+
throw new Error(`Pulumi deployment failed: ${msg}`);
|
|
10562
10665
|
}
|
|
10563
10666
|
if (metadata.services.cdn) {
|
|
10564
10667
|
metadata.services.cdn.pulumiStackName = `wraps-cdn-${identity.accountId}-${region}`;
|
|
@@ -10691,7 +10794,8 @@ ${pc11.yellow(pc11.bold("Configuration Notes:"))}`);
|
|
|
10691
10794
|
}
|
|
10692
10795
|
} catch (error) {
|
|
10693
10796
|
progress.stop();
|
|
10694
|
-
|
|
10797
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
10798
|
+
clack10.log.warn(`Could not manage DNS records: ${msg}`);
|
|
10695
10799
|
}
|
|
10696
10800
|
}
|
|
10697
10801
|
}
|
|
@@ -11115,13 +11219,14 @@ async function cdnSync(options) {
|
|
|
11115
11219
|
return result.summary;
|
|
11116
11220
|
});
|
|
11117
11221
|
} catch (error) {
|
|
11222
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
11118
11223
|
trackCommand("storage:sync", {
|
|
11119
11224
|
success: false,
|
|
11120
|
-
error:
|
|
11225
|
+
error: msg,
|
|
11121
11226
|
region,
|
|
11122
11227
|
duration_ms: Date.now() - startTime
|
|
11123
11228
|
});
|
|
11124
|
-
clack12.log.error(`Sync failed: ${
|
|
11229
|
+
clack12.log.error(`Sync failed: ${msg}`);
|
|
11125
11230
|
process.exit(1);
|
|
11126
11231
|
}
|
|
11127
11232
|
clack12.log.success(pc13.green("CDN infrastructure synced!"));
|
|
@@ -11245,7 +11350,8 @@ Current configuration:
|
|
|
11245
11350
|
certStatus = certResponse.Certificate?.Status || "UNKNOWN";
|
|
11246
11351
|
} catch (error) {
|
|
11247
11352
|
progress.fail("Failed to check certificate status");
|
|
11248
|
-
|
|
11353
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
11354
|
+
clack13.log.error(`Error: ${msg}`);
|
|
11249
11355
|
process.exit(1);
|
|
11250
11356
|
}
|
|
11251
11357
|
progress.stop();
|
|
@@ -11369,13 +11475,14 @@ Ready to add custom domain: ${pc14.cyan(pendingDomain)}`);
|
|
|
11369
11475
|
}
|
|
11370
11476
|
);
|
|
11371
11477
|
} catch (error) {
|
|
11478
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
11372
11479
|
trackCommand("storage:upgrade", {
|
|
11373
11480
|
success: false,
|
|
11374
|
-
error:
|
|
11481
|
+
error: msg,
|
|
11375
11482
|
region,
|
|
11376
11483
|
duration_ms: Date.now() - startTime
|
|
11377
11484
|
});
|
|
11378
|
-
clack13.log.error(`Upgrade failed: ${
|
|
11485
|
+
clack13.log.error(`Upgrade failed: ${msg}`);
|
|
11379
11486
|
process.exit(1);
|
|
11380
11487
|
}
|
|
11381
11488
|
if (metadata.services.cdn) {
|
|
@@ -14224,16 +14331,17 @@ async function check(options) {
|
|
|
14224
14331
|
process.exit(getExitCode(result.score.grade));
|
|
14225
14332
|
} catch (error) {
|
|
14226
14333
|
spinner8?.stop("Check failed");
|
|
14334
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
14227
14335
|
if (options.json) {
|
|
14228
|
-
console.log(JSON.stringify({ error:
|
|
14336
|
+
console.log(JSON.stringify({ error: msg }));
|
|
14229
14337
|
} else {
|
|
14230
|
-
clack15.log.error(
|
|
14338
|
+
clack15.log.error(msg);
|
|
14231
14339
|
}
|
|
14232
14340
|
const duration = Date.now() - startTime;
|
|
14233
14341
|
trackCommand("email:check", {
|
|
14234
14342
|
success: false,
|
|
14235
14343
|
duration_ms: duration,
|
|
14236
|
-
error:
|
|
14344
|
+
error: msg
|
|
14237
14345
|
});
|
|
14238
14346
|
process.exit(4);
|
|
14239
14347
|
}
|
|
@@ -14843,23 +14951,8 @@ async function createAlertingResources(config2) {
|
|
|
14843
14951
|
|
|
14844
14952
|
// src/infrastructure/resources/dynamodb.ts
|
|
14845
14953
|
init_esm_shims();
|
|
14954
|
+
init_resource_checks();
|
|
14846
14955
|
import * as aws5 from "@pulumi/aws";
|
|
14847
|
-
async function tableExists(tableName) {
|
|
14848
|
-
try {
|
|
14849
|
-
const { DynamoDBClient: DynamoDBClient6, DescribeTableCommand: DescribeTableCommand2 } = await import("@aws-sdk/client-dynamodb");
|
|
14850
|
-
const dynamodb3 = new DynamoDBClient6({
|
|
14851
|
-
region: process.env.AWS_REGION || "us-east-1"
|
|
14852
|
-
});
|
|
14853
|
-
await dynamodb3.send(new DescribeTableCommand2({ TableName: tableName }));
|
|
14854
|
-
return true;
|
|
14855
|
-
} catch (error) {
|
|
14856
|
-
if (error.name === "ResourceNotFoundException") {
|
|
14857
|
-
return false;
|
|
14858
|
-
}
|
|
14859
|
-
console.error("Error checking for existing DynamoDB table:", error);
|
|
14860
|
-
return false;
|
|
14861
|
-
}
|
|
14862
|
-
}
|
|
14863
14956
|
async function createDynamoDBTables(_config) {
|
|
14864
14957
|
const tableName = "wraps-email-history";
|
|
14865
14958
|
const exists = await tableExists(tableName);
|
|
@@ -15056,24 +15149,9 @@ async function createEventBridgeResources(config2) {
|
|
|
15056
15149
|
|
|
15057
15150
|
// src/infrastructure/resources/iam.ts
|
|
15058
15151
|
init_esm_shims();
|
|
15152
|
+
init_resource_checks();
|
|
15059
15153
|
import * as aws7 from "@pulumi/aws";
|
|
15060
15154
|
import * as pulumi10 from "@pulumi/pulumi";
|
|
15061
|
-
async function roleExists2(roleName) {
|
|
15062
|
-
try {
|
|
15063
|
-
const { IAMClient: IAMClient4, GetRoleCommand: GetRoleCommand3 } = await import("@aws-sdk/client-iam");
|
|
15064
|
-
const iam10 = new IAMClient4({
|
|
15065
|
-
region: process.env.AWS_REGION || "us-east-1"
|
|
15066
|
-
});
|
|
15067
|
-
await iam10.send(new GetRoleCommand3({ RoleName: roleName }));
|
|
15068
|
-
return true;
|
|
15069
|
-
} catch (error) {
|
|
15070
|
-
if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity") {
|
|
15071
|
-
return false;
|
|
15072
|
-
}
|
|
15073
|
-
console.error("Error checking for existing IAM role:", error);
|
|
15074
|
-
return false;
|
|
15075
|
-
}
|
|
15076
|
-
}
|
|
15077
15155
|
async function createIAMRole(config2) {
|
|
15078
15156
|
let assumeRolePolicy;
|
|
15079
15157
|
if (config2.provider === "vercel" && config2.oidcProvider) {
|
|
@@ -15110,7 +15188,7 @@ async function createIAMRole(config2) {
|
|
|
15110
15188
|
throw new Error("Other providers not yet implemented");
|
|
15111
15189
|
}
|
|
15112
15190
|
const roleName = "wraps-email-role";
|
|
15113
|
-
const exists = await
|
|
15191
|
+
const exists = await roleExists(roleName);
|
|
15114
15192
|
const role = exists ? new aws7.iam.Role(
|
|
15115
15193
|
roleName,
|
|
15116
15194
|
{
|
|
@@ -15955,10 +16033,11 @@ ${pc17.bold("Current Configuration:")}
|
|
|
15955
16033
|
return;
|
|
15956
16034
|
} catch (error) {
|
|
15957
16035
|
trackError("PREVIEW_FAILED", "email:config", { step: "preview" });
|
|
15958
|
-
|
|
16036
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
16037
|
+
if (msg.includes("stack is currently locked")) {
|
|
15959
16038
|
throw errors.stackLocked();
|
|
15960
16039
|
}
|
|
15961
|
-
throw new Error(`Preview failed: ${
|
|
16040
|
+
throw new Error(`Preview failed: ${msg}`);
|
|
15962
16041
|
}
|
|
15963
16042
|
}
|
|
15964
16043
|
let outputs;
|
|
@@ -16016,16 +16095,17 @@ ${pc17.bold("Current Configuration:")}
|
|
|
16016
16095
|
}
|
|
16017
16096
|
);
|
|
16018
16097
|
} catch (error) {
|
|
16098
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
16019
16099
|
trackCommand("email:config", {
|
|
16020
16100
|
success: false,
|
|
16021
16101
|
duration_ms: Date.now() - startTime
|
|
16022
16102
|
});
|
|
16023
|
-
if (
|
|
16103
|
+
if (msg.includes("stack is currently locked")) {
|
|
16024
16104
|
trackError("STACK_LOCKED", "email:config", { step: "update" });
|
|
16025
16105
|
throw errors.stackLocked();
|
|
16026
16106
|
}
|
|
16027
16107
|
trackError("UPDATE_FAILED", "email:config", { step: "update" });
|
|
16028
|
-
throw new Error(`Pulumi update failed: ${
|
|
16108
|
+
throw new Error(`Pulumi update failed: ${msg}`);
|
|
16029
16109
|
}
|
|
16030
16110
|
metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
16031
16111
|
await saveConnectionMetadata(metadata);
|
|
@@ -16456,10 +16536,11 @@ async function connect2(options) {
|
|
|
16456
16536
|
return;
|
|
16457
16537
|
} catch (error) {
|
|
16458
16538
|
trackError("PREVIEW_FAILED", "email:connect", { step: "preview" });
|
|
16459
|
-
|
|
16539
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
16540
|
+
if (msg.includes("stack is currently locked")) {
|
|
16460
16541
|
throw errors.stackLocked();
|
|
16461
16542
|
}
|
|
16462
|
-
throw new Error(`Preview failed: ${
|
|
16543
|
+
throw new Error(`Preview failed: ${msg}`);
|
|
16463
16544
|
}
|
|
16464
16545
|
}
|
|
16465
16546
|
let outputs;
|
|
@@ -16515,17 +16596,18 @@ async function connect2(options) {
|
|
|
16515
16596
|
}
|
|
16516
16597
|
);
|
|
16517
16598
|
} catch (error) {
|
|
16599
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
16518
16600
|
trackServiceInit("email", false, {
|
|
16519
16601
|
preset,
|
|
16520
16602
|
provider,
|
|
16521
16603
|
duration_ms: Date.now() - startTime
|
|
16522
16604
|
});
|
|
16523
|
-
if (
|
|
16605
|
+
if (msg.includes("stack is currently locked")) {
|
|
16524
16606
|
trackError("STACK_LOCKED", "email:connect", { step: "deploy" });
|
|
16525
16607
|
throw errors.stackLocked();
|
|
16526
16608
|
}
|
|
16527
16609
|
trackError("DEPLOYMENT_FAILED", "email:connect", { step: "deploy" });
|
|
16528
|
-
throw new Error(`Pulumi deployment failed: ${
|
|
16610
|
+
throw new Error(`Pulumi deployment failed: ${msg}`);
|
|
16529
16611
|
}
|
|
16530
16612
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
16531
16613
|
const { findHostedZone: findHostedZone2, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
|
|
@@ -16544,9 +16626,8 @@ async function connect2(options) {
|
|
|
16544
16626
|
);
|
|
16545
16627
|
progress.succeed("DNS records created in Route53");
|
|
16546
16628
|
} catch (error) {
|
|
16547
|
-
|
|
16548
|
-
|
|
16549
|
-
);
|
|
16629
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
16630
|
+
progress.fail(`Failed to create DNS records automatically: ${msg}`);
|
|
16550
16631
|
progress.info(
|
|
16551
16632
|
"You can manually add the required DNS records shown below"
|
|
16552
16633
|
);
|
|
@@ -16675,8 +16756,11 @@ async function getEmailIdentityInfo(domain, region) {
|
|
|
16675
16756
|
dkimTokens: response.DkimAttributes?.Tokens || [],
|
|
16676
16757
|
mailFromDomain: response.MailFromAttributes?.MailFromDomain
|
|
16677
16758
|
};
|
|
16678
|
-
} catch (
|
|
16679
|
-
|
|
16759
|
+
} catch (error) {
|
|
16760
|
+
if (isAWSError(error)) {
|
|
16761
|
+
return { dkimTokens: [] };
|
|
16762
|
+
}
|
|
16763
|
+
throw error;
|
|
16680
16764
|
}
|
|
16681
16765
|
}
|
|
16682
16766
|
async function emailDestroy(options) {
|
|
@@ -16771,7 +16855,7 @@ async function emailDestroy(options) {
|
|
|
16771
16855
|
stackName,
|
|
16772
16856
|
workDir: getPulumiWorkDir()
|
|
16773
16857
|
});
|
|
16774
|
-
} catch
|
|
16858
|
+
} catch {
|
|
16775
16859
|
throw new Error("No email infrastructure found to preview");
|
|
16776
16860
|
}
|
|
16777
16861
|
const result = await previewWithResourceChanges(stack, {
|
|
@@ -16805,12 +16889,13 @@ async function emailDestroy(options) {
|
|
|
16805
16889
|
return;
|
|
16806
16890
|
} catch (error) {
|
|
16807
16891
|
progress.stop();
|
|
16808
|
-
|
|
16892
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
16893
|
+
if (msg.includes("No email infrastructure found")) {
|
|
16809
16894
|
clack18.log.warn("No email infrastructure found to preview");
|
|
16810
16895
|
process.exit(0);
|
|
16811
16896
|
}
|
|
16812
16897
|
trackError("PREVIEW_FAILED", "email destroy", { step: "preview" });
|
|
16813
|
-
throw new Error(`Preview failed: ${
|
|
16898
|
+
throw new Error(`Preview failed: ${msg}`);
|
|
16814
16899
|
}
|
|
16815
16900
|
}
|
|
16816
16901
|
if (shouldCleanDNS && hostedZone && domain && dkimTokens.length > 0) {
|
|
@@ -16826,7 +16911,8 @@ async function emailDestroy(options) {
|
|
|
16826
16911
|
);
|
|
16827
16912
|
});
|
|
16828
16913
|
} catch (error) {
|
|
16829
|
-
|
|
16914
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
16915
|
+
clack18.log.warn(`Could not delete DNS records: ${msg}`);
|
|
16830
16916
|
clack18.log.info("You may need to delete them manually from Route53");
|
|
16831
16917
|
}
|
|
16832
16918
|
}
|
|
@@ -16842,7 +16928,7 @@ async function emailDestroy(options) {
|
|
|
16842
16928
|
stackName,
|
|
16843
16929
|
workDir: getPulumiWorkDir()
|
|
16844
16930
|
});
|
|
16845
|
-
} catch
|
|
16931
|
+
} catch {
|
|
16846
16932
|
throw new Error("No email infrastructure found to destroy");
|
|
16847
16933
|
}
|
|
16848
16934
|
await withTimeout(
|
|
@@ -16857,12 +16943,13 @@ async function emailDestroy(options) {
|
|
|
16857
16943
|
);
|
|
16858
16944
|
} catch (error) {
|
|
16859
16945
|
progress.stop();
|
|
16860
|
-
|
|
16946
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
16947
|
+
if (msg.includes("No email infrastructure found")) {
|
|
16861
16948
|
clack18.log.warn("No email infrastructure found");
|
|
16862
16949
|
await deleteConnectionMetadata(identity.accountId, region);
|
|
16863
16950
|
process.exit(0);
|
|
16864
16951
|
}
|
|
16865
|
-
if (
|
|
16952
|
+
if (msg.includes("stack is currently locked")) {
|
|
16866
16953
|
trackError("STACK_LOCKED", "email destroy", { step: "destroy" });
|
|
16867
16954
|
throw errors.stackLocked();
|
|
16868
16955
|
}
|
|
@@ -16907,6 +16994,7 @@ init_client();
|
|
|
16907
16994
|
init_events();
|
|
16908
16995
|
init_dns();
|
|
16909
16996
|
init_aws();
|
|
16997
|
+
init_errors();
|
|
16910
16998
|
init_metadata();
|
|
16911
16999
|
import { Resolver as Resolver2 } from "dns/promises";
|
|
16912
17000
|
import { GetEmailIdentityCommand as GetEmailIdentityCommand2, SESv2Client as SESv2Client3 } from "@aws-sdk/client-sesv2";
|
|
@@ -16933,16 +17021,19 @@ async function verifyDomain(options) {
|
|
|
16933
17021
|
);
|
|
16934
17022
|
dkimTokens = identity.DkimAttributes?.Tokens || [];
|
|
16935
17023
|
mailFromDomain = identity.MailFromAttributes?.MailFromDomain;
|
|
16936
|
-
} catch (
|
|
16937
|
-
|
|
16938
|
-
|
|
16939
|
-
|
|
16940
|
-
|
|
17024
|
+
} catch (error) {
|
|
17025
|
+
if (isAWSNotFoundError(error)) {
|
|
17026
|
+
progress.stop();
|
|
17027
|
+
clack19.log.error(`Domain ${options.domain} not found in SES`);
|
|
17028
|
+
console.log(
|
|
17029
|
+
`
|
|
16941
17030
|
Run ${pc20.cyan(`wraps email init --domain ${options.domain}`)} to add this domain.
|
|
16942
17031
|
`
|
|
16943
|
-
|
|
16944
|
-
|
|
16945
|
-
|
|
17032
|
+
);
|
|
17033
|
+
process.exit(1);
|
|
17034
|
+
return;
|
|
17035
|
+
}
|
|
17036
|
+
throw error;
|
|
16946
17037
|
}
|
|
16947
17038
|
const resolver = new Resolver2();
|
|
16948
17039
|
resolver.setServers(["8.8.8.8", "1.1.1.1"]);
|
|
@@ -16959,12 +17050,24 @@ Run ${pc20.cyan(`wraps email init --domain ${options.domain}`)} to add this doma
|
|
|
16959
17050
|
status: found ? "verified" : "incorrect",
|
|
16960
17051
|
records
|
|
16961
17052
|
});
|
|
16962
|
-
} catch (
|
|
16963
|
-
|
|
16964
|
-
|
|
16965
|
-
|
|
16966
|
-
|
|
16967
|
-
|
|
17053
|
+
} catch (error) {
|
|
17054
|
+
const dnsClass = classifyDNSError(error);
|
|
17055
|
+
if (dnsClass === "missing") {
|
|
17056
|
+
dnsResults.push({
|
|
17057
|
+
name: dkimRecord,
|
|
17058
|
+
type: "CNAME",
|
|
17059
|
+
status: "missing"
|
|
17060
|
+
});
|
|
17061
|
+
} else if (dnsClass === "network") {
|
|
17062
|
+
dnsResults.push({
|
|
17063
|
+
name: dkimRecord,
|
|
17064
|
+
type: "CNAME",
|
|
17065
|
+
status: "missing",
|
|
17066
|
+
records: ["DNS lookup failed (network issue)"]
|
|
17067
|
+
});
|
|
17068
|
+
} else {
|
|
17069
|
+
throw error;
|
|
17070
|
+
}
|
|
16968
17071
|
}
|
|
16969
17072
|
}
|
|
16970
17073
|
try {
|
|
@@ -16977,12 +17080,24 @@ Run ${pc20.cyan(`wraps email init --domain ${options.domain}`)} to add this doma
|
|
|
16977
17080
|
status: hasAmazonSES ? "verified" : spfRecord ? "incorrect" : "missing",
|
|
16978
17081
|
records: spfRecord ? [spfRecord] : void 0
|
|
16979
17082
|
});
|
|
16980
|
-
} catch (
|
|
16981
|
-
|
|
16982
|
-
|
|
16983
|
-
|
|
16984
|
-
|
|
16985
|
-
|
|
17083
|
+
} catch (error) {
|
|
17084
|
+
const dnsClass = classifyDNSError(error);
|
|
17085
|
+
if (dnsClass === "missing") {
|
|
17086
|
+
dnsResults.push({
|
|
17087
|
+
name: options.domain,
|
|
17088
|
+
type: "TXT (SPF)",
|
|
17089
|
+
status: "missing"
|
|
17090
|
+
});
|
|
17091
|
+
} else if (dnsClass === "network") {
|
|
17092
|
+
dnsResults.push({
|
|
17093
|
+
name: options.domain,
|
|
17094
|
+
type: "TXT (SPF)",
|
|
17095
|
+
status: "missing",
|
|
17096
|
+
records: ["DNS lookup failed (network issue)"]
|
|
17097
|
+
});
|
|
17098
|
+
} else {
|
|
17099
|
+
throw error;
|
|
17100
|
+
}
|
|
16986
17101
|
}
|
|
16987
17102
|
try {
|
|
16988
17103
|
const records = await resolver.resolveTxt(`_dmarc.${options.domain}`);
|
|
@@ -16993,12 +17108,24 @@ Run ${pc20.cyan(`wraps email init --domain ${options.domain}`)} to add this doma
|
|
|
16993
17108
|
status: dmarcRecord ? "verified" : "missing",
|
|
16994
17109
|
records: dmarcRecord ? [dmarcRecord] : void 0
|
|
16995
17110
|
});
|
|
16996
|
-
} catch (
|
|
16997
|
-
|
|
16998
|
-
|
|
16999
|
-
|
|
17000
|
-
|
|
17001
|
-
|
|
17111
|
+
} catch (error) {
|
|
17112
|
+
const dnsClass = classifyDNSError(error);
|
|
17113
|
+
if (dnsClass === "missing") {
|
|
17114
|
+
dnsResults.push({
|
|
17115
|
+
name: `_dmarc.${options.domain}`,
|
|
17116
|
+
type: "TXT (DMARC)",
|
|
17117
|
+
status: "missing"
|
|
17118
|
+
});
|
|
17119
|
+
} else if (dnsClass === "network") {
|
|
17120
|
+
dnsResults.push({
|
|
17121
|
+
name: `_dmarc.${options.domain}`,
|
|
17122
|
+
type: "TXT (DMARC)",
|
|
17123
|
+
status: "missing",
|
|
17124
|
+
records: ["DNS lookup failed (network issue)"]
|
|
17125
|
+
});
|
|
17126
|
+
} else {
|
|
17127
|
+
throw error;
|
|
17128
|
+
}
|
|
17002
17129
|
}
|
|
17003
17130
|
if (mailFromDomain) {
|
|
17004
17131
|
try {
|
|
@@ -17013,12 +17140,24 @@ Run ${pc20.cyan(`wraps email init --domain ${options.domain}`)} to add this doma
|
|
|
17013
17140
|
status: hasMx ? "verified" : mxRecords.length > 0 ? "incorrect" : "missing",
|
|
17014
17141
|
records: mxRecords.map((r) => `${r.priority} ${r.exchange}`)
|
|
17015
17142
|
});
|
|
17016
|
-
} catch (
|
|
17017
|
-
|
|
17018
|
-
|
|
17019
|
-
|
|
17020
|
-
|
|
17021
|
-
|
|
17143
|
+
} catch (error) {
|
|
17144
|
+
const dnsClass = classifyDNSError(error);
|
|
17145
|
+
if (dnsClass === "missing") {
|
|
17146
|
+
dnsResults.push({
|
|
17147
|
+
name: mailFromDomain,
|
|
17148
|
+
type: "MX",
|
|
17149
|
+
status: "missing"
|
|
17150
|
+
});
|
|
17151
|
+
} else if (dnsClass === "network") {
|
|
17152
|
+
dnsResults.push({
|
|
17153
|
+
name: mailFromDomain,
|
|
17154
|
+
type: "MX",
|
|
17155
|
+
status: "missing",
|
|
17156
|
+
records: ["DNS lookup failed (network issue)"]
|
|
17157
|
+
});
|
|
17158
|
+
} else {
|
|
17159
|
+
throw error;
|
|
17160
|
+
}
|
|
17022
17161
|
}
|
|
17023
17162
|
try {
|
|
17024
17163
|
const records = await resolver.resolveTxt(mailFromDomain);
|
|
@@ -17030,12 +17169,24 @@ Run ${pc20.cyan(`wraps email init --domain ${options.domain}`)} to add this doma
|
|
|
17030
17169
|
status: hasAmazonSES ? "verified" : spfRecord ? "incorrect" : "missing",
|
|
17031
17170
|
records: spfRecord ? [spfRecord] : void 0
|
|
17032
17171
|
});
|
|
17033
|
-
} catch (
|
|
17034
|
-
|
|
17035
|
-
|
|
17036
|
-
|
|
17037
|
-
|
|
17038
|
-
|
|
17172
|
+
} catch (error) {
|
|
17173
|
+
const dnsClass = classifyDNSError(error);
|
|
17174
|
+
if (dnsClass === "missing") {
|
|
17175
|
+
dnsResults.push({
|
|
17176
|
+
name: mailFromDomain,
|
|
17177
|
+
type: "TXT (SPF)",
|
|
17178
|
+
status: "missing"
|
|
17179
|
+
});
|
|
17180
|
+
} else if (dnsClass === "network") {
|
|
17181
|
+
dnsResults.push({
|
|
17182
|
+
name: mailFromDomain,
|
|
17183
|
+
type: "TXT (SPF)",
|
|
17184
|
+
status: "missing",
|
|
17185
|
+
records: ["DNS lookup failed (network issue)"]
|
|
17186
|
+
});
|
|
17187
|
+
} else {
|
|
17188
|
+
throw error;
|
|
17189
|
+
}
|
|
17039
17190
|
}
|
|
17040
17191
|
}
|
|
17041
17192
|
progress.stop();
|
|
@@ -17177,7 +17328,7 @@ Run ${pc20.cyan(`wraps email domains verify --domain ${domain}`)} to check verif
|
|
|
17177
17328
|
);
|
|
17178
17329
|
return;
|
|
17179
17330
|
} catch (error) {
|
|
17180
|
-
if (error
|
|
17331
|
+
if (!isAWSNotFoundError(error)) {
|
|
17181
17332
|
throw error;
|
|
17182
17333
|
}
|
|
17183
17334
|
}
|
|
@@ -17490,7 +17641,7 @@ ${pc20.bold("DNS Records to add:")}
|
|
|
17490
17641
|
} catch (error) {
|
|
17491
17642
|
progress.stop();
|
|
17492
17643
|
trackCommand("email:domains:get-dkim", { success: false });
|
|
17493
|
-
if (error
|
|
17644
|
+
if (isAWSNotFoundError(error)) {
|
|
17494
17645
|
clack19.log.error(`Domain ${options.domain} not found in SES`);
|
|
17495
17646
|
console.log(
|
|
17496
17647
|
`
|
|
@@ -17571,7 +17722,7 @@ Use ${pc20.cyan(`wraps email domains remove --domain ${options.domain} --force`)
|
|
|
17571
17722
|
} catch (error) {
|
|
17572
17723
|
progress.stop();
|
|
17573
17724
|
trackCommand("email:domains:remove", { success: false });
|
|
17574
|
-
if (error
|
|
17725
|
+
if (isAWSNotFoundError(error)) {
|
|
17575
17726
|
clack19.log.error(`Domain ${options.domain} not found in SES`);
|
|
17576
17727
|
process.exit(1);
|
|
17577
17728
|
return;
|
|
@@ -18670,10 +18821,11 @@ ${pc22.yellow(pc22.bold("Configuration Warnings:"))}`);
|
|
|
18670
18821
|
return;
|
|
18671
18822
|
} catch (error) {
|
|
18672
18823
|
trackError("PREVIEW_FAILED", "email:init", { step: "preview" });
|
|
18673
|
-
|
|
18824
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
18825
|
+
if (msg.includes("stack is currently locked")) {
|
|
18674
18826
|
throw errors.stackLocked();
|
|
18675
18827
|
}
|
|
18676
|
-
throw new Error(`Preview failed: ${
|
|
18828
|
+
throw new Error(`Preview failed: ${msg}`);
|
|
18677
18829
|
}
|
|
18678
18830
|
}
|
|
18679
18831
|
let outputs;
|
|
@@ -18744,13 +18896,14 @@ ${pc22.yellow(pc22.bold("Configuration Warnings:"))}`);
|
|
|
18744
18896
|
}
|
|
18745
18897
|
);
|
|
18746
18898
|
} catch (error) {
|
|
18899
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
18747
18900
|
trackServiceInit("email", false, {
|
|
18748
18901
|
preset,
|
|
18749
18902
|
provider,
|
|
18750
18903
|
region,
|
|
18751
18904
|
duration_ms: Date.now() - startTime
|
|
18752
18905
|
});
|
|
18753
|
-
if (
|
|
18906
|
+
if (msg.includes("stack is currently locked")) {
|
|
18754
18907
|
trackError("STACK_LOCKED", "email:init", { step: "deploy" });
|
|
18755
18908
|
throw errors.stackLocked();
|
|
18756
18909
|
}
|
|
@@ -18781,7 +18934,7 @@ ${pc22.yellow(pc22.bold("Configuration Warnings:"))}`);
|
|
|
18781
18934
|
}
|
|
18782
18935
|
}
|
|
18783
18936
|
trackError("DEPLOYMENT_FAILED", "email:init", { step: "deploy" });
|
|
18784
|
-
throw new Error(`Pulumi deployment failed: ${
|
|
18937
|
+
throw new Error(`Pulumi deployment failed: ${msg}`);
|
|
18785
18938
|
}
|
|
18786
18939
|
if (metadata.services.email) {
|
|
18787
18940
|
metadata.services.email.pulumiStackName = `wraps-${identity.accountId}-${region}`;
|
|
@@ -18879,7 +19032,8 @@ ${pc22.yellow(pc22.bold("Configuration Warnings:"))}`);
|
|
|
18879
19032
|
}
|
|
18880
19033
|
} catch (error) {
|
|
18881
19034
|
progress.stop();
|
|
18882
|
-
|
|
19035
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
19036
|
+
clack21.log.warn(`Could not manage DNS records: ${msg}`);
|
|
18883
19037
|
}
|
|
18884
19038
|
} else {
|
|
18885
19039
|
const recordData = {
|
|
@@ -19114,7 +19268,8 @@ ${pc23.bold("The following Wraps resources will be removed:")}
|
|
|
19114
19268
|
return;
|
|
19115
19269
|
} catch (error) {
|
|
19116
19270
|
trackError("PREVIEW_FAILED", "email:restore", { step: "preview" });
|
|
19117
|
-
|
|
19271
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
19272
|
+
throw new Error(`Preview failed: ${msg}`);
|
|
19118
19273
|
}
|
|
19119
19274
|
}
|
|
19120
19275
|
return;
|
|
@@ -19149,7 +19304,8 @@ ${pc23.bold("The following Wraps resources will be removed:")}
|
|
|
19149
19304
|
);
|
|
19150
19305
|
} catch (error) {
|
|
19151
19306
|
trackError("DESTROY_FAILED", "email:restore", { step: "destroy" });
|
|
19152
|
-
|
|
19307
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
19308
|
+
throw new Error(`Failed to destroy Pulumi stack: ${msg}`);
|
|
19153
19309
|
}
|
|
19154
19310
|
});
|
|
19155
19311
|
}
|
|
@@ -19176,6 +19332,7 @@ init_esm_shims();
|
|
|
19176
19332
|
init_client();
|
|
19177
19333
|
init_events();
|
|
19178
19334
|
init_aws();
|
|
19335
|
+
init_errors();
|
|
19179
19336
|
init_fs();
|
|
19180
19337
|
init_metadata();
|
|
19181
19338
|
import * as clack23 from "@clack/prompts";
|
|
@@ -19220,15 +19377,19 @@ async function emailStatus(options) {
|
|
|
19220
19377
|
workDir: getPulumiWorkDir()
|
|
19221
19378
|
});
|
|
19222
19379
|
stackOutputs = await stack.outputs();
|
|
19223
|
-
} catch (
|
|
19224
|
-
|
|
19225
|
-
|
|
19226
|
-
|
|
19227
|
-
|
|
19380
|
+
} catch (error) {
|
|
19381
|
+
if (error instanceof Error && (error.message.includes("no stack named") || error.message.includes("not found"))) {
|
|
19382
|
+
progress.stop();
|
|
19383
|
+
clack23.log.error("No email infrastructure found");
|
|
19384
|
+
console.log(
|
|
19385
|
+
`
|
|
19228
19386
|
Run ${pc24.cyan("wraps email init")} to deploy email infrastructure.
|
|
19229
19387
|
`
|
|
19230
|
-
|
|
19231
|
-
|
|
19388
|
+
);
|
|
19389
|
+
process.exit(1);
|
|
19390
|
+
return;
|
|
19391
|
+
}
|
|
19392
|
+
throw error;
|
|
19232
19393
|
}
|
|
19233
19394
|
const domains = await listSESDomains(region);
|
|
19234
19395
|
const { SESv2Client: SESv2Client6, GetEmailIdentityCommand: GetEmailIdentityCommand5 } = await import("@aws-sdk/client-sesv2");
|
|
@@ -19253,17 +19414,20 @@ Run ${pc24.cyan("wraps email init")} to deploy email infrastructure.
|
|
|
19253
19414
|
isPrimary: tracked?.isPrimary,
|
|
19254
19415
|
purpose: tracked?.purpose
|
|
19255
19416
|
};
|
|
19256
|
-
} catch (
|
|
19257
|
-
|
|
19258
|
-
|
|
19259
|
-
|
|
19260
|
-
|
|
19261
|
-
|
|
19262
|
-
|
|
19263
|
-
|
|
19264
|
-
|
|
19265
|
-
|
|
19266
|
-
|
|
19417
|
+
} catch (error) {
|
|
19418
|
+
if (isAWSError(error)) {
|
|
19419
|
+
return {
|
|
19420
|
+
domain: d.domain,
|
|
19421
|
+
status: d.verified ? "verified" : "pending",
|
|
19422
|
+
dkimTokens: void 0,
|
|
19423
|
+
mailFromDomain: void 0,
|
|
19424
|
+
mailFromStatus: void 0,
|
|
19425
|
+
managed: tracked?.managed,
|
|
19426
|
+
isPrimary: tracked?.isPrimary,
|
|
19427
|
+
purpose: tracked?.purpose
|
|
19428
|
+
};
|
|
19429
|
+
}
|
|
19430
|
+
throw error;
|
|
19267
19431
|
}
|
|
19268
19432
|
})
|
|
19269
19433
|
);
|
|
@@ -20686,12 +20850,19 @@ ${pc28.bold("Current Configuration:")}
|
|
|
20686
20850
|
if (config2.domain) {
|
|
20687
20851
|
console.log(` Sending Domain: ${pc28.cyan(config2.domain)}`);
|
|
20688
20852
|
}
|
|
20853
|
+
const hasHttpsTrackingPending = config2.tracking?.httpsEnabled && config2.tracking?.customRedirectDomain;
|
|
20689
20854
|
if (config2.tracking?.enabled) {
|
|
20690
20855
|
console.log(` ${pc28.green("\u2713")} Open & Click Tracking`);
|
|
20691
20856
|
if (config2.tracking.customRedirectDomain) {
|
|
20692
|
-
|
|
20693
|
-
|
|
20694
|
-
|
|
20857
|
+
if (hasHttpsTrackingPending) {
|
|
20858
|
+
console.log(
|
|
20859
|
+
` ${pc28.dim("\u2514\u2500")} Custom domain: ${pc28.cyan(config2.tracking.customRedirectDomain)} ${pc28.yellow("(HTTPS pending - certificate validation required)")}`
|
|
20860
|
+
);
|
|
20861
|
+
} else {
|
|
20862
|
+
console.log(
|
|
20863
|
+
` ${pc28.dim("\u2514\u2500")} Custom domain: ${pc28.cyan(config2.tracking.customRedirectDomain)}`
|
|
20864
|
+
);
|
|
20865
|
+
}
|
|
20695
20866
|
}
|
|
20696
20867
|
}
|
|
20697
20868
|
if (config2.suppressionList?.enabled) {
|
|
@@ -20747,60 +20918,69 @@ ${pc28.bold("Current Configuration:")}
|
|
|
20747
20918
|
Estimated Cost: ${pc28.cyan(`~${formatCost(currentCostData.total.monthly)}/mo`)}`
|
|
20748
20919
|
);
|
|
20749
20920
|
console.log("");
|
|
20921
|
+
const upgradeOptions = [];
|
|
20922
|
+
if (hasHttpsTrackingPending) {
|
|
20923
|
+
upgradeOptions.push({
|
|
20924
|
+
value: "finish-tracking-domain",
|
|
20925
|
+
label: "Finish setting up custom tracking domain",
|
|
20926
|
+
hint: `Complete HTTPS setup for ${config2.tracking.customRedirectDomain}`
|
|
20927
|
+
});
|
|
20928
|
+
}
|
|
20929
|
+
upgradeOptions.push(
|
|
20930
|
+
{
|
|
20931
|
+
value: "preset",
|
|
20932
|
+
label: "Upgrade to a different preset",
|
|
20933
|
+
hint: "Starter \u2192 Production \u2192 Enterprise"
|
|
20934
|
+
},
|
|
20935
|
+
{
|
|
20936
|
+
value: "archiving",
|
|
20937
|
+
label: config2.emailArchiving?.enabled ? "Change email archiving settings" : "Enable email archiving",
|
|
20938
|
+
hint: config2.emailArchiving?.enabled ? "Update retention or disable" : "Store full email content with HTML"
|
|
20939
|
+
},
|
|
20940
|
+
{
|
|
20941
|
+
value: "tracking-domain",
|
|
20942
|
+
label: "Add/change custom tracking domain",
|
|
20943
|
+
hint: "Use your own domain for email links"
|
|
20944
|
+
},
|
|
20945
|
+
{
|
|
20946
|
+
value: "retention",
|
|
20947
|
+
label: "Change email history retention",
|
|
20948
|
+
hint: "7 days, 30 days, 90 days, 6 months, 1 year, 18 months"
|
|
20949
|
+
},
|
|
20950
|
+
{
|
|
20951
|
+
value: "events",
|
|
20952
|
+
label: "Customize tracked event types",
|
|
20953
|
+
hint: "Choose which SES events to track"
|
|
20954
|
+
},
|
|
20955
|
+
{
|
|
20956
|
+
value: "dedicated-ip",
|
|
20957
|
+
label: "Enable dedicated IP address",
|
|
20958
|
+
hint: "Requires 100k+ emails/day ($50-100/mo)"
|
|
20959
|
+
},
|
|
20960
|
+
{
|
|
20961
|
+
value: "alerts",
|
|
20962
|
+
label: config2.alerts?.enabled ? "Manage reputation alerts" : "Enable reputation alerts",
|
|
20963
|
+
hint: config2.alerts?.enabled ? "Update thresholds or notification settings" : "Get notified before AWS suspends your account"
|
|
20964
|
+
},
|
|
20965
|
+
{
|
|
20966
|
+
value: "custom",
|
|
20967
|
+
label: "Custom configuration",
|
|
20968
|
+
hint: "Modify multiple settings at once"
|
|
20969
|
+
},
|
|
20970
|
+
{
|
|
20971
|
+
value: "wraps-dashboard",
|
|
20972
|
+
label: metadata.services.email?.webhookSecret ? "Manage Wraps Dashboard connection" : "Connect to Wraps Dashboard",
|
|
20973
|
+
hint: metadata.services.email?.webhookSecret ? "Regenerate secret or disconnect" : "Send events to dashboard for analytics"
|
|
20974
|
+
},
|
|
20975
|
+
{
|
|
20976
|
+
value: "smtp-credentials",
|
|
20977
|
+
label: metadata.services.email?.smtpCredentials?.enabled ? "Manage SMTP credentials" : "Enable SMTP credentials",
|
|
20978
|
+
hint: metadata.services.email?.smtpCredentials?.enabled ? "Rotate or disable credentials" : "Generate credentials for PHP, WordPress, etc."
|
|
20979
|
+
}
|
|
20980
|
+
);
|
|
20750
20981
|
upgradeAction = await clack27.select({
|
|
20751
20982
|
message: "What would you like to do?",
|
|
20752
|
-
options:
|
|
20753
|
-
{
|
|
20754
|
-
value: "preset",
|
|
20755
|
-
label: "Upgrade to a different preset",
|
|
20756
|
-
hint: "Starter \u2192 Production \u2192 Enterprise"
|
|
20757
|
-
},
|
|
20758
|
-
{
|
|
20759
|
-
value: "archiving",
|
|
20760
|
-
label: config2.emailArchiving?.enabled ? "Change email archiving settings" : "Enable email archiving",
|
|
20761
|
-
hint: config2.emailArchiving?.enabled ? "Update retention or disable" : "Store full email content with HTML"
|
|
20762
|
-
},
|
|
20763
|
-
{
|
|
20764
|
-
value: "tracking-domain",
|
|
20765
|
-
label: "Add/change custom tracking domain",
|
|
20766
|
-
hint: "Use your own domain for email links"
|
|
20767
|
-
},
|
|
20768
|
-
{
|
|
20769
|
-
value: "retention",
|
|
20770
|
-
label: "Change email history retention",
|
|
20771
|
-
hint: "7 days, 30 days, 90 days, 6 months, 1 year, 18 months"
|
|
20772
|
-
},
|
|
20773
|
-
{
|
|
20774
|
-
value: "events",
|
|
20775
|
-
label: "Customize tracked event types",
|
|
20776
|
-
hint: "Choose which SES events to track"
|
|
20777
|
-
},
|
|
20778
|
-
{
|
|
20779
|
-
value: "dedicated-ip",
|
|
20780
|
-
label: "Enable dedicated IP address",
|
|
20781
|
-
hint: "Requires 100k+ emails/day ($50-100/mo)"
|
|
20782
|
-
},
|
|
20783
|
-
{
|
|
20784
|
-
value: "alerts",
|
|
20785
|
-
label: config2.alerts?.enabled ? "Manage reputation alerts" : "Enable reputation alerts",
|
|
20786
|
-
hint: config2.alerts?.enabled ? "Update thresholds or notification settings" : "Get notified before AWS suspends your account"
|
|
20787
|
-
},
|
|
20788
|
-
{
|
|
20789
|
-
value: "custom",
|
|
20790
|
-
label: "Custom configuration",
|
|
20791
|
-
hint: "Modify multiple settings at once"
|
|
20792
|
-
},
|
|
20793
|
-
{
|
|
20794
|
-
value: "wraps-dashboard",
|
|
20795
|
-
label: metadata.services.email?.webhookSecret ? "Manage Wraps Dashboard connection" : "Connect to Wraps Dashboard",
|
|
20796
|
-
hint: metadata.services.email?.webhookSecret ? "Regenerate secret or disconnect" : "Send events to dashboard for analytics"
|
|
20797
|
-
},
|
|
20798
|
-
{
|
|
20799
|
-
value: "smtp-credentials",
|
|
20800
|
-
label: metadata.services.email?.smtpCredentials?.enabled ? "Manage SMTP credentials" : "Enable SMTP credentials",
|
|
20801
|
-
hint: metadata.services.email?.smtpCredentials?.enabled ? "Rotate or disable credentials" : "Generate credentials for PHP, WordPress, etc."
|
|
20802
|
-
}
|
|
20803
|
-
]
|
|
20983
|
+
options: upgradeOptions
|
|
20804
20984
|
});
|
|
20805
20985
|
if (clack27.isCancel(upgradeAction)) {
|
|
20806
20986
|
clack27.cancel("Upgrade cancelled.");
|
|
@@ -20809,6 +20989,14 @@ ${pc28.bold("Current Configuration:")}
|
|
|
20809
20989
|
let updatedConfig = { ...config2 };
|
|
20810
20990
|
let newPreset = metadata.services.email?.preset;
|
|
20811
20991
|
switch (upgradeAction) {
|
|
20992
|
+
case "finish-tracking-domain": {
|
|
20993
|
+
clack27.log.info(
|
|
20994
|
+
`Checking certificate status for ${pc28.cyan(config2.tracking.customRedirectDomain)}...`
|
|
20995
|
+
);
|
|
20996
|
+
updatedConfig = { ...config2 };
|
|
20997
|
+
newPreset = metadata.services.email?.preset;
|
|
20998
|
+
break;
|
|
20999
|
+
}
|
|
20812
21000
|
case "preset": {
|
|
20813
21001
|
const presets = getAllPresetInfo().filter(
|
|
20814
21002
|
(p) => p.name.toLowerCase() !== "custom"
|
|
@@ -21863,11 +22051,12 @@ ${pc28.bold("Cost Impact:")}`);
|
|
|
21863
22051
|
});
|
|
21864
22052
|
return;
|
|
21865
22053
|
} catch (error) {
|
|
22054
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
21866
22055
|
trackError("PREVIEW_FAILED", "email:upgrade", { step: "preview" });
|
|
21867
|
-
if (
|
|
22056
|
+
if (msg.includes("stack is currently locked")) {
|
|
21868
22057
|
throw errors.stackLocked();
|
|
21869
22058
|
}
|
|
21870
|
-
throw new Error(`Preview failed: ${
|
|
22059
|
+
throw new Error(`Preview failed: ${msg}`);
|
|
21871
22060
|
}
|
|
21872
22061
|
}
|
|
21873
22062
|
let outputs;
|
|
@@ -21949,18 +22138,19 @@ ${pc28.bold("Cost Impact:")}`);
|
|
|
21949
22138
|
}
|
|
21950
22139
|
);
|
|
21951
22140
|
} catch (error) {
|
|
22141
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
21952
22142
|
trackServiceUpgrade("email", {
|
|
21953
22143
|
from_preset: metadata.services.email?.preset,
|
|
21954
22144
|
to_preset: newPreset,
|
|
21955
22145
|
action: typeof upgradeAction === "string" ? upgradeAction : void 0,
|
|
21956
22146
|
duration_ms: Date.now() - startTime
|
|
21957
22147
|
});
|
|
21958
|
-
if (
|
|
22148
|
+
if (msg.includes("stack is currently locked")) {
|
|
21959
22149
|
trackError("STACK_LOCKED", "email:upgrade", { step: "deploy" });
|
|
21960
22150
|
throw errors.stackLocked();
|
|
21961
22151
|
}
|
|
21962
22152
|
trackError("UPGRADE_FAILED", "email:upgrade", { step: "deploy" });
|
|
21963
|
-
throw new Error(`Pulumi upgrade failed: ${
|
|
22153
|
+
throw new Error(`Pulumi upgrade failed: ${msg}`);
|
|
21964
22154
|
}
|
|
21965
22155
|
let dnsAutoCreated = false;
|
|
21966
22156
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
@@ -22018,9 +22208,8 @@ ${pc28.bold("Cost Impact:")}`);
|
|
|
22018
22208
|
);
|
|
22019
22209
|
}
|
|
22020
22210
|
} catch (error) {
|
|
22021
|
-
|
|
22022
|
-
|
|
22023
|
-
);
|
|
22211
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
22212
|
+
progress.fail(`Failed to create DNS records automatically: ${msg}`);
|
|
22024
22213
|
progress.info(
|
|
22025
22214
|
"You can manually add the required DNS records shown below"
|
|
22026
22215
|
);
|
|
@@ -22159,9 +22348,8 @@ ${pc28.bold("Add these DNS records to your DNS provider:")}
|
|
|
22159
22348
|
}
|
|
22160
22349
|
}
|
|
22161
22350
|
} catch (error) {
|
|
22162
|
-
|
|
22163
|
-
|
|
22164
|
-
);
|
|
22351
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
22352
|
+
progress.fail(`Failed to create ACM validation record: ${msg}`);
|
|
22165
22353
|
}
|
|
22166
22354
|
}
|
|
22167
22355
|
}
|
|
@@ -24387,10 +24575,10 @@ var WRAPS_PLATFORM_ACCOUNT_ID = "905130073023";
|
|
|
24387
24575
|
async function updatePlatformRole(metadata, progress, externalId) {
|
|
24388
24576
|
const roleName = "wraps-console-access-role";
|
|
24389
24577
|
const iam10 = new IAMClient2({ region: "us-east-1" });
|
|
24390
|
-
let
|
|
24578
|
+
let roleExists2 = false;
|
|
24391
24579
|
try {
|
|
24392
24580
|
await iam10.send(new GetRoleCommand({ RoleName: roleName }));
|
|
24393
|
-
|
|
24581
|
+
roleExists2 = true;
|
|
24394
24582
|
} catch (error) {
|
|
24395
24583
|
const isNotFound = error instanceof Error && (error.name === "NoSuchEntityException" || error.name === "NoSuchEntity" || error.message.includes("NoSuchEntity"));
|
|
24396
24584
|
if (!isNotFound) {
|
|
@@ -24400,7 +24588,7 @@ async function updatePlatformRole(metadata, progress, externalId) {
|
|
|
24400
24588
|
const emailConfig = metadata.services.email?.config;
|
|
24401
24589
|
const smsConfig = metadata.services.sms?.config;
|
|
24402
24590
|
const policy = buildConsolePolicyDocument(emailConfig, smsConfig);
|
|
24403
|
-
if (
|
|
24591
|
+
if (roleExists2) {
|
|
24404
24592
|
await progress.execute("Updating platform access role", async () => {
|
|
24405
24593
|
await iam10.send(
|
|
24406
24594
|
new PutRolePolicyCommand({
|
|
@@ -24858,17 +25046,17 @@ Run ${pc33.cyan("wraps email init")} or ${pc33.cyan("wraps sms init")} first.
|
|
|
24858
25046
|
}
|
|
24859
25047
|
const roleName = "wraps-console-access-role";
|
|
24860
25048
|
const iam10 = new IAMClient2({ region: "us-east-1" });
|
|
24861
|
-
let
|
|
25049
|
+
let roleExists2 = false;
|
|
24862
25050
|
try {
|
|
24863
25051
|
await iam10.send(new GetRoleCommand({ RoleName: roleName }));
|
|
24864
|
-
|
|
25052
|
+
roleExists2 = true;
|
|
24865
25053
|
} catch (error) {
|
|
24866
25054
|
const isNotFound = error instanceof Error && (error.name === "NoSuchEntityException" || error.name === "NoSuchEntity" || error.message.includes("NoSuchEntity"));
|
|
24867
25055
|
if (!isNotFound) {
|
|
24868
25056
|
throw error;
|
|
24869
25057
|
}
|
|
24870
25058
|
}
|
|
24871
|
-
if (
|
|
25059
|
+
if (roleExists2) {
|
|
24872
25060
|
const emailConfig = metadata.services.email?.config;
|
|
24873
25061
|
const smsConfig = metadata.services.sms?.config;
|
|
24874
25062
|
const policy = buildConsolePolicyDocument(emailConfig, smsConfig);
|
|
@@ -25025,10 +25213,10 @@ Run ${pc35.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
25025
25213
|
}
|
|
25026
25214
|
const roleName = "wraps-console-access-role";
|
|
25027
25215
|
const iam10 = new IAMClient3({ region: "us-east-1" });
|
|
25028
|
-
let
|
|
25216
|
+
let roleExists2 = false;
|
|
25029
25217
|
try {
|
|
25030
25218
|
await iam10.send(new GetRoleCommand2({ RoleName: roleName }));
|
|
25031
|
-
|
|
25219
|
+
roleExists2 = true;
|
|
25032
25220
|
} catch (error) {
|
|
25033
25221
|
const isNotFound = error instanceof Error && (error.name === "NoSuchEntityException" || error.name === "NoSuchEntity" || error.message.includes("NoSuchEntity"));
|
|
25034
25222
|
if (!isNotFound) {
|
|
@@ -25036,7 +25224,7 @@ Run ${pc35.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
25036
25224
|
}
|
|
25037
25225
|
}
|
|
25038
25226
|
const externalId = metadata.platform?.externalId;
|
|
25039
|
-
if (!(
|
|
25227
|
+
if (!(roleExists2 || externalId)) {
|
|
25040
25228
|
progress.stop();
|
|
25041
25229
|
log31.warn(`IAM role ${pc35.cyan(roleName)} does not exist`);
|
|
25042
25230
|
console.log(
|
|
@@ -25048,7 +25236,7 @@ Run ${pc35.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
25048
25236
|
);
|
|
25049
25237
|
process.exit(0);
|
|
25050
25238
|
}
|
|
25051
|
-
if (
|
|
25239
|
+
if (roleExists2) {
|
|
25052
25240
|
progress.info(`Found IAM role: ${pc35.cyan(roleName)}`);
|
|
25053
25241
|
} else {
|
|
25054
25242
|
progress.info(
|
|
@@ -25057,7 +25245,7 @@ Run ${pc35.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
25057
25245
|
}
|
|
25058
25246
|
if (!options.force) {
|
|
25059
25247
|
progress.stop();
|
|
25060
|
-
const actionLabel =
|
|
25248
|
+
const actionLabel = roleExists2 ? "Update" : "Create";
|
|
25061
25249
|
const shouldContinue = await confirm13({
|
|
25062
25250
|
message: `${actionLabel} IAM role ${pc35.cyan(roleName)} with latest permissions?`,
|
|
25063
25251
|
initialValue: true
|
|
@@ -25076,7 +25264,7 @@ Run ${pc35.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
25076
25264
|
const smsEnabled = !!smsConfig;
|
|
25077
25265
|
const smsSendingEnabled = smsConfig && smsConfig.sendingEnabled !== false;
|
|
25078
25266
|
const smsEventTracking = smsConfig?.eventTracking;
|
|
25079
|
-
if (!
|
|
25267
|
+
if (!roleExists2 && externalId) {
|
|
25080
25268
|
const WRAPS_PLATFORM_ACCOUNT_ID2 = "905130073023";
|
|
25081
25269
|
await progress.execute("Creating IAM role", async () => {
|
|
25082
25270
|
const trustPolicy = {
|
|
@@ -25129,7 +25317,7 @@ Run ${pc35.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
25129
25317
|
});
|
|
25130
25318
|
}
|
|
25131
25319
|
progress.stop();
|
|
25132
|
-
const actionVerb =
|
|
25320
|
+
const actionVerb = roleExists2 ? "updated" : "created";
|
|
25133
25321
|
trackCommand("platform:update-role", {
|
|
25134
25322
|
success: true,
|
|
25135
25323
|
duration_ms: Date.now() - startTime,
|
|
@@ -28313,38 +28501,9 @@ import pc39 from "picocolors";
|
|
|
28313
28501
|
|
|
28314
28502
|
// src/infrastructure/sms-stack.ts
|
|
28315
28503
|
init_esm_shims();
|
|
28504
|
+
init_resource_checks();
|
|
28316
28505
|
import * as aws19 from "@pulumi/aws";
|
|
28317
28506
|
import * as pulumi24 from "@pulumi/pulumi";
|
|
28318
|
-
async function roleExists3(roleName) {
|
|
28319
|
-
try {
|
|
28320
|
-
const { IAMClient: IAMClient4, GetRoleCommand: GetRoleCommand3 } = await import("@aws-sdk/client-iam");
|
|
28321
|
-
const iam10 = new IAMClient4({
|
|
28322
|
-
region: process.env.AWS_REGION || "us-east-1"
|
|
28323
|
-
});
|
|
28324
|
-
await iam10.send(new GetRoleCommand3({ RoleName: roleName }));
|
|
28325
|
-
return true;
|
|
28326
|
-
} catch (error) {
|
|
28327
|
-
if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity") {
|
|
28328
|
-
return false;
|
|
28329
|
-
}
|
|
28330
|
-
return false;
|
|
28331
|
-
}
|
|
28332
|
-
}
|
|
28333
|
-
async function tableExists2(tableName) {
|
|
28334
|
-
try {
|
|
28335
|
-
const { DynamoDBClient: DynamoDBClient6, DescribeTableCommand: DescribeTableCommand2 } = await import("@aws-sdk/client-dynamodb");
|
|
28336
|
-
const dynamodb3 = new DynamoDBClient6({
|
|
28337
|
-
region: process.env.AWS_REGION || "us-east-1"
|
|
28338
|
-
});
|
|
28339
|
-
await dynamodb3.send(new DescribeTableCommand2({ TableName: tableName }));
|
|
28340
|
-
return true;
|
|
28341
|
-
} catch (error) {
|
|
28342
|
-
if (error instanceof Error && "name" in error && error.name === "ResourceNotFoundException") {
|
|
28343
|
-
return false;
|
|
28344
|
-
}
|
|
28345
|
-
return false;
|
|
28346
|
-
}
|
|
28347
|
-
}
|
|
28348
28507
|
async function createSMSIAMRole(config2) {
|
|
28349
28508
|
let assumeRolePolicy;
|
|
28350
28509
|
if (config2.provider === "vercel" && config2.oidcProvider) {
|
|
@@ -28390,7 +28549,7 @@ async function createSMSIAMRole(config2) {
|
|
|
28390
28549
|
throw new Error("Other providers not yet implemented");
|
|
28391
28550
|
}
|
|
28392
28551
|
const roleName = "wraps-sms-role";
|
|
28393
|
-
const exists = await
|
|
28552
|
+
const exists = await roleExists(roleName);
|
|
28394
28553
|
const role = exists ? new aws19.iam.Role(
|
|
28395
28554
|
roleName,
|
|
28396
28555
|
{
|
|
@@ -28608,20 +28767,6 @@ async function createSMSPhoneNumber(phoneNumberType, optOutList) {
|
|
|
28608
28767
|
}
|
|
28609
28768
|
);
|
|
28610
28769
|
}
|
|
28611
|
-
async function sqsQueueExists(queueName) {
|
|
28612
|
-
try {
|
|
28613
|
-
const { SQSClient, GetQueueUrlCommand } = await import("@aws-sdk/client-sqs");
|
|
28614
|
-
const sqs5 = new SQSClient({
|
|
28615
|
-
region: process.env.AWS_REGION || "us-east-1"
|
|
28616
|
-
});
|
|
28617
|
-
const response = await sqs5.send(
|
|
28618
|
-
new GetQueueUrlCommand({ QueueName: queueName })
|
|
28619
|
-
);
|
|
28620
|
-
return response.QueueUrl || null;
|
|
28621
|
-
} catch {
|
|
28622
|
-
return null;
|
|
28623
|
-
}
|
|
28624
|
-
}
|
|
28625
28770
|
async function createSMSSQSResources() {
|
|
28626
28771
|
const dlqName = "wraps-sms-events-dlq";
|
|
28627
28772
|
const queueName = "wraps-sms-events";
|
|
@@ -28671,30 +28816,6 @@ async function createSMSSQSResources() {
|
|
|
28671
28816
|
});
|
|
28672
28817
|
return { queue, dlq };
|
|
28673
28818
|
}
|
|
28674
|
-
async function snsTopicExists(topicName) {
|
|
28675
|
-
try {
|
|
28676
|
-
const { SNSClient: SNSClient2, ListTopicsCommand: ListTopicsCommand2 } = await import("@aws-sdk/client-sns");
|
|
28677
|
-
const sns3 = new SNSClient2({
|
|
28678
|
-
region: process.env.AWS_REGION || "us-east-1"
|
|
28679
|
-
});
|
|
28680
|
-
let nextToken;
|
|
28681
|
-
do {
|
|
28682
|
-
const response = await sns3.send(
|
|
28683
|
-
new ListTopicsCommand2({ NextToken: nextToken })
|
|
28684
|
-
);
|
|
28685
|
-
const found = response.Topics?.find(
|
|
28686
|
-
(t) => t.TopicArn?.endsWith(`:${topicName}`)
|
|
28687
|
-
);
|
|
28688
|
-
if (found?.TopicArn) {
|
|
28689
|
-
return found.TopicArn;
|
|
28690
|
-
}
|
|
28691
|
-
nextToken = response.NextToken;
|
|
28692
|
-
} while (nextToken);
|
|
28693
|
-
return null;
|
|
28694
|
-
} catch {
|
|
28695
|
-
return null;
|
|
28696
|
-
}
|
|
28697
|
-
}
|
|
28698
28819
|
async function createSMSSNSResources(config2) {
|
|
28699
28820
|
const topicName = "wraps-sms-events";
|
|
28700
28821
|
const topicArn = await snsTopicExists(topicName);
|
|
@@ -28762,7 +28883,7 @@ async function createSMSSNSResources(config2) {
|
|
|
28762
28883
|
}
|
|
28763
28884
|
async function createSMSDynamoDBTable() {
|
|
28764
28885
|
const tableName = "wraps-sms-history";
|
|
28765
|
-
const exists = await
|
|
28886
|
+
const exists = await tableExists(tableName);
|
|
28766
28887
|
const tableConfig = {
|
|
28767
28888
|
name: tableName,
|
|
28768
28889
|
billingMode: "PAY_PER_REQUEST",
|
|
@@ -31698,18 +31819,19 @@ ${pc45.bold("Cost Impact:")}`);
|
|
|
31698
31819
|
});
|
|
31699
31820
|
}
|
|
31700
31821
|
} catch (error) {
|
|
31822
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
31701
31823
|
trackServiceUpgrade("sms", {
|
|
31702
31824
|
from_preset: metadata.services.sms?.preset,
|
|
31703
31825
|
to_preset: newPreset,
|
|
31704
31826
|
action: typeof upgradeAction === "string" ? upgradeAction : void 0,
|
|
31705
31827
|
duration_ms: Date.now() - startTime
|
|
31706
31828
|
});
|
|
31707
|
-
if (
|
|
31829
|
+
if (msg.includes("stack is currently locked")) {
|
|
31708
31830
|
trackError("STACK_LOCKED", "sms:upgrade", { step: "deploy" });
|
|
31709
31831
|
throw errors.stackLocked();
|
|
31710
31832
|
}
|
|
31711
31833
|
trackError("UPGRADE_FAILED", "sms:upgrade", { step: "deploy" });
|
|
31712
|
-
throw new Error(`SMS upgrade failed: ${
|
|
31834
|
+
throw new Error(`SMS upgrade failed: ${msg}`);
|
|
31713
31835
|
}
|
|
31714
31836
|
updateServiceConfig(metadata, "sms", updatedConfig);
|
|
31715
31837
|
if (metadata.services.sms) {
|