@wraps.dev/cli 1.3.0 → 1.4.1
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 +119 -54
- package/dist/cli.js.map +1 -1
- package/dist/lambda/event-processor/.bundled +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -146,7 +146,7 @@ var require_package = __commonJS({
|
|
|
146
146
|
"package.json"(exports, module) {
|
|
147
147
|
module.exports = {
|
|
148
148
|
name: "@wraps.dev/cli",
|
|
149
|
-
version: "1.
|
|
149
|
+
version: "1.4.1",
|
|
150
150
|
description: "CLI for deploying Wraps email infrastructure to your AWS account",
|
|
151
151
|
type: "module",
|
|
152
152
|
main: "./dist/cli.js",
|
|
@@ -546,6 +546,12 @@ To remove: wraps destroy --stack ${stackName}`,
|
|
|
546
546
|
"PULUMI_NOT_INSTALLED",
|
|
547
547
|
"Install Pulumi:\n macOS: brew install pulumi/tap/pulumi\n Linux: curl -fsSL https://get.pulumi.com | sh\n Windows: choco install pulumi\n\nOr download from: https://www.pulumi.com/docs/install/",
|
|
548
548
|
"https://www.pulumi.com/docs/install/"
|
|
549
|
+
),
|
|
550
|
+
stackLocked: () => new WrapsError(
|
|
551
|
+
"The Pulumi stack is locked from a previous run",
|
|
552
|
+
"STACK_LOCKED",
|
|
553
|
+
"This happens when a previous deployment was interrupted.\n\nTo unlock, run:\n rm -rf ~/.wraps/pulumi/.pulumi/locks\n\nThen try your command again.",
|
|
554
|
+
"https://wraps.dev/docs/troubleshooting"
|
|
549
555
|
)
|
|
550
556
|
};
|
|
551
557
|
}
|
|
@@ -2796,7 +2802,9 @@ function createConnectionMetadata(accountId, region, provider, emailConfig, pres
|
|
|
2796
2802
|
function applyConfigUpdates(existingConfig, updates) {
|
|
2797
2803
|
const result = { ...existingConfig };
|
|
2798
2804
|
for (const [key, value] of Object.entries(updates)) {
|
|
2799
|
-
if (value === void 0)
|
|
2805
|
+
if (value === void 0) {
|
|
2806
|
+
continue;
|
|
2807
|
+
}
|
|
2800
2808
|
if (key === "tracking" && typeof value === "object") {
|
|
2801
2809
|
const trackingUpdate = value;
|
|
2802
2810
|
result.tracking = {
|
|
@@ -3269,6 +3277,7 @@ ${pc3.bold("Updated Permissions:")}`);
|
|
|
3269
3277
|
console.log(
|
|
3270
3278
|
` ${pc3.green("\u2713")} SES metrics and identity verification (always enabled)`
|
|
3271
3279
|
);
|
|
3280
|
+
console.log(` ${pc3.green("\u2713")} SES template management (always enabled)`);
|
|
3272
3281
|
if (sendingEnabled) {
|
|
3273
3282
|
console.log(` ${pc3.green("\u2713")} Email sending via SES`);
|
|
3274
3283
|
}
|
|
@@ -3302,6 +3311,18 @@ function buildConsolePolicyDocument(emailConfig) {
|
|
|
3302
3311
|
],
|
|
3303
3312
|
Resource: "*"
|
|
3304
3313
|
});
|
|
3314
|
+
statements.push({
|
|
3315
|
+
Effect: "Allow",
|
|
3316
|
+
Action: [
|
|
3317
|
+
"ses:GetTemplate",
|
|
3318
|
+
"ses:ListTemplates",
|
|
3319
|
+
"ses:CreateTemplate",
|
|
3320
|
+
"ses:UpdateTemplate",
|
|
3321
|
+
"ses:DeleteTemplate",
|
|
3322
|
+
"ses:TestRenderTemplate"
|
|
3323
|
+
],
|
|
3324
|
+
Resource: "*"
|
|
3325
|
+
});
|
|
3305
3326
|
const sendingEnabled = !emailConfig || emailConfig.sendingEnabled !== false;
|
|
3306
3327
|
if (sendingEnabled) {
|
|
3307
3328
|
statements.push({
|
|
@@ -3777,7 +3798,7 @@ Try running: pnpm build`
|
|
|
3777
3798
|
entryPoints: [sourcePath],
|
|
3778
3799
|
bundle: true,
|
|
3779
3800
|
platform: "node",
|
|
3780
|
-
target: "
|
|
3801
|
+
target: "node24",
|
|
3781
3802
|
format: "esm",
|
|
3782
3803
|
outfile: join3(outdir, "index.mjs"),
|
|
3783
3804
|
external: ["@aws-sdk/*"],
|
|
@@ -3849,7 +3870,7 @@ async function deployLambdaFunctions(config2) {
|
|
|
3849
3870
|
functionName,
|
|
3850
3871
|
{
|
|
3851
3872
|
name: functionName,
|
|
3852
|
-
runtime:
|
|
3873
|
+
runtime: "nodejs24.x",
|
|
3853
3874
|
handler: "index.handler",
|
|
3854
3875
|
role: lambdaRole.arn,
|
|
3855
3876
|
code: new pulumi3.asset.FileArchive(eventProcessorCode),
|
|
@@ -3873,7 +3894,7 @@ async function deployLambdaFunctions(config2) {
|
|
|
3873
3894
|
}
|
|
3874
3895
|
) : new aws4.lambda.Function(functionName, {
|
|
3875
3896
|
name: functionName,
|
|
3876
|
-
runtime:
|
|
3897
|
+
runtime: "nodejs24.x",
|
|
3877
3898
|
handler: "index.handler",
|
|
3878
3899
|
role: lambdaRole.arn,
|
|
3879
3900
|
code: new pulumi3.asset.FileArchive(eventProcessorCode),
|
|
@@ -3942,6 +3963,28 @@ async function configurationSetExists(configSetName, region) {
|
|
|
3942
3963
|
return false;
|
|
3943
3964
|
}
|
|
3944
3965
|
}
|
|
3966
|
+
async function eventDestinationExists(configSetName, eventDestName, region) {
|
|
3967
|
+
try {
|
|
3968
|
+
const {
|
|
3969
|
+
SESv2Client: SESv2Client5,
|
|
3970
|
+
GetConfigurationSetEventDestinationsCommand
|
|
3971
|
+
} = await import("@aws-sdk/client-sesv2");
|
|
3972
|
+
const ses = new SESv2Client5({ region });
|
|
3973
|
+
const response = await ses.send(
|
|
3974
|
+
new GetConfigurationSetEventDestinationsCommand({
|
|
3975
|
+
ConfigurationSetName: configSetName
|
|
3976
|
+
})
|
|
3977
|
+
);
|
|
3978
|
+
return response.EventDestinations?.some(
|
|
3979
|
+
(dest) => dest.Name === eventDestName
|
|
3980
|
+
) ?? false;
|
|
3981
|
+
} catch (error) {
|
|
3982
|
+
if (error.name === "NotFoundException") {
|
|
3983
|
+
return false;
|
|
3984
|
+
}
|
|
3985
|
+
return false;
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3945
3988
|
async function emailIdentityExists(emailIdentity, region) {
|
|
3946
3989
|
try {
|
|
3947
3990
|
const { SESv2Client: SESv2Client5, GetEmailIdentityCommand: GetEmailIdentityCommand4 } = await import("@aws-sdk/client-sesv2");
|
|
@@ -3995,29 +4038,37 @@ async function createSESResources(config2) {
|
|
|
3995
4038
|
});
|
|
3996
4039
|
if (config2.eventTrackingEnabled) {
|
|
3997
4040
|
const eventDestName = "wraps-email-eventbridge";
|
|
3998
|
-
new aws5.sesv2.ConfigurationSetEventDestination(
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4041
|
+
new aws5.sesv2.ConfigurationSetEventDestination(
|
|
4042
|
+
"wraps-email-all-events",
|
|
4043
|
+
{
|
|
4044
|
+
configurationSetName: configSet.configurationSetName,
|
|
4045
|
+
eventDestinationName: eventDestName,
|
|
4046
|
+
eventDestination: {
|
|
4047
|
+
enabled: true,
|
|
4048
|
+
matchingEventTypes: [
|
|
4049
|
+
"SEND",
|
|
4050
|
+
"DELIVERY",
|
|
4051
|
+
"OPEN",
|
|
4052
|
+
"CLICK",
|
|
4053
|
+
"BOUNCE",
|
|
4054
|
+
"COMPLAINT",
|
|
4055
|
+
"REJECT",
|
|
4056
|
+
"RENDERING_FAILURE",
|
|
4057
|
+
"DELIVERY_DELAY",
|
|
4058
|
+
"SUBSCRIPTION"
|
|
4059
|
+
],
|
|
4060
|
+
eventBridgeDestination: {
|
|
4061
|
+
// SES requires default bus - cannot use custom bus
|
|
4062
|
+
eventBusArn: defaultEventBus.arn
|
|
4063
|
+
}
|
|
4018
4064
|
}
|
|
4065
|
+
},
|
|
4066
|
+
{
|
|
4067
|
+
// Import existing resource if it already exists in AWS
|
|
4068
|
+
// This prevents AlreadyExistsException when the resource exists but isn't in Pulumi state
|
|
4069
|
+
import: config2.importExistingEventDestination ? `wraps-email-tracking|${eventDestName}` : void 0
|
|
4019
4070
|
}
|
|
4020
|
-
|
|
4071
|
+
);
|
|
4021
4072
|
}
|
|
4022
4073
|
let domainIdentity;
|
|
4023
4074
|
let dkimTokens;
|
|
@@ -4229,6 +4280,11 @@ async function deployEmailStack(config2) {
|
|
|
4229
4280
|
}
|
|
4230
4281
|
let sesResources;
|
|
4231
4282
|
if (emailConfig.tracking?.enabled || emailConfig.eventTracking?.enabled) {
|
|
4283
|
+
const shouldImportEventDest = emailConfig.eventTracking?.enabled && await eventDestinationExists(
|
|
4284
|
+
"wraps-email-tracking",
|
|
4285
|
+
"wraps-email-eventbridge",
|
|
4286
|
+
config2.region
|
|
4287
|
+
);
|
|
4232
4288
|
sesResources = await createSESResources({
|
|
4233
4289
|
domain: emailConfig.domain,
|
|
4234
4290
|
mailFromDomain: emailConfig.mailFromDomain,
|
|
@@ -4237,8 +4293,10 @@ async function deployEmailStack(config2) {
|
|
|
4237
4293
|
eventTypes: emailConfig.eventTracking?.events,
|
|
4238
4294
|
eventTrackingEnabled: emailConfig.eventTracking?.enabled,
|
|
4239
4295
|
// Pass flag to create EventBridge destination
|
|
4240
|
-
tlsRequired: emailConfig.tlsRequired
|
|
4296
|
+
tlsRequired: emailConfig.tlsRequired,
|
|
4241
4297
|
// Require TLS encryption for all emails
|
|
4298
|
+
importExistingEventDestination: shouldImportEventDest
|
|
4299
|
+
// Import if exists to avoid AlreadyExistsException
|
|
4242
4300
|
});
|
|
4243
4301
|
}
|
|
4244
4302
|
let dynamoTables;
|
|
@@ -4303,6 +4361,7 @@ async function deployEmailStack(config2) {
|
|
|
4303
4361
|
|
|
4304
4362
|
// src/commands/email/config.ts
|
|
4305
4363
|
init_aws();
|
|
4364
|
+
init_errors();
|
|
4306
4365
|
|
|
4307
4366
|
// src/utils/shared/pulumi.ts
|
|
4308
4367
|
init_esm_shims();
|
|
@@ -4373,7 +4432,14 @@ ${pc4.bold("Current Configuration:")}
|
|
|
4373
4432
|
} else {
|
|
4374
4433
|
console.log(` Preset: ${pc4.cyan("custom")}`);
|
|
4375
4434
|
}
|
|
4376
|
-
const config2 = metadata.services.email
|
|
4435
|
+
const config2 = metadata.services.email?.config;
|
|
4436
|
+
if (!config2) {
|
|
4437
|
+
clack3.log.error("No email configuration found in metadata");
|
|
4438
|
+
clack3.log.info(
|
|
4439
|
+
`Use ${pc4.cyan("wraps email init")} to create new infrastructure.`
|
|
4440
|
+
);
|
|
4441
|
+
process.exit(1);
|
|
4442
|
+
}
|
|
4377
4443
|
if (config2.domain) {
|
|
4378
4444
|
console.log(` Sending Domain: ${pc4.cyan(config2.domain)}`);
|
|
4379
4445
|
}
|
|
@@ -4462,6 +4528,8 @@ ${pc4.bold("Current Configuration:")}
|
|
|
4462
4528
|
metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`
|
|
4463
4529
|
);
|
|
4464
4530
|
await stack.setConfig("aws:region", { value: region });
|
|
4531
|
+
await stack.refresh({ onOutput: () => {
|
|
4532
|
+
} });
|
|
4465
4533
|
const upResult = await stack.up({ onOutput: () => {
|
|
4466
4534
|
} });
|
|
4467
4535
|
const pulumiOutputs = upResult.outputs;
|
|
@@ -4478,12 +4546,8 @@ ${pc4.bold("Current Configuration:")}
|
|
|
4478
4546
|
}
|
|
4479
4547
|
);
|
|
4480
4548
|
} catch (error) {
|
|
4481
|
-
clack3.log.error("Infrastructure update failed");
|
|
4482
4549
|
if (error.message?.includes("stack is currently locked")) {
|
|
4483
|
-
|
|
4484
|
-
clack3.log.info("To fix this, run:");
|
|
4485
|
-
clack3.log.info(` ${pc4.cyan("rm -rf ~/.wraps/pulumi/.pulumi/locks")}`);
|
|
4486
|
-
clack3.log.info("\nThen try running wraps update again.");
|
|
4550
|
+
throw errors.stackLocked();
|
|
4487
4551
|
}
|
|
4488
4552
|
throw new Error(`Pulumi update failed: ${error.message}`);
|
|
4489
4553
|
}
|
|
@@ -4524,6 +4588,7 @@ import * as pulumi6 from "@pulumi/pulumi";
|
|
|
4524
4588
|
import pc6 from "picocolors";
|
|
4525
4589
|
init_presets();
|
|
4526
4590
|
init_aws();
|
|
4591
|
+
init_errors();
|
|
4527
4592
|
init_prompts();
|
|
4528
4593
|
|
|
4529
4594
|
// src/utils/shared/scanner.ts
|
|
@@ -4898,12 +4963,8 @@ async function connect(options) {
|
|
|
4898
4963
|
}
|
|
4899
4964
|
);
|
|
4900
4965
|
} catch (error) {
|
|
4901
|
-
clack5.log.error("Infrastructure deployment failed");
|
|
4902
4966
|
if (error.message?.includes("stack is currently locked")) {
|
|
4903
|
-
|
|
4904
|
-
clack5.log.info("To fix this, run:");
|
|
4905
|
-
clack5.log.info(` ${pc6.cyan("rm -rf ~/.wraps/pulumi/.pulumi/locks")}`);
|
|
4906
|
-
clack5.log.info("\nThen try running wraps email connect again.");
|
|
4967
|
+
throw errors.stackLocked();
|
|
4907
4968
|
}
|
|
4908
4969
|
throw new Error(`Pulumi deployment failed: ${error.message}`);
|
|
4909
4970
|
}
|
|
@@ -5401,6 +5462,7 @@ import pc8 from "picocolors";
|
|
|
5401
5462
|
init_costs();
|
|
5402
5463
|
init_presets();
|
|
5403
5464
|
init_aws();
|
|
5465
|
+
init_errors();
|
|
5404
5466
|
init_prompts();
|
|
5405
5467
|
async function init(options) {
|
|
5406
5468
|
clack7.intro(pc8.bold("Wraps Email Infrastructure Setup"));
|
|
@@ -5562,12 +5624,8 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5562
5624
|
}
|
|
5563
5625
|
);
|
|
5564
5626
|
} catch (error) {
|
|
5565
|
-
clack7.log.error("Infrastructure deployment failed");
|
|
5566
5627
|
if (error.message?.includes("stack is currently locked")) {
|
|
5567
|
-
|
|
5568
|
-
clack7.log.info("To fix this, run:");
|
|
5569
|
-
clack7.log.info(` ${pc8.cyan("rm -rf ~/.wraps/pulumi/.pulumi/locks")}`);
|
|
5570
|
-
clack7.log.info("\nThen try running wraps email init again.");
|
|
5628
|
+
throw errors.stackLocked();
|
|
5571
5629
|
}
|
|
5572
5630
|
throw new Error(`Pulumi deployment failed: ${error.message}`);
|
|
5573
5631
|
}
|
|
@@ -5662,13 +5720,13 @@ async function restore(options) {
|
|
|
5662
5720
|
${pc9.bold("The following Wraps resources will be removed:")}
|
|
5663
5721
|
`
|
|
5664
5722
|
);
|
|
5665
|
-
if (metadata.services.email
|
|
5723
|
+
if (metadata.services.email?.config.tracking?.enabled) {
|
|
5666
5724
|
console.log(` ${pc9.cyan("\u2713")} Configuration Set (wraps-email-tracking)`);
|
|
5667
5725
|
}
|
|
5668
|
-
if (metadata.services.email
|
|
5726
|
+
if (metadata.services.email?.config.eventTracking?.dynamoDBHistory) {
|
|
5669
5727
|
console.log(` ${pc9.cyan("\u2713")} DynamoDB Table (wraps-email-history)`);
|
|
5670
5728
|
}
|
|
5671
|
-
if (metadata.services.email
|
|
5729
|
+
if (metadata.services.email?.config.eventTracking?.enabled) {
|
|
5672
5730
|
console.log(` ${pc9.cyan("\u2713")} EventBridge Rules`);
|
|
5673
5731
|
console.log(` ${pc9.cyan("\u2713")} SQS Queues`);
|
|
5674
5732
|
console.log(` ${pc9.cyan("\u2713")} Lambda Functions`);
|
|
@@ -5740,6 +5798,7 @@ import pc10 from "picocolors";
|
|
|
5740
5798
|
init_costs();
|
|
5741
5799
|
init_presets();
|
|
5742
5800
|
init_aws();
|
|
5801
|
+
init_errors();
|
|
5743
5802
|
init_prompts();
|
|
5744
5803
|
async function upgrade(options) {
|
|
5745
5804
|
clack9.intro(pc10.bold("Wraps Upgrade - Enhance Your Email Infrastructure"));
|
|
@@ -5780,7 +5839,14 @@ ${pc10.bold("Current Configuration:")}
|
|
|
5780
5839
|
} else {
|
|
5781
5840
|
console.log(` Preset: ${pc10.cyan("custom")}`);
|
|
5782
5841
|
}
|
|
5783
|
-
const config2 = metadata.services.email
|
|
5842
|
+
const config2 = metadata.services.email?.config;
|
|
5843
|
+
if (!config2) {
|
|
5844
|
+
clack9.log.error("No email configuration found in metadata");
|
|
5845
|
+
clack9.log.info(
|
|
5846
|
+
`Use ${pc10.cyan("wraps email init")} to create new infrastructure.`
|
|
5847
|
+
);
|
|
5848
|
+
process.exit(1);
|
|
5849
|
+
}
|
|
5784
5850
|
if (config2.domain) {
|
|
5785
5851
|
console.log(` Sending Domain: ${pc10.cyan(config2.domain)}`);
|
|
5786
5852
|
}
|
|
@@ -6454,12 +6520,8 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6454
6520
|
}
|
|
6455
6521
|
);
|
|
6456
6522
|
} catch (error) {
|
|
6457
|
-
clack9.log.error("Infrastructure upgrade failed");
|
|
6458
6523
|
if (error.message?.includes("stack is currently locked")) {
|
|
6459
|
-
|
|
6460
|
-
clack9.log.info("To fix this, run:");
|
|
6461
|
-
clack9.log.info(` ${pc10.cyan("rm -rf ~/.wraps/pulumi/.pulumi/locks")}`);
|
|
6462
|
-
clack9.log.info("\nThen try running wraps upgrade again.");
|
|
6524
|
+
throw errors.stackLocked();
|
|
6463
6525
|
}
|
|
6464
6526
|
throw new Error(`Pulumi upgrade failed: ${error.message}`);
|
|
6465
6527
|
}
|
|
@@ -7438,7 +7500,7 @@ function createSettingsRouter(config2) {
|
|
|
7438
7500
|
});
|
|
7439
7501
|
}
|
|
7440
7502
|
const configSetName = "wraps-email-tracking";
|
|
7441
|
-
const domain = metadata.services.email
|
|
7503
|
+
const domain = metadata.services.email?.config.domain;
|
|
7442
7504
|
const settings = await fetchEmailSettings(
|
|
7443
7505
|
config2.roleArn,
|
|
7444
7506
|
config2.region,
|
|
@@ -8113,7 +8175,9 @@ function showHelp() {
|
|
|
8113
8175
|
` ${pc15.cyan("email connect")} Connect to existing AWS SES`
|
|
8114
8176
|
);
|
|
8115
8177
|
console.log(` ${pc15.cyan("email domains verify")} Verify domain DNS records`);
|
|
8116
|
-
console.log(
|
|
8178
|
+
console.log(
|
|
8179
|
+
` ${pc15.cyan("email sync")} Apply CLI updates to infrastructure`
|
|
8180
|
+
);
|
|
8117
8181
|
console.log(` ${pc15.cyan("email upgrade")} Add features`);
|
|
8118
8182
|
console.log(
|
|
8119
8183
|
` ${pc15.cyan("email restore")} Restore original configuration
|
|
@@ -8318,6 +8382,7 @@ async function run() {
|
|
|
8318
8382
|
});
|
|
8319
8383
|
break;
|
|
8320
8384
|
case "config":
|
|
8385
|
+
case "sync":
|
|
8321
8386
|
await config({
|
|
8322
8387
|
region: flags.region,
|
|
8323
8388
|
yes: flags.yes
|