@wraps.dev/cli 1.4.1 → 1.5.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/README.md +63 -0
- package/dist/cli.js +653 -29
- 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.5.1",
|
|
150
150
|
description: "CLI for deploying Wraps email infrastructure to your AWS account",
|
|
151
151
|
type: "module",
|
|
152
152
|
main: "./dist/cli.js",
|
|
@@ -448,6 +448,21 @@ function trackCommand(command, metadata) {
|
|
|
448
448
|
sanitized.email = void 0;
|
|
449
449
|
client.track(`command:${command}`, sanitized);
|
|
450
450
|
}
|
|
451
|
+
function trackServiceInit(service, success, metadata) {
|
|
452
|
+
const client = getTelemetryClient();
|
|
453
|
+
client.track("service:init", {
|
|
454
|
+
service,
|
|
455
|
+
success,
|
|
456
|
+
...metadata
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
function trackServiceDeployed(service, metadata) {
|
|
460
|
+
const client = getTelemetryClient();
|
|
461
|
+
client.track("service:deployed", {
|
|
462
|
+
service,
|
|
463
|
+
...metadata
|
|
464
|
+
});
|
|
465
|
+
}
|
|
451
466
|
function trackError(errorCode, command, metadata) {
|
|
452
467
|
const client = getTelemetryClient();
|
|
453
468
|
client.track("error:occurred", {
|
|
@@ -456,6 +471,24 @@ function trackError(errorCode, command, metadata) {
|
|
|
456
471
|
...metadata
|
|
457
472
|
});
|
|
458
473
|
}
|
|
474
|
+
function trackFeature(feature, metadata) {
|
|
475
|
+
const client = getTelemetryClient();
|
|
476
|
+
client.track(`feature:${feature}`, metadata || {});
|
|
477
|
+
}
|
|
478
|
+
function trackServiceUpgrade(service, metadata) {
|
|
479
|
+
const client = getTelemetryClient();
|
|
480
|
+
client.track("service:upgraded", {
|
|
481
|
+
service,
|
|
482
|
+
...metadata
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
function trackServiceRemoved(service, metadata) {
|
|
486
|
+
const client = getTelemetryClient();
|
|
487
|
+
client.track("service:removed", {
|
|
488
|
+
service,
|
|
489
|
+
...metadata
|
|
490
|
+
});
|
|
491
|
+
}
|
|
459
492
|
var init_events = __esm({
|
|
460
493
|
"src/telemetry/events.ts"() {
|
|
461
494
|
"use strict";
|
|
@@ -3198,6 +3231,35 @@ ${pc2.bold("Dashboard:")} ${pc2.blue("https://app.wraps.dev")}`);
|
|
|
3198
3231
|
console.log(`${pc2.bold("Docs:")} ${pc2.blue("https://wraps.dev/docs")}
|
|
3199
3232
|
`);
|
|
3200
3233
|
}
|
|
3234
|
+
function displayPreview(outputs) {
|
|
3235
|
+
console.log(pc2.yellow("\n--- PREVIEW MODE (no changes will be made) ---\n"));
|
|
3236
|
+
const changes = outputs.changeSummary;
|
|
3237
|
+
const summaryLines = [];
|
|
3238
|
+
if (changes.create && changes.create > 0) {
|
|
3239
|
+
summaryLines.push(` ${pc2.green("+")} ${changes.create} to create`);
|
|
3240
|
+
}
|
|
3241
|
+
if (changes.update && changes.update > 0) {
|
|
3242
|
+
summaryLines.push(` ${pc2.yellow("~")} ${changes.update} to update`);
|
|
3243
|
+
}
|
|
3244
|
+
if (changes.delete && changes.delete > 0) {
|
|
3245
|
+
summaryLines.push(` ${pc2.red("-")} ${changes.delete} to destroy`);
|
|
3246
|
+
}
|
|
3247
|
+
if (changes.same && changes.same > 0) {
|
|
3248
|
+
summaryLines.push(` ${pc2.dim("=")} ${changes.same} unchanged`);
|
|
3249
|
+
}
|
|
3250
|
+
if (changes.replace && changes.replace > 0) {
|
|
3251
|
+
summaryLines.push(` ${pc2.magenta("+-")} ${changes.replace} to replace`);
|
|
3252
|
+
}
|
|
3253
|
+
if (summaryLines.length > 0) {
|
|
3254
|
+
clack2.note(summaryLines.join("\n"), "Resource Changes");
|
|
3255
|
+
} else {
|
|
3256
|
+
clack2.note("No changes detected", "Resource Changes");
|
|
3257
|
+
}
|
|
3258
|
+
if (outputs.costEstimate) {
|
|
3259
|
+
clack2.note(outputs.costEstimate, "Estimated Monthly Cost");
|
|
3260
|
+
}
|
|
3261
|
+
console.log(pc2.yellow("\n--- END PREVIEW (no changes were made) ---\n"));
|
|
3262
|
+
}
|
|
3201
3263
|
|
|
3202
3264
|
// src/commands/dashboard/update-role.ts
|
|
3203
3265
|
async function updateRole(options) {
|
|
@@ -3965,19 +4027,14 @@ async function configurationSetExists(configSetName, region) {
|
|
|
3965
4027
|
}
|
|
3966
4028
|
async function eventDestinationExists(configSetName, eventDestName, region) {
|
|
3967
4029
|
try {
|
|
3968
|
-
const {
|
|
3969
|
-
SESv2Client: SESv2Client5,
|
|
3970
|
-
GetConfigurationSetEventDestinationsCommand
|
|
3971
|
-
} = await import("@aws-sdk/client-sesv2");
|
|
4030
|
+
const { SESv2Client: SESv2Client5, GetConfigurationSetEventDestinationsCommand } = await import("@aws-sdk/client-sesv2");
|
|
3972
4031
|
const ses = new SESv2Client5({ region });
|
|
3973
4032
|
const response = await ses.send(
|
|
3974
4033
|
new GetConfigurationSetEventDestinationsCommand({
|
|
3975
4034
|
ConfigurationSetName: configSetName
|
|
3976
4035
|
})
|
|
3977
4036
|
);
|
|
3978
|
-
return response.EventDestinations?.some(
|
|
3979
|
-
(dest) => dest.Name === eventDestName
|
|
3980
|
-
) ?? false;
|
|
4037
|
+
return response.EventDestinations?.some((dest) => dest.Name === eventDestName) ?? false;
|
|
3981
4038
|
} catch (error) {
|
|
3982
4039
|
if (error.name === "NotFoundException") {
|
|
3983
4040
|
return false;
|
|
@@ -4360,6 +4417,7 @@ async function deployEmailStack(config2) {
|
|
|
4360
4417
|
}
|
|
4361
4418
|
|
|
4362
4419
|
// src/commands/email/config.ts
|
|
4420
|
+
init_events();
|
|
4363
4421
|
init_aws();
|
|
4364
4422
|
init_errors();
|
|
4365
4423
|
|
|
@@ -4394,7 +4452,12 @@ async function ensurePulumiInstalled() {
|
|
|
4394
4452
|
|
|
4395
4453
|
// src/commands/email/config.ts
|
|
4396
4454
|
async function config(options) {
|
|
4397
|
-
|
|
4455
|
+
const startTime = Date.now();
|
|
4456
|
+
clack3.intro(
|
|
4457
|
+
pc4.bold(
|
|
4458
|
+
options.preview ? "Wraps Config Preview" : "Wraps Config - Apply CLI Updates to Infrastructure"
|
|
4459
|
+
)
|
|
4460
|
+
);
|
|
4398
4461
|
const progress = new DeploymentProgress();
|
|
4399
4462
|
const wasAutoInstalled = await progress.execute(
|
|
4400
4463
|
"Checking Pulumi CLI installation",
|
|
@@ -4471,7 +4534,7 @@ ${pc4.bold("Current Configuration:")}
|
|
|
4471
4534
|
"Your current configuration will be preserved - no features will be added or removed"
|
|
4472
4535
|
);
|
|
4473
4536
|
console.log("");
|
|
4474
|
-
if (!options.yes) {
|
|
4537
|
+
if (!(options.yes || options.preview)) {
|
|
4475
4538
|
const confirmed = await clack3.confirm({
|
|
4476
4539
|
message: "Proceed with update?",
|
|
4477
4540
|
initialValue: true
|
|
@@ -4491,6 +4554,67 @@ ${pc4.bold("Current Configuration:")}
|
|
|
4491
4554
|
vercel: vercelConfig,
|
|
4492
4555
|
emailConfig: config2
|
|
4493
4556
|
};
|
|
4557
|
+
if (options.preview) {
|
|
4558
|
+
try {
|
|
4559
|
+
const previewResult = await progress.execute(
|
|
4560
|
+
"Generating update preview",
|
|
4561
|
+
async () => {
|
|
4562
|
+
await ensurePulumiWorkDir();
|
|
4563
|
+
const stack = await pulumi5.automation.LocalWorkspace.createOrSelectStack(
|
|
4564
|
+
{
|
|
4565
|
+
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
4566
|
+
projectName: "wraps-email",
|
|
4567
|
+
program: async () => {
|
|
4568
|
+
const result2 = await deployEmailStack(stackConfig);
|
|
4569
|
+
return {
|
|
4570
|
+
roleArn: result2.roleArn,
|
|
4571
|
+
configSetName: result2.configSetName,
|
|
4572
|
+
tableName: result2.tableName,
|
|
4573
|
+
region: result2.region,
|
|
4574
|
+
lambdaFunctions: result2.lambdaFunctions,
|
|
4575
|
+
domain: result2.domain,
|
|
4576
|
+
dkimTokens: result2.dkimTokens,
|
|
4577
|
+
customTrackingDomain: result2.customTrackingDomain
|
|
4578
|
+
};
|
|
4579
|
+
}
|
|
4580
|
+
},
|
|
4581
|
+
{
|
|
4582
|
+
workDir: getPulumiWorkDir(),
|
|
4583
|
+
envVars: {
|
|
4584
|
+
PULUMI_CONFIG_PASSPHRASE: "",
|
|
4585
|
+
AWS_REGION: region
|
|
4586
|
+
},
|
|
4587
|
+
secretsProvider: "passphrase"
|
|
4588
|
+
}
|
|
4589
|
+
);
|
|
4590
|
+
await stack.setConfig("aws:region", { value: region });
|
|
4591
|
+
await stack.refresh({ onOutput: () => {
|
|
4592
|
+
} });
|
|
4593
|
+
const result = await stack.preview({ diff: true });
|
|
4594
|
+
return result;
|
|
4595
|
+
}
|
|
4596
|
+
);
|
|
4597
|
+
displayPreview({
|
|
4598
|
+
changeSummary: previewResult.changeSummary,
|
|
4599
|
+
commandName: "wraps email config"
|
|
4600
|
+
});
|
|
4601
|
+
clack3.outro(
|
|
4602
|
+
pc4.green("Preview complete. Run without --preview to update.")
|
|
4603
|
+
);
|
|
4604
|
+
trackCommand("email:config", {
|
|
4605
|
+
success: true,
|
|
4606
|
+
preview: true,
|
|
4607
|
+
duration_ms: Date.now() - startTime
|
|
4608
|
+
});
|
|
4609
|
+
return;
|
|
4610
|
+
} catch (error) {
|
|
4611
|
+
trackError("PREVIEW_FAILED", "email:config", { step: "preview" });
|
|
4612
|
+
if (error.message?.includes("stack is currently locked")) {
|
|
4613
|
+
throw errors.stackLocked();
|
|
4614
|
+
}
|
|
4615
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4494
4618
|
let outputs;
|
|
4495
4619
|
try {
|
|
4496
4620
|
outputs = await progress.execute(
|
|
@@ -4546,9 +4670,15 @@ ${pc4.bold("Current Configuration:")}
|
|
|
4546
4670
|
}
|
|
4547
4671
|
);
|
|
4548
4672
|
} catch (error) {
|
|
4673
|
+
trackCommand("email:config", {
|
|
4674
|
+
success: false,
|
|
4675
|
+
duration_ms: Date.now() - startTime
|
|
4676
|
+
});
|
|
4549
4677
|
if (error.message?.includes("stack is currently locked")) {
|
|
4678
|
+
trackError("STACK_LOCKED", "email:config", { step: "update" });
|
|
4550
4679
|
throw errors.stackLocked();
|
|
4551
4680
|
}
|
|
4681
|
+
trackError("UPDATE_FAILED", "email:config", { step: "update" });
|
|
4552
4682
|
throw new Error(`Pulumi update failed: ${error.message}`);
|
|
4553
4683
|
}
|
|
4554
4684
|
metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4579,6 +4709,10 @@ ${pc4.green("\u2713")} ${pc4.bold("Update complete!")}
|
|
|
4579
4709
|
` ${pc4.cyan("3.")} View analytics at ${pc4.cyan("wraps console")}
|
|
4580
4710
|
`
|
|
4581
4711
|
);
|
|
4712
|
+
trackCommand("email:config", {
|
|
4713
|
+
success: true,
|
|
4714
|
+
duration_ms: Date.now() - startTime
|
|
4715
|
+
});
|
|
4582
4716
|
}
|
|
4583
4717
|
|
|
4584
4718
|
// src/commands/email/connect.ts
|
|
@@ -4586,6 +4720,7 @@ init_esm_shims();
|
|
|
4586
4720
|
import * as clack5 from "@clack/prompts";
|
|
4587
4721
|
import * as pulumi6 from "@pulumi/pulumi";
|
|
4588
4722
|
import pc6 from "picocolors";
|
|
4723
|
+
init_events();
|
|
4589
4724
|
init_presets();
|
|
4590
4725
|
init_aws();
|
|
4591
4726
|
init_errors();
|
|
@@ -4819,7 +4954,12 @@ async function scanAWSResources(region) {
|
|
|
4819
4954
|
|
|
4820
4955
|
// src/commands/email/connect.ts
|
|
4821
4956
|
async function connect(options) {
|
|
4822
|
-
|
|
4957
|
+
const startTime = Date.now();
|
|
4958
|
+
clack5.intro(
|
|
4959
|
+
pc6.bold(
|
|
4960
|
+
options.preview ? "Wraps Connect Preview" : "Wraps Connect - Link Existing Infrastructure"
|
|
4961
|
+
)
|
|
4962
|
+
);
|
|
4823
4963
|
const progress = new DeploymentProgress();
|
|
4824
4964
|
const wasAutoInstalled = await progress.execute(
|
|
4825
4965
|
"Checking Pulumi CLI installation",
|
|
@@ -4897,7 +5037,7 @@ async function connect(options) {
|
|
|
4897
5037
|
if (domainIdentities.length > 0) {
|
|
4898
5038
|
emailConfig.domain = domainIdentities[0];
|
|
4899
5039
|
}
|
|
4900
|
-
if (!options.yes) {
|
|
5040
|
+
if (!(options.yes || options.preview)) {
|
|
4901
5041
|
const confirmed = await confirmConnect();
|
|
4902
5042
|
if (!confirmed) {
|
|
4903
5043
|
clack5.cancel("Connection cancelled.");
|
|
@@ -4910,6 +5050,67 @@ async function connect(options) {
|
|
|
4910
5050
|
vercel: vercelConfig,
|
|
4911
5051
|
emailConfig
|
|
4912
5052
|
};
|
|
5053
|
+
if (options.preview) {
|
|
5054
|
+
try {
|
|
5055
|
+
const previewResult = await progress.execute(
|
|
5056
|
+
"Generating infrastructure preview",
|
|
5057
|
+
async () => {
|
|
5058
|
+
await ensurePulumiWorkDir();
|
|
5059
|
+
const stack = await pulumi6.automation.LocalWorkspace.createOrSelectStack(
|
|
5060
|
+
{
|
|
5061
|
+
stackName: `wraps-${identity.accountId}-${region}`,
|
|
5062
|
+
projectName: "wraps-email",
|
|
5063
|
+
program: async () => {
|
|
5064
|
+
const result2 = await deployEmailStack(stackConfig);
|
|
5065
|
+
return {
|
|
5066
|
+
roleArn: result2.roleArn,
|
|
5067
|
+
configSetName: result2.configSetName,
|
|
5068
|
+
tableName: result2.tableName,
|
|
5069
|
+
region: result2.region,
|
|
5070
|
+
lambdaFunctions: result2.lambdaFunctions,
|
|
5071
|
+
domain: result2.domain,
|
|
5072
|
+
dkimTokens: result2.dkimTokens,
|
|
5073
|
+
customTrackingDomain: result2.customTrackingDomain
|
|
5074
|
+
};
|
|
5075
|
+
}
|
|
5076
|
+
},
|
|
5077
|
+
{
|
|
5078
|
+
workDir: getPulumiWorkDir(),
|
|
5079
|
+
envVars: {
|
|
5080
|
+
PULUMI_CONFIG_PASSPHRASE: "",
|
|
5081
|
+
AWS_REGION: region
|
|
5082
|
+
},
|
|
5083
|
+
secretsProvider: "passphrase"
|
|
5084
|
+
}
|
|
5085
|
+
);
|
|
5086
|
+
await stack.setConfig("aws:region", { value: region });
|
|
5087
|
+
const result = await stack.preview({ diff: true });
|
|
5088
|
+
return result;
|
|
5089
|
+
}
|
|
5090
|
+
);
|
|
5091
|
+
displayPreview({
|
|
5092
|
+
changeSummary: previewResult.changeSummary,
|
|
5093
|
+
commandName: "wraps email connect"
|
|
5094
|
+
});
|
|
5095
|
+
clack5.outro(
|
|
5096
|
+
pc6.green("Preview complete. Run without --preview to connect.")
|
|
5097
|
+
);
|
|
5098
|
+
trackServiceInit("email", true, {
|
|
5099
|
+
preset,
|
|
5100
|
+
provider,
|
|
5101
|
+
preview: true,
|
|
5102
|
+
duration_ms: Date.now() - startTime,
|
|
5103
|
+
existing_identities: selectedIdentities.length
|
|
5104
|
+
});
|
|
5105
|
+
return;
|
|
5106
|
+
} catch (error) {
|
|
5107
|
+
trackError("PREVIEW_FAILED", "email:connect", { step: "preview" });
|
|
5108
|
+
if (error.message?.includes("stack is currently locked")) {
|
|
5109
|
+
throw errors.stackLocked();
|
|
5110
|
+
}
|
|
5111
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
4913
5114
|
let outputs;
|
|
4914
5115
|
try {
|
|
4915
5116
|
outputs = await progress.execute(
|
|
@@ -4963,9 +5164,16 @@ async function connect(options) {
|
|
|
4963
5164
|
}
|
|
4964
5165
|
);
|
|
4965
5166
|
} catch (error) {
|
|
5167
|
+
trackServiceInit("email", false, {
|
|
5168
|
+
preset,
|
|
5169
|
+
provider,
|
|
5170
|
+
duration_ms: Date.now() - startTime
|
|
5171
|
+
});
|
|
4966
5172
|
if (error.message?.includes("stack is currently locked")) {
|
|
5173
|
+
trackError("STACK_LOCKED", "email:connect", { step: "deploy" });
|
|
4967
5174
|
throw errors.stackLocked();
|
|
4968
5175
|
}
|
|
5176
|
+
trackError("DEPLOYMENT_FAILED", "email:connect", { step: "deploy" });
|
|
4969
5177
|
throw new Error(`Pulumi deployment failed: ${error.message}`);
|
|
4970
5178
|
}
|
|
4971
5179
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
@@ -5032,10 +5240,32 @@ ${pc6.dim("Example:")}`);
|
|
|
5032
5240
|
);
|
|
5033
5241
|
console.log("");
|
|
5034
5242
|
}
|
|
5243
|
+
const duration = Date.now() - startTime;
|
|
5244
|
+
const enabledFeatures = [];
|
|
5245
|
+
if (emailConfig.tracking?.enabled) enabledFeatures.push("tracking");
|
|
5246
|
+
if (emailConfig.suppressionList?.enabled)
|
|
5247
|
+
enabledFeatures.push("suppression_list");
|
|
5248
|
+
if (emailConfig.eventTracking?.enabled)
|
|
5249
|
+
enabledFeatures.push("event_tracking");
|
|
5250
|
+
if (emailConfig.eventTracking?.dynamoDBHistory)
|
|
5251
|
+
enabledFeatures.push("dynamodb_history");
|
|
5252
|
+
trackServiceInit("email", true, {
|
|
5253
|
+
preset,
|
|
5254
|
+
provider,
|
|
5255
|
+
features: enabledFeatures,
|
|
5256
|
+
duration_ms: duration,
|
|
5257
|
+
existing_identities: selectedIdentities.length
|
|
5258
|
+
});
|
|
5259
|
+
trackServiceDeployed("email", {
|
|
5260
|
+
duration_ms: duration,
|
|
5261
|
+
features: enabledFeatures,
|
|
5262
|
+
preset
|
|
5263
|
+
});
|
|
5035
5264
|
}
|
|
5036
5265
|
|
|
5037
5266
|
// src/commands/email/domains.ts
|
|
5038
5267
|
init_esm_shims();
|
|
5268
|
+
init_events();
|
|
5039
5269
|
init_aws();
|
|
5040
5270
|
import { Resolver } from "dns/promises";
|
|
5041
5271
|
import { GetEmailIdentityCommand, SESv2Client as SESv2Client2 } from "@aws-sdk/client-sesv2";
|
|
@@ -5207,6 +5437,7 @@ Run ${pc7.cyan(`wraps email init --domain ${options.domain}`)} to add this domai
|
|
|
5207
5437
|
clack6.outro(
|
|
5208
5438
|
pc7.green("\u2713 Domain is fully verified and ready to send emails!")
|
|
5209
5439
|
);
|
|
5440
|
+
trackFeature("domain_verified", { dns_auto_detected: true });
|
|
5210
5441
|
} else if (someIncorrect) {
|
|
5211
5442
|
clack6.outro(
|
|
5212
5443
|
pc7.red("\u2717 Some DNS records are incorrect. Please update them.")
|
|
@@ -5225,6 +5456,11 @@ Run ${pc7.cyan("wraps email status")} to see the correct DNS records.
|
|
|
5225
5456
|
"SES verification usually completes within 72 hours after DNS propagation.\n"
|
|
5226
5457
|
);
|
|
5227
5458
|
}
|
|
5459
|
+
trackCommand("email:domains:verify", {
|
|
5460
|
+
success: true,
|
|
5461
|
+
verified: verificationStatus === "verified" && allVerified,
|
|
5462
|
+
dkim_status: dkimStatus
|
|
5463
|
+
});
|
|
5228
5464
|
}
|
|
5229
5465
|
async function addDomain(options) {
|
|
5230
5466
|
clack6.intro(pc7.bold(`Adding domain ${options.domain} to SES`));
|
|
@@ -5282,8 +5518,15 @@ ${pc7.bold("Next steps:")}
|
|
|
5282
5518
|
);
|
|
5283
5519
|
console.log(`3. Check status: ${pc7.cyan("wraps email status")}
|
|
5284
5520
|
`);
|
|
5521
|
+
trackCommand("email:domains:add", {
|
|
5522
|
+
success: true
|
|
5523
|
+
});
|
|
5524
|
+
trackFeature("domain_added", {});
|
|
5285
5525
|
} catch (error) {
|
|
5286
5526
|
progress.stop();
|
|
5527
|
+
trackCommand("email:domains:add", {
|
|
5528
|
+
success: false
|
|
5529
|
+
});
|
|
5287
5530
|
throw error;
|
|
5288
5531
|
}
|
|
5289
5532
|
}
|
|
@@ -5352,8 +5595,13 @@ Run ${pc7.cyan("wraps email domains add <domain>")} to add a domain.
|
|
|
5352
5595
|
`Run ${pc7.cyan("wraps email domains verify --domain <domain>")} for details`
|
|
5353
5596
|
)
|
|
5354
5597
|
);
|
|
5598
|
+
trackCommand("email:domains:list", {
|
|
5599
|
+
success: true,
|
|
5600
|
+
domain_count: domains.length
|
|
5601
|
+
});
|
|
5355
5602
|
} catch (error) {
|
|
5356
5603
|
progress.stop();
|
|
5604
|
+
trackCommand("email:domains:list", { success: false });
|
|
5357
5605
|
throw error;
|
|
5358
5606
|
}
|
|
5359
5607
|
}
|
|
@@ -5396,8 +5644,13 @@ ${pc7.bold("DNS Records to add:")}
|
|
|
5396
5644
|
`
|
|
5397
5645
|
);
|
|
5398
5646
|
}
|
|
5647
|
+
trackCommand("email:domains:get-dkim", {
|
|
5648
|
+
success: true,
|
|
5649
|
+
dkim_status: dkimStatus
|
|
5650
|
+
});
|
|
5399
5651
|
} catch (error) {
|
|
5400
5652
|
progress.stop();
|
|
5653
|
+
trackCommand("email:domains:get-dkim", { success: false });
|
|
5401
5654
|
if (error.name === "NotFoundException") {
|
|
5402
5655
|
clack6.log.error(`Domain ${options.domain} not found in SES`);
|
|
5403
5656
|
console.log(
|
|
@@ -5443,8 +5696,13 @@ async function removeDomain(options) {
|
|
|
5443
5696
|
});
|
|
5444
5697
|
progress.stop();
|
|
5445
5698
|
clack6.outro(pc7.green(`\u2713 Domain ${options.domain} removed successfully`));
|
|
5699
|
+
trackCommand("email:domains:remove", {
|
|
5700
|
+
success: true
|
|
5701
|
+
});
|
|
5702
|
+
trackFeature("domain_removed", {});
|
|
5446
5703
|
} catch (error) {
|
|
5447
5704
|
progress.stop();
|
|
5705
|
+
trackCommand("email:domains:remove", { success: false });
|
|
5448
5706
|
if (error.name === "NotFoundException") {
|
|
5449
5707
|
clack6.log.error(`Domain ${options.domain} not found in SES`);
|
|
5450
5708
|
process.exit(1);
|
|
@@ -5459,13 +5717,19 @@ init_esm_shims();
|
|
|
5459
5717
|
import * as clack7 from "@clack/prompts";
|
|
5460
5718
|
import * as pulumi7 from "@pulumi/pulumi";
|
|
5461
5719
|
import pc8 from "picocolors";
|
|
5720
|
+
init_events();
|
|
5462
5721
|
init_costs();
|
|
5463
5722
|
init_presets();
|
|
5464
5723
|
init_aws();
|
|
5465
5724
|
init_errors();
|
|
5466
5725
|
init_prompts();
|
|
5467
5726
|
async function init(options) {
|
|
5468
|
-
|
|
5727
|
+
const startTime = Date.now();
|
|
5728
|
+
clack7.intro(
|
|
5729
|
+
pc8.bold(
|
|
5730
|
+
options.preview ? "Wraps Email Infrastructure Preview" : "Wraps Email Infrastructure Setup"
|
|
5731
|
+
)
|
|
5732
|
+
);
|
|
5469
5733
|
const progress = new DeploymentProgress();
|
|
5470
5734
|
const wasAutoInstalled = await progress.execute(
|
|
5471
5735
|
"Checking Pulumi CLI installation",
|
|
@@ -5548,7 +5812,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5548
5812
|
if (vercelConfig) {
|
|
5549
5813
|
metadata.vercel = vercelConfig;
|
|
5550
5814
|
}
|
|
5551
|
-
if (!options.yes) {
|
|
5815
|
+
if (!(options.yes || options.preview)) {
|
|
5552
5816
|
const confirmed = await confirmDeploy();
|
|
5553
5817
|
if (!confirmed) {
|
|
5554
5818
|
clack7.cancel("Deployment cancelled.");
|
|
@@ -5561,6 +5825,71 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5561
5825
|
vercel: vercelConfig,
|
|
5562
5826
|
emailConfig
|
|
5563
5827
|
};
|
|
5828
|
+
if (options.preview) {
|
|
5829
|
+
try {
|
|
5830
|
+
const previewResult = await progress.execute(
|
|
5831
|
+
"Generating infrastructure preview",
|
|
5832
|
+
async () => {
|
|
5833
|
+
await ensurePulumiWorkDir();
|
|
5834
|
+
const stack = await pulumi7.automation.LocalWorkspace.createOrSelectStack(
|
|
5835
|
+
{
|
|
5836
|
+
stackName: `wraps-${identity.accountId}-${region}`,
|
|
5837
|
+
projectName: "wraps-email",
|
|
5838
|
+
program: async () => {
|
|
5839
|
+
const result2 = await deployEmailStack(stackConfig);
|
|
5840
|
+
return {
|
|
5841
|
+
roleArn: result2.roleArn,
|
|
5842
|
+
configSetName: result2.configSetName,
|
|
5843
|
+
tableName: result2.tableName,
|
|
5844
|
+
region: result2.region,
|
|
5845
|
+
lambdaFunctions: result2.lambdaFunctions,
|
|
5846
|
+
domain: result2.domain,
|
|
5847
|
+
dkimTokens: result2.dkimTokens,
|
|
5848
|
+
customTrackingDomain: result2.customTrackingDomain,
|
|
5849
|
+
mailFromDomain: result2.mailFromDomain,
|
|
5850
|
+
archiveArn: result2.archiveArn,
|
|
5851
|
+
archivingEnabled: result2.archivingEnabled,
|
|
5852
|
+
archiveRetention: result2.archiveRetention
|
|
5853
|
+
};
|
|
5854
|
+
}
|
|
5855
|
+
},
|
|
5856
|
+
{
|
|
5857
|
+
workDir: getPulumiWorkDir(),
|
|
5858
|
+
envVars: {
|
|
5859
|
+
PULUMI_CONFIG_PASSPHRASE: "",
|
|
5860
|
+
AWS_REGION: region
|
|
5861
|
+
},
|
|
5862
|
+
secretsProvider: "passphrase"
|
|
5863
|
+
}
|
|
5864
|
+
);
|
|
5865
|
+
await stack.setConfig("aws:region", { value: region });
|
|
5866
|
+
const result = await stack.preview({ diff: true });
|
|
5867
|
+
return result;
|
|
5868
|
+
}
|
|
5869
|
+
);
|
|
5870
|
+
displayPreview({
|
|
5871
|
+
changeSummary: previewResult.changeSummary,
|
|
5872
|
+
costEstimate: costSummary,
|
|
5873
|
+
commandName: "wraps email init"
|
|
5874
|
+
});
|
|
5875
|
+
clack7.outro(
|
|
5876
|
+
pc8.green("Preview complete. Run without --preview to deploy.")
|
|
5877
|
+
);
|
|
5878
|
+
trackServiceInit("email", true, {
|
|
5879
|
+
preset,
|
|
5880
|
+
provider,
|
|
5881
|
+
preview: true,
|
|
5882
|
+
duration_ms: Date.now() - startTime
|
|
5883
|
+
});
|
|
5884
|
+
return;
|
|
5885
|
+
} catch (error) {
|
|
5886
|
+
trackError("PREVIEW_FAILED", "email:init", { step: "preview" });
|
|
5887
|
+
if (error.message?.includes("stack is currently locked")) {
|
|
5888
|
+
throw errors.stackLocked();
|
|
5889
|
+
}
|
|
5890
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
5891
|
+
}
|
|
5892
|
+
}
|
|
5564
5893
|
let outputs;
|
|
5565
5894
|
try {
|
|
5566
5895
|
outputs = await progress.execute(
|
|
@@ -5624,9 +5953,16 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5624
5953
|
}
|
|
5625
5954
|
);
|
|
5626
5955
|
} catch (error) {
|
|
5956
|
+
trackServiceInit("email", false, {
|
|
5957
|
+
preset,
|
|
5958
|
+
provider,
|
|
5959
|
+
duration_ms: Date.now() - startTime
|
|
5960
|
+
});
|
|
5627
5961
|
if (error.message?.includes("stack is currently locked")) {
|
|
5962
|
+
trackError("STACK_LOCKED", "email:init", { step: "deploy" });
|
|
5628
5963
|
throw errors.stackLocked();
|
|
5629
5964
|
}
|
|
5965
|
+
trackError("DEPLOYMENT_FAILED", "email:init", { step: "deploy" });
|
|
5630
5966
|
throw new Error(`Pulumi deployment failed: ${error.message}`);
|
|
5631
5967
|
}
|
|
5632
5968
|
if (metadata.services.email) {
|
|
@@ -5677,16 +6013,45 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5677
6013
|
domain: outputs.domain,
|
|
5678
6014
|
mailFromDomain: outputs.mailFromDomain
|
|
5679
6015
|
});
|
|
6016
|
+
const duration = Date.now() - startTime;
|
|
6017
|
+
const enabledFeatures = [];
|
|
6018
|
+
if (emailConfig.tracking?.enabled) enabledFeatures.push("tracking");
|
|
6019
|
+
if (emailConfig.suppressionList?.enabled)
|
|
6020
|
+
enabledFeatures.push("suppression_list");
|
|
6021
|
+
if (emailConfig.eventTracking?.enabled)
|
|
6022
|
+
enabledFeatures.push("event_tracking");
|
|
6023
|
+
if (emailConfig.eventTracking?.dynamoDBHistory)
|
|
6024
|
+
enabledFeatures.push("dynamodb_history");
|
|
6025
|
+
if (emailConfig.dedicatedIp) enabledFeatures.push("dedicated_ip");
|
|
6026
|
+
if (emailConfig.emailArchiving?.enabled)
|
|
6027
|
+
enabledFeatures.push("email_archiving");
|
|
6028
|
+
trackServiceInit("email", true, {
|
|
6029
|
+
preset,
|
|
6030
|
+
provider,
|
|
6031
|
+
features: enabledFeatures,
|
|
6032
|
+
duration_ms: duration
|
|
6033
|
+
});
|
|
6034
|
+
trackServiceDeployed("email", {
|
|
6035
|
+
duration_ms: duration,
|
|
6036
|
+
features: enabledFeatures,
|
|
6037
|
+
preset
|
|
6038
|
+
});
|
|
5680
6039
|
}
|
|
5681
6040
|
|
|
5682
6041
|
// src/commands/email/restore.ts
|
|
5683
6042
|
init_esm_shims();
|
|
6043
|
+
init_events();
|
|
5684
6044
|
init_aws();
|
|
5685
6045
|
import * as clack8 from "@clack/prompts";
|
|
5686
6046
|
import * as pulumi8 from "@pulumi/pulumi";
|
|
5687
6047
|
import pc9 from "picocolors";
|
|
5688
6048
|
async function restore(options) {
|
|
5689
|
-
|
|
6049
|
+
const startTime = Date.now();
|
|
6050
|
+
clack8.intro(
|
|
6051
|
+
pc9.bold(
|
|
6052
|
+
options.preview ? "Wraps Restore Preview" : "Wraps Restore - Remove Wraps Infrastructure"
|
|
6053
|
+
)
|
|
6054
|
+
);
|
|
5690
6055
|
clack8.log.info(
|
|
5691
6056
|
`${pc9.yellow("Note:")} This will remove all Wraps-managed infrastructure.`
|
|
5692
6057
|
);
|
|
@@ -5733,7 +6098,7 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
5733
6098
|
}
|
|
5734
6099
|
console.log(` ${pc9.cyan("\u2713")} IAM Role (wraps-email-role)`);
|
|
5735
6100
|
console.log("");
|
|
5736
|
-
if (!options.force) {
|
|
6101
|
+
if (!(options.force || options.preview)) {
|
|
5737
6102
|
const confirmed = await clack8.confirm({
|
|
5738
6103
|
message: "Proceed with removal? This cannot be undone.",
|
|
5739
6104
|
initialValue: false
|
|
@@ -5743,6 +6108,55 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
5743
6108
|
process.exit(0);
|
|
5744
6109
|
}
|
|
5745
6110
|
}
|
|
6111
|
+
if (options.preview) {
|
|
6112
|
+
if (metadata.services.email?.pulumiStackName) {
|
|
6113
|
+
try {
|
|
6114
|
+
const previewResult = await progress.execute(
|
|
6115
|
+
"Generating removal preview",
|
|
6116
|
+
async () => {
|
|
6117
|
+
const stack = await pulumi8.automation.LocalWorkspace.selectStack(
|
|
6118
|
+
{
|
|
6119
|
+
stackName: metadata.services.email.pulumiStackName,
|
|
6120
|
+
projectName: "wraps-email",
|
|
6121
|
+
program: async () => {
|
|
6122
|
+
}
|
|
6123
|
+
// Empty program for destroy
|
|
6124
|
+
},
|
|
6125
|
+
{
|
|
6126
|
+
workDir: getPulumiWorkDir(),
|
|
6127
|
+
envVars: {
|
|
6128
|
+
PULUMI_CONFIG_PASSPHRASE: "",
|
|
6129
|
+
AWS_REGION: region
|
|
6130
|
+
},
|
|
6131
|
+
secretsProvider: "passphrase"
|
|
6132
|
+
}
|
|
6133
|
+
);
|
|
6134
|
+
const result = await stack.preview({ diff: true });
|
|
6135
|
+
return result;
|
|
6136
|
+
}
|
|
6137
|
+
);
|
|
6138
|
+
displayPreview({
|
|
6139
|
+
changeSummary: previewResult.changeSummary,
|
|
6140
|
+
costEstimate: "Monthly cost after removal: $0.00",
|
|
6141
|
+
commandName: "wraps email restore"
|
|
6142
|
+
});
|
|
6143
|
+
clack8.outro(
|
|
6144
|
+
pc9.green(
|
|
6145
|
+
"Preview complete. Run without --preview to remove infrastructure."
|
|
6146
|
+
)
|
|
6147
|
+
);
|
|
6148
|
+
trackServiceRemoved("email", {
|
|
6149
|
+
preview: true,
|
|
6150
|
+
duration_ms: Date.now() - startTime
|
|
6151
|
+
});
|
|
6152
|
+
return;
|
|
6153
|
+
} catch (error) {
|
|
6154
|
+
trackError("PREVIEW_FAILED", "email:restore", { step: "preview" });
|
|
6155
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
6156
|
+
}
|
|
6157
|
+
}
|
|
6158
|
+
return;
|
|
6159
|
+
}
|
|
5746
6160
|
if (metadata.services.email?.pulumiStackName) {
|
|
5747
6161
|
await progress.execute("Removing Wraps infrastructure", async () => {
|
|
5748
6162
|
try {
|
|
@@ -5772,6 +6186,7 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
5772
6186
|
metadata.services.email.pulumiStackName
|
|
5773
6187
|
);
|
|
5774
6188
|
} catch (error) {
|
|
6189
|
+
trackError("DESTROY_FAILED", "email:restore", { step: "destroy" });
|
|
5775
6190
|
throw new Error(`Failed to destroy Pulumi stack: ${error.message}`);
|
|
5776
6191
|
}
|
|
5777
6192
|
});
|
|
@@ -5788,6 +6203,10 @@ ${pc9.green("\u2713")} ${pc9.bold("Infrastructure removed successfully!")}
|
|
|
5788
6203
|
);
|
|
5789
6204
|
console.log(`${pc9.dim("Your original AWS resources remain unchanged.")}
|
|
5790
6205
|
`);
|
|
6206
|
+
trackServiceRemoved("email", {
|
|
6207
|
+
reason: "user_initiated",
|
|
6208
|
+
duration_ms: Date.now() - startTime
|
|
6209
|
+
});
|
|
5791
6210
|
}
|
|
5792
6211
|
|
|
5793
6212
|
// src/commands/email/upgrade.ts
|
|
@@ -5795,13 +6214,20 @@ init_esm_shims();
|
|
|
5795
6214
|
import * as clack9 from "@clack/prompts";
|
|
5796
6215
|
import * as pulumi9 from "@pulumi/pulumi";
|
|
5797
6216
|
import pc10 from "picocolors";
|
|
6217
|
+
init_events();
|
|
5798
6218
|
init_costs();
|
|
5799
6219
|
init_presets();
|
|
5800
6220
|
init_aws();
|
|
5801
6221
|
init_errors();
|
|
5802
6222
|
init_prompts();
|
|
5803
6223
|
async function upgrade(options) {
|
|
5804
|
-
|
|
6224
|
+
const startTime = Date.now();
|
|
6225
|
+
let upgradeAction = "";
|
|
6226
|
+
clack9.intro(
|
|
6227
|
+
pc10.bold(
|
|
6228
|
+
options.preview ? "Wraps Upgrade Preview" : "Wraps Upgrade - Enhance Your Email Infrastructure"
|
|
6229
|
+
)
|
|
6230
|
+
);
|
|
5805
6231
|
const progress = new DeploymentProgress();
|
|
5806
6232
|
const wasAutoInstalled = await progress.execute(
|
|
5807
6233
|
"Checking Pulumi CLI installation",
|
|
@@ -5903,7 +6329,7 @@ ${pc10.bold("Current Configuration:")}
|
|
|
5903
6329
|
Estimated Cost: ${pc10.cyan(`~${formatCost(currentCostData.total.monthly)}/mo`)}`
|
|
5904
6330
|
);
|
|
5905
6331
|
console.log("");
|
|
5906
|
-
|
|
6332
|
+
upgradeAction = await clack9.select({
|
|
5907
6333
|
message: "What would you like to do?",
|
|
5908
6334
|
options: [
|
|
5909
6335
|
{
|
|
@@ -6431,7 +6857,7 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6431
6857
|
);
|
|
6432
6858
|
}
|
|
6433
6859
|
console.log("");
|
|
6434
|
-
if (!options.yes) {
|
|
6860
|
+
if (!(options.yes || options.preview)) {
|
|
6435
6861
|
const confirmed = await clack9.confirm({
|
|
6436
6862
|
message: "Proceed with upgrade?",
|
|
6437
6863
|
initialValue: true
|
|
@@ -6453,6 +6879,81 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6453
6879
|
vercel: vercelConfig,
|
|
6454
6880
|
emailConfig: updatedConfig
|
|
6455
6881
|
};
|
|
6882
|
+
if (options.preview) {
|
|
6883
|
+
try {
|
|
6884
|
+
const previewResult = await progress.execute(
|
|
6885
|
+
"Generating upgrade preview",
|
|
6886
|
+
async () => {
|
|
6887
|
+
await ensurePulumiWorkDir();
|
|
6888
|
+
const stack = await pulumi9.automation.LocalWorkspace.createOrSelectStack(
|
|
6889
|
+
{
|
|
6890
|
+
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
6891
|
+
projectName: "wraps-email",
|
|
6892
|
+
program: async () => {
|
|
6893
|
+
const result2 = await deployEmailStack(stackConfig);
|
|
6894
|
+
return {
|
|
6895
|
+
roleArn: result2.roleArn,
|
|
6896
|
+
configSetName: result2.configSetName,
|
|
6897
|
+
tableName: result2.tableName,
|
|
6898
|
+
region: result2.region,
|
|
6899
|
+
lambdaFunctions: result2.lambdaFunctions,
|
|
6900
|
+
domain: result2.domain,
|
|
6901
|
+
dkimTokens: result2.dkimTokens,
|
|
6902
|
+
customTrackingDomain: result2.customTrackingDomain,
|
|
6903
|
+
httpsTrackingEnabled: result2.httpsTrackingEnabled,
|
|
6904
|
+
cloudFrontDomain: result2.cloudFrontDomain,
|
|
6905
|
+
acmCertificateValidationRecords: result2.acmCertificateValidationRecords,
|
|
6906
|
+
archiveArn: result2.archiveArn,
|
|
6907
|
+
archivingEnabled: result2.archivingEnabled,
|
|
6908
|
+
archiveRetention: result2.archiveRetention
|
|
6909
|
+
};
|
|
6910
|
+
}
|
|
6911
|
+
},
|
|
6912
|
+
{
|
|
6913
|
+
workDir: getPulumiWorkDir(),
|
|
6914
|
+
envVars: {
|
|
6915
|
+
PULUMI_CONFIG_PASSPHRASE: "",
|
|
6916
|
+
AWS_REGION: region
|
|
6917
|
+
},
|
|
6918
|
+
secretsProvider: "passphrase"
|
|
6919
|
+
}
|
|
6920
|
+
);
|
|
6921
|
+
await stack.setConfig("aws:region", { value: region });
|
|
6922
|
+
await stack.refresh({ onOutput: () => {
|
|
6923
|
+
} });
|
|
6924
|
+
const result = await stack.preview({ diff: true });
|
|
6925
|
+
return result;
|
|
6926
|
+
}
|
|
6927
|
+
);
|
|
6928
|
+
const costComparison = [
|
|
6929
|
+
`Current: ${formatCost(currentCostData.total.monthly)}/mo`,
|
|
6930
|
+
`After upgrade: ${formatCost(newCostData.total.monthly)}/mo`,
|
|
6931
|
+
costDiff > 0 ? `Change: +${formatCost(costDiff)}/mo` : costDiff < 0 ? `Change: -${formatCost(Math.abs(costDiff))}/mo` : "Change: No cost difference"
|
|
6932
|
+
].join("\n");
|
|
6933
|
+
displayPreview({
|
|
6934
|
+
changeSummary: previewResult.changeSummary,
|
|
6935
|
+
costEstimate: costComparison,
|
|
6936
|
+
commandName: "wraps email upgrade"
|
|
6937
|
+
});
|
|
6938
|
+
clack9.outro(
|
|
6939
|
+
pc10.green("Preview complete. Run without --preview to upgrade.")
|
|
6940
|
+
);
|
|
6941
|
+
trackServiceUpgrade("email", {
|
|
6942
|
+
from_preset: metadata.services.email?.preset,
|
|
6943
|
+
to_preset: newPreset,
|
|
6944
|
+
preview: true,
|
|
6945
|
+
action: typeof upgradeAction === "string" ? upgradeAction : void 0,
|
|
6946
|
+
duration_ms: Date.now() - startTime
|
|
6947
|
+
});
|
|
6948
|
+
return;
|
|
6949
|
+
} catch (error) {
|
|
6950
|
+
trackError("PREVIEW_FAILED", "email:upgrade", { step: "preview" });
|
|
6951
|
+
if (error.message?.includes("stack is currently locked")) {
|
|
6952
|
+
throw errors.stackLocked();
|
|
6953
|
+
}
|
|
6954
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
6955
|
+
}
|
|
6956
|
+
}
|
|
6456
6957
|
let outputs;
|
|
6457
6958
|
try {
|
|
6458
6959
|
outputs = await progress.execute(
|
|
@@ -6520,9 +7021,17 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6520
7021
|
}
|
|
6521
7022
|
);
|
|
6522
7023
|
} catch (error) {
|
|
7024
|
+
trackServiceUpgrade("email", {
|
|
7025
|
+
from_preset: metadata.services.email?.preset,
|
|
7026
|
+
to_preset: newPreset,
|
|
7027
|
+
action: typeof upgradeAction === "string" ? upgradeAction : void 0,
|
|
7028
|
+
duration_ms: Date.now() - startTime
|
|
7029
|
+
});
|
|
6523
7030
|
if (error.message?.includes("stack is currently locked")) {
|
|
7031
|
+
trackError("STACK_LOCKED", "email:upgrade", { step: "deploy" });
|
|
6524
7032
|
throw errors.stackLocked();
|
|
6525
7033
|
}
|
|
7034
|
+
trackError("UPGRADE_FAILED", "email:upgrade", { step: "deploy" });
|
|
6526
7035
|
throw new Error(`Pulumi upgrade failed: ${error.message}`);
|
|
6527
7036
|
}
|
|
6528
7037
|
if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
|
|
@@ -6627,6 +7136,24 @@ ${pc10.green("\u2713")} ${pc10.bold("Upgrade complete!")}
|
|
|
6627
7136
|
pc10.green("\u2713") + " " + pc10.bold("HTTPS tracking is fully configured and ready to use!\n")
|
|
6628
7137
|
);
|
|
6629
7138
|
}
|
|
7139
|
+
const enabledFeatures = [];
|
|
7140
|
+
if (updatedConfig.tracking?.enabled) enabledFeatures.push("tracking");
|
|
7141
|
+
if (updatedConfig.suppressionList?.enabled)
|
|
7142
|
+
enabledFeatures.push("suppression_list");
|
|
7143
|
+
if (updatedConfig.eventTracking?.enabled)
|
|
7144
|
+
enabledFeatures.push("event_tracking");
|
|
7145
|
+
if (updatedConfig.eventTracking?.dynamoDBHistory)
|
|
7146
|
+
enabledFeatures.push("dynamodb_history");
|
|
7147
|
+
if (updatedConfig.dedicatedIp) enabledFeatures.push("dedicated_ip");
|
|
7148
|
+
if (updatedConfig.emailArchiving?.enabled)
|
|
7149
|
+
enabledFeatures.push("email_archiving");
|
|
7150
|
+
trackServiceUpgrade("email", {
|
|
7151
|
+
from_preset: metadata.services.email?.preset,
|
|
7152
|
+
to_preset: newPreset,
|
|
7153
|
+
added_features: enabledFeatures,
|
|
7154
|
+
action: typeof upgradeAction === "string" ? upgradeAction : void 0,
|
|
7155
|
+
duration_ms: Date.now() - startTime
|
|
7156
|
+
});
|
|
6630
7157
|
}
|
|
6631
7158
|
|
|
6632
7159
|
// src/commands/shared/dashboard.ts
|
|
@@ -7857,6 +8384,7 @@ async function startConsoleServer(config2) {
|
|
|
7857
8384
|
}
|
|
7858
8385
|
|
|
7859
8386
|
// src/commands/shared/dashboard.ts
|
|
8387
|
+
init_events();
|
|
7860
8388
|
init_aws();
|
|
7861
8389
|
async function dashboard(options) {
|
|
7862
8390
|
clack10.intro(pc11.bold("Wraps Dashboard"));
|
|
@@ -7907,25 +8435,36 @@ async function dashboard(options) {
|
|
|
7907
8435
|
if (!options.noOpen) {
|
|
7908
8436
|
await open(url);
|
|
7909
8437
|
}
|
|
8438
|
+
trackCommand("console", {
|
|
8439
|
+
success: true,
|
|
8440
|
+
port,
|
|
8441
|
+
no_open: options.noOpen ?? false
|
|
8442
|
+
});
|
|
7910
8443
|
await new Promise(() => {
|
|
7911
8444
|
});
|
|
7912
8445
|
}
|
|
7913
8446
|
|
|
7914
8447
|
// src/commands/shared/destroy.ts
|
|
7915
8448
|
init_esm_shims();
|
|
8449
|
+
init_events();
|
|
7916
8450
|
init_aws();
|
|
7917
8451
|
import * as clack11 from "@clack/prompts";
|
|
7918
8452
|
import * as pulumi11 from "@pulumi/pulumi";
|
|
7919
8453
|
import pc12 from "picocolors";
|
|
7920
8454
|
async function destroy(options) {
|
|
7921
|
-
|
|
8455
|
+
const startTime = Date.now();
|
|
8456
|
+
clack11.intro(
|
|
8457
|
+
pc12.bold(
|
|
8458
|
+
options.preview ? "Wraps Destruction Preview" : "Wraps Email Infrastructure Teardown"
|
|
8459
|
+
)
|
|
8460
|
+
);
|
|
7922
8461
|
const progress = new DeploymentProgress();
|
|
7923
8462
|
const identity = await progress.execute(
|
|
7924
8463
|
"Validating AWS credentials",
|
|
7925
8464
|
async () => validateAWSCredentials()
|
|
7926
8465
|
);
|
|
7927
8466
|
const region = await getAWSRegion();
|
|
7928
|
-
if (!options.force) {
|
|
8467
|
+
if (!(options.force || options.preview)) {
|
|
7929
8468
|
const confirmed = await clack11.confirm({
|
|
7930
8469
|
message: pc12.red(
|
|
7931
8470
|
"Are you sure you want to destroy all Wraps infrastructure?"
|
|
@@ -7937,6 +8476,49 @@ async function destroy(options) {
|
|
|
7937
8476
|
process.exit(0);
|
|
7938
8477
|
}
|
|
7939
8478
|
}
|
|
8479
|
+
if (options.preview) {
|
|
8480
|
+
try {
|
|
8481
|
+
const previewResult = await progress.execute(
|
|
8482
|
+
"Generating destruction preview",
|
|
8483
|
+
async () => {
|
|
8484
|
+
await ensurePulumiWorkDir();
|
|
8485
|
+
const stackName = `wraps-${identity.accountId}-${region}`;
|
|
8486
|
+
let stack;
|
|
8487
|
+
try {
|
|
8488
|
+
stack = await pulumi11.automation.LocalWorkspace.selectStack({
|
|
8489
|
+
stackName,
|
|
8490
|
+
workDir: getPulumiWorkDir()
|
|
8491
|
+
});
|
|
8492
|
+
} catch (_error) {
|
|
8493
|
+
throw new Error("No Wraps infrastructure found to preview");
|
|
8494
|
+
}
|
|
8495
|
+
const result = await stack.preview({ diff: true });
|
|
8496
|
+
return result;
|
|
8497
|
+
}
|
|
8498
|
+
);
|
|
8499
|
+
displayPreview({
|
|
8500
|
+
changeSummary: previewResult.changeSummary,
|
|
8501
|
+
costEstimate: "Monthly cost after destruction: $0.00",
|
|
8502
|
+
commandName: "wraps destroy"
|
|
8503
|
+
});
|
|
8504
|
+
clack11.outro(
|
|
8505
|
+
pc12.green("Preview complete. Run without --preview to destroy.")
|
|
8506
|
+
);
|
|
8507
|
+
trackServiceRemoved("email", {
|
|
8508
|
+
preview: true,
|
|
8509
|
+
duration_ms: Date.now() - startTime
|
|
8510
|
+
});
|
|
8511
|
+
return;
|
|
8512
|
+
} catch (error) {
|
|
8513
|
+
progress.stop();
|
|
8514
|
+
if (error.message.includes("No Wraps infrastructure found")) {
|
|
8515
|
+
clack11.log.warn("No Wraps infrastructure found to preview");
|
|
8516
|
+
process.exit(0);
|
|
8517
|
+
}
|
|
8518
|
+
trackError("PREVIEW_FAILED", "destroy", { step: "preview" });
|
|
8519
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
8520
|
+
}
|
|
8521
|
+
}
|
|
7940
8522
|
try {
|
|
7941
8523
|
await progress.execute(
|
|
7942
8524
|
"Destroying infrastructure (this may take 2-3 minutes)",
|
|
@@ -7964,6 +8546,7 @@ async function destroy(options) {
|
|
|
7964
8546
|
await deleteConnectionMetadata(identity.accountId, region);
|
|
7965
8547
|
process.exit(0);
|
|
7966
8548
|
}
|
|
8549
|
+
trackError("DESTROY_FAILED", "destroy", { step: "destroy" });
|
|
7967
8550
|
clack11.log.error("Infrastructure destruction failed");
|
|
7968
8551
|
throw error;
|
|
7969
8552
|
}
|
|
@@ -7975,15 +8558,21 @@ async function destroy(options) {
|
|
|
7975
8558
|
Run ${pc12.cyan("wraps email init")} to deploy infrastructure again.
|
|
7976
8559
|
`
|
|
7977
8560
|
);
|
|
8561
|
+
trackServiceRemoved("email", {
|
|
8562
|
+
reason: "user_initiated",
|
|
8563
|
+
duration_ms: Date.now() - startTime
|
|
8564
|
+
});
|
|
7978
8565
|
}
|
|
7979
8566
|
|
|
7980
8567
|
// src/commands/shared/status.ts
|
|
7981
8568
|
init_esm_shims();
|
|
8569
|
+
init_events();
|
|
7982
8570
|
init_aws();
|
|
7983
8571
|
import * as clack12 from "@clack/prompts";
|
|
7984
8572
|
import * as pulumi12 from "@pulumi/pulumi";
|
|
7985
8573
|
import pc13 from "picocolors";
|
|
7986
8574
|
async function status(_options) {
|
|
8575
|
+
const startTime = Date.now();
|
|
7987
8576
|
const progress = new DeploymentProgress();
|
|
7988
8577
|
const identity = await progress.execute(
|
|
7989
8578
|
"Loading infrastructure status",
|
|
@@ -8057,6 +8646,12 @@ Run ${pc13.cyan("wraps email init")} to deploy infrastructure.
|
|
|
8057
8646
|
cloudFrontDomain: stackOutputs.cloudFrontDomain?.value
|
|
8058
8647
|
} : void 0
|
|
8059
8648
|
});
|
|
8649
|
+
trackCommand("status", {
|
|
8650
|
+
success: true,
|
|
8651
|
+
domain_count: domainsWithTokens.length,
|
|
8652
|
+
integration_level: integrationLevel,
|
|
8653
|
+
duration_ms: Date.now() - startTime
|
|
8654
|
+
});
|
|
8060
8655
|
}
|
|
8061
8656
|
|
|
8062
8657
|
// src/commands/telemetry.ts
|
|
@@ -8207,6 +8802,9 @@ function showHelp() {
|
|
|
8207
8802
|
console.log(` ${pc15.dim("--preset")} Configuration preset`);
|
|
8208
8803
|
console.log(` ${pc15.dim("-y, --yes")} Skip confirmation prompts`);
|
|
8209
8804
|
console.log(` ${pc15.dim("-f, --force")} Force destructive operations`);
|
|
8805
|
+
console.log(
|
|
8806
|
+
` ${pc15.dim("--preview")} Preview changes without deploying`
|
|
8807
|
+
);
|
|
8210
8808
|
console.log(` ${pc15.dim("-v, --version")} Show version number
|
|
8211
8809
|
`);
|
|
8212
8810
|
console.log(
|
|
@@ -8266,6 +8864,11 @@ args.options([
|
|
|
8266
8864
|
name: "noOpen",
|
|
8267
8865
|
description: "Don't open browser automatically",
|
|
8268
8866
|
defaultValue: false
|
|
8867
|
+
},
|
|
8868
|
+
{
|
|
8869
|
+
name: "preview",
|
|
8870
|
+
description: "Preview changes without deploying",
|
|
8871
|
+
defaultValue: false
|
|
8269
8872
|
}
|
|
8270
8873
|
]);
|
|
8271
8874
|
var flags = args.parse(process.argv);
|
|
@@ -8327,13 +8930,15 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
|
|
|
8327
8930
|
region: flags.region,
|
|
8328
8931
|
domain: flags.domain,
|
|
8329
8932
|
preset: flags.preset,
|
|
8330
|
-
yes: flags.yes
|
|
8933
|
+
yes: flags.yes,
|
|
8934
|
+
preview: flags.preview
|
|
8331
8935
|
});
|
|
8332
8936
|
} else {
|
|
8333
8937
|
await connect({
|
|
8334
8938
|
provider: flags.provider,
|
|
8335
8939
|
region: flags.region,
|
|
8336
|
-
yes: flags.yes
|
|
8940
|
+
yes: flags.yes,
|
|
8941
|
+
preview: flags.preview
|
|
8337
8942
|
});
|
|
8338
8943
|
}
|
|
8339
8944
|
}
|
|
@@ -8371,33 +8976,38 @@ async function run() {
|
|
|
8371
8976
|
region: flags.region,
|
|
8372
8977
|
domain: flags.domain,
|
|
8373
8978
|
preset: flags.preset,
|
|
8374
|
-
yes: flags.yes
|
|
8979
|
+
yes: flags.yes,
|
|
8980
|
+
preview: flags.preview
|
|
8375
8981
|
});
|
|
8376
8982
|
break;
|
|
8377
8983
|
case "connect":
|
|
8378
8984
|
await connect({
|
|
8379
8985
|
provider: flags.provider,
|
|
8380
8986
|
region: flags.region,
|
|
8381
|
-
yes: flags.yes
|
|
8987
|
+
yes: flags.yes,
|
|
8988
|
+
preview: flags.preview
|
|
8382
8989
|
});
|
|
8383
8990
|
break;
|
|
8384
8991
|
case "config":
|
|
8385
8992
|
case "sync":
|
|
8386
8993
|
await config({
|
|
8387
8994
|
region: flags.region,
|
|
8388
|
-
yes: flags.yes
|
|
8995
|
+
yes: flags.yes,
|
|
8996
|
+
preview: flags.preview
|
|
8389
8997
|
});
|
|
8390
8998
|
break;
|
|
8391
8999
|
case "upgrade":
|
|
8392
9000
|
await upgrade({
|
|
8393
9001
|
region: flags.region,
|
|
8394
|
-
yes: flags.yes
|
|
9002
|
+
yes: flags.yes,
|
|
9003
|
+
preview: flags.preview
|
|
8395
9004
|
});
|
|
8396
9005
|
break;
|
|
8397
9006
|
case "restore":
|
|
8398
9007
|
await restore({
|
|
8399
9008
|
region: flags.region,
|
|
8400
|
-
force: flags.force
|
|
9009
|
+
force: flags.force,
|
|
9010
|
+
preview: flags.preview
|
|
8401
9011
|
});
|
|
8402
9012
|
break;
|
|
8403
9013
|
case "domains": {
|
|
@@ -8483,6 +9093,13 @@ Run ${pc15.cyan("wraps --help")} for available commands.
|
|
|
8483
9093
|
);
|
|
8484
9094
|
process.exit(1);
|
|
8485
9095
|
}
|
|
9096
|
+
const emailDuration = Date.now() - startTime;
|
|
9097
|
+
const emailCommandName = `email:${subCommand}`;
|
|
9098
|
+
trackCommand(emailCommandName, {
|
|
9099
|
+
success: true,
|
|
9100
|
+
duration_ms: emailDuration,
|
|
9101
|
+
service: "email"
|
|
9102
|
+
});
|
|
8486
9103
|
return;
|
|
8487
9104
|
}
|
|
8488
9105
|
if (primaryCommand === "dashboard" && subCommand) {
|
|
@@ -8502,6 +9119,12 @@ Available commands: ${pc15.cyan("update-role")}
|
|
|
8502
9119
|
`);
|
|
8503
9120
|
process.exit(1);
|
|
8504
9121
|
}
|
|
9122
|
+
const dashboardDuration = Date.now() - startTime;
|
|
9123
|
+
const dashboardCommandName = `dashboard:${subCommand}`;
|
|
9124
|
+
trackCommand(dashboardCommandName, {
|
|
9125
|
+
success: true,
|
|
9126
|
+
duration_ms: dashboardDuration
|
|
9127
|
+
});
|
|
8505
9128
|
return;
|
|
8506
9129
|
}
|
|
8507
9130
|
if (primaryCommand === "sms" && subCommand) {
|
|
@@ -8539,7 +9162,8 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
|
|
|
8539
9162
|
break;
|
|
8540
9163
|
case "destroy":
|
|
8541
9164
|
await destroy({
|
|
8542
|
-
force: flags.force
|
|
9165
|
+
force: flags.force,
|
|
9166
|
+
preview: flags.preview
|
|
8543
9167
|
});
|
|
8544
9168
|
break;
|
|
8545
9169
|
case "completion":
|