@wraps.dev/cli 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +482 -67
- 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.0",
|
|
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
|
}
|
|
@@ -3192,6 +3198,35 @@ ${pc2.bold("Dashboard:")} ${pc2.blue("https://app.wraps.dev")}`);
|
|
|
3192
3198
|
console.log(`${pc2.bold("Docs:")} ${pc2.blue("https://wraps.dev/docs")}
|
|
3193
3199
|
`);
|
|
3194
3200
|
}
|
|
3201
|
+
function displayPreview(outputs) {
|
|
3202
|
+
console.log(pc2.yellow("\n--- PREVIEW MODE (no changes will be made) ---\n"));
|
|
3203
|
+
const changes = outputs.changeSummary;
|
|
3204
|
+
const summaryLines = [];
|
|
3205
|
+
if (changes.create && changes.create > 0) {
|
|
3206
|
+
summaryLines.push(` ${pc2.green("+")} ${changes.create} to create`);
|
|
3207
|
+
}
|
|
3208
|
+
if (changes.update && changes.update > 0) {
|
|
3209
|
+
summaryLines.push(` ${pc2.yellow("~")} ${changes.update} to update`);
|
|
3210
|
+
}
|
|
3211
|
+
if (changes.delete && changes.delete > 0) {
|
|
3212
|
+
summaryLines.push(` ${pc2.red("-")} ${changes.delete} to destroy`);
|
|
3213
|
+
}
|
|
3214
|
+
if (changes.same && changes.same > 0) {
|
|
3215
|
+
summaryLines.push(` ${pc2.dim("=")} ${changes.same} unchanged`);
|
|
3216
|
+
}
|
|
3217
|
+
if (changes.replace && changes.replace > 0) {
|
|
3218
|
+
summaryLines.push(` ${pc2.magenta("+-")} ${changes.replace} to replace`);
|
|
3219
|
+
}
|
|
3220
|
+
if (summaryLines.length > 0) {
|
|
3221
|
+
clack2.note(summaryLines.join("\n"), "Resource Changes");
|
|
3222
|
+
} else {
|
|
3223
|
+
clack2.note("No changes detected", "Resource Changes");
|
|
3224
|
+
}
|
|
3225
|
+
if (outputs.costEstimate) {
|
|
3226
|
+
clack2.note(outputs.costEstimate, "Estimated Monthly Cost");
|
|
3227
|
+
}
|
|
3228
|
+
console.log(pc2.yellow("\n--- END PREVIEW (no changes were made) ---\n"));
|
|
3229
|
+
}
|
|
3195
3230
|
|
|
3196
3231
|
// src/commands/dashboard/update-role.ts
|
|
3197
3232
|
async function updateRole(options) {
|
|
@@ -3792,7 +3827,7 @@ Try running: pnpm build`
|
|
|
3792
3827
|
entryPoints: [sourcePath],
|
|
3793
3828
|
bundle: true,
|
|
3794
3829
|
platform: "node",
|
|
3795
|
-
target: "
|
|
3830
|
+
target: "node24",
|
|
3796
3831
|
format: "esm",
|
|
3797
3832
|
outfile: join3(outdir, "index.mjs"),
|
|
3798
3833
|
external: ["@aws-sdk/*"],
|
|
@@ -3864,7 +3899,7 @@ async function deployLambdaFunctions(config2) {
|
|
|
3864
3899
|
functionName,
|
|
3865
3900
|
{
|
|
3866
3901
|
name: functionName,
|
|
3867
|
-
runtime:
|
|
3902
|
+
runtime: "nodejs24.x",
|
|
3868
3903
|
handler: "index.handler",
|
|
3869
3904
|
role: lambdaRole.arn,
|
|
3870
3905
|
code: new pulumi3.asset.FileArchive(eventProcessorCode),
|
|
@@ -3888,7 +3923,7 @@ async function deployLambdaFunctions(config2) {
|
|
|
3888
3923
|
}
|
|
3889
3924
|
) : new aws4.lambda.Function(functionName, {
|
|
3890
3925
|
name: functionName,
|
|
3891
|
-
runtime:
|
|
3926
|
+
runtime: "nodejs24.x",
|
|
3892
3927
|
handler: "index.handler",
|
|
3893
3928
|
role: lambdaRole.arn,
|
|
3894
3929
|
code: new pulumi3.asset.FileArchive(eventProcessorCode),
|
|
@@ -3957,6 +3992,23 @@ async function configurationSetExists(configSetName, region) {
|
|
|
3957
3992
|
return false;
|
|
3958
3993
|
}
|
|
3959
3994
|
}
|
|
3995
|
+
async function eventDestinationExists(configSetName, eventDestName, region) {
|
|
3996
|
+
try {
|
|
3997
|
+
const { SESv2Client: SESv2Client5, GetConfigurationSetEventDestinationsCommand } = await import("@aws-sdk/client-sesv2");
|
|
3998
|
+
const ses = new SESv2Client5({ region });
|
|
3999
|
+
const response = await ses.send(
|
|
4000
|
+
new GetConfigurationSetEventDestinationsCommand({
|
|
4001
|
+
ConfigurationSetName: configSetName
|
|
4002
|
+
})
|
|
4003
|
+
);
|
|
4004
|
+
return response.EventDestinations?.some((dest) => dest.Name === eventDestName) ?? false;
|
|
4005
|
+
} catch (error) {
|
|
4006
|
+
if (error.name === "NotFoundException") {
|
|
4007
|
+
return false;
|
|
4008
|
+
}
|
|
4009
|
+
return false;
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
3960
4012
|
async function emailIdentityExists(emailIdentity, region) {
|
|
3961
4013
|
try {
|
|
3962
4014
|
const { SESv2Client: SESv2Client5, GetEmailIdentityCommand: GetEmailIdentityCommand4 } = await import("@aws-sdk/client-sesv2");
|
|
@@ -4010,29 +4062,37 @@ async function createSESResources(config2) {
|
|
|
4010
4062
|
});
|
|
4011
4063
|
if (config2.eventTrackingEnabled) {
|
|
4012
4064
|
const eventDestName = "wraps-email-eventbridge";
|
|
4013
|
-
new aws5.sesv2.ConfigurationSetEventDestination(
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4065
|
+
new aws5.sesv2.ConfigurationSetEventDestination(
|
|
4066
|
+
"wraps-email-all-events",
|
|
4067
|
+
{
|
|
4068
|
+
configurationSetName: configSet.configurationSetName,
|
|
4069
|
+
eventDestinationName: eventDestName,
|
|
4070
|
+
eventDestination: {
|
|
4071
|
+
enabled: true,
|
|
4072
|
+
matchingEventTypes: [
|
|
4073
|
+
"SEND",
|
|
4074
|
+
"DELIVERY",
|
|
4075
|
+
"OPEN",
|
|
4076
|
+
"CLICK",
|
|
4077
|
+
"BOUNCE",
|
|
4078
|
+
"COMPLAINT",
|
|
4079
|
+
"REJECT",
|
|
4080
|
+
"RENDERING_FAILURE",
|
|
4081
|
+
"DELIVERY_DELAY",
|
|
4082
|
+
"SUBSCRIPTION"
|
|
4083
|
+
],
|
|
4084
|
+
eventBridgeDestination: {
|
|
4085
|
+
// SES requires default bus - cannot use custom bus
|
|
4086
|
+
eventBusArn: defaultEventBus.arn
|
|
4087
|
+
}
|
|
4033
4088
|
}
|
|
4089
|
+
},
|
|
4090
|
+
{
|
|
4091
|
+
// Import existing resource if it already exists in AWS
|
|
4092
|
+
// This prevents AlreadyExistsException when the resource exists but isn't in Pulumi state
|
|
4093
|
+
import: config2.importExistingEventDestination ? `wraps-email-tracking|${eventDestName}` : void 0
|
|
4034
4094
|
}
|
|
4035
|
-
|
|
4095
|
+
);
|
|
4036
4096
|
}
|
|
4037
4097
|
let domainIdentity;
|
|
4038
4098
|
let dkimTokens;
|
|
@@ -4244,6 +4304,11 @@ async function deployEmailStack(config2) {
|
|
|
4244
4304
|
}
|
|
4245
4305
|
let sesResources;
|
|
4246
4306
|
if (emailConfig.tracking?.enabled || emailConfig.eventTracking?.enabled) {
|
|
4307
|
+
const shouldImportEventDest = emailConfig.eventTracking?.enabled && await eventDestinationExists(
|
|
4308
|
+
"wraps-email-tracking",
|
|
4309
|
+
"wraps-email-eventbridge",
|
|
4310
|
+
config2.region
|
|
4311
|
+
);
|
|
4247
4312
|
sesResources = await createSESResources({
|
|
4248
4313
|
domain: emailConfig.domain,
|
|
4249
4314
|
mailFromDomain: emailConfig.mailFromDomain,
|
|
@@ -4252,8 +4317,10 @@ async function deployEmailStack(config2) {
|
|
|
4252
4317
|
eventTypes: emailConfig.eventTracking?.events,
|
|
4253
4318
|
eventTrackingEnabled: emailConfig.eventTracking?.enabled,
|
|
4254
4319
|
// Pass flag to create EventBridge destination
|
|
4255
|
-
tlsRequired: emailConfig.tlsRequired
|
|
4320
|
+
tlsRequired: emailConfig.tlsRequired,
|
|
4256
4321
|
// Require TLS encryption for all emails
|
|
4322
|
+
importExistingEventDestination: shouldImportEventDest
|
|
4323
|
+
// Import if exists to avoid AlreadyExistsException
|
|
4257
4324
|
});
|
|
4258
4325
|
}
|
|
4259
4326
|
let dynamoTables;
|
|
@@ -4318,6 +4385,7 @@ async function deployEmailStack(config2) {
|
|
|
4318
4385
|
|
|
4319
4386
|
// src/commands/email/config.ts
|
|
4320
4387
|
init_aws();
|
|
4388
|
+
init_errors();
|
|
4321
4389
|
|
|
4322
4390
|
// src/utils/shared/pulumi.ts
|
|
4323
4391
|
init_esm_shims();
|
|
@@ -4350,7 +4418,11 @@ async function ensurePulumiInstalled() {
|
|
|
4350
4418
|
|
|
4351
4419
|
// src/commands/email/config.ts
|
|
4352
4420
|
async function config(options) {
|
|
4353
|
-
clack3.intro(
|
|
4421
|
+
clack3.intro(
|
|
4422
|
+
pc4.bold(
|
|
4423
|
+
options.preview ? "Wraps Config Preview" : "Wraps Config - Apply CLI Updates to Infrastructure"
|
|
4424
|
+
)
|
|
4425
|
+
);
|
|
4354
4426
|
const progress = new DeploymentProgress();
|
|
4355
4427
|
const wasAutoInstalled = await progress.execute(
|
|
4356
4428
|
"Checking Pulumi CLI installation",
|
|
@@ -4427,7 +4499,7 @@ ${pc4.bold("Current Configuration:")}
|
|
|
4427
4499
|
"Your current configuration will be preserved - no features will be added or removed"
|
|
4428
4500
|
);
|
|
4429
4501
|
console.log("");
|
|
4430
|
-
if (!options.yes) {
|
|
4502
|
+
if (!(options.yes || options.preview)) {
|
|
4431
4503
|
const confirmed = await clack3.confirm({
|
|
4432
4504
|
message: "Proceed with update?",
|
|
4433
4505
|
initialValue: true
|
|
@@ -4447,6 +4519,61 @@ ${pc4.bold("Current Configuration:")}
|
|
|
4447
4519
|
vercel: vercelConfig,
|
|
4448
4520
|
emailConfig: config2
|
|
4449
4521
|
};
|
|
4522
|
+
if (options.preview) {
|
|
4523
|
+
try {
|
|
4524
|
+
const previewResult = await progress.execute(
|
|
4525
|
+
"Generating update preview",
|
|
4526
|
+
async () => {
|
|
4527
|
+
await ensurePulumiWorkDir();
|
|
4528
|
+
const stack = await pulumi5.automation.LocalWorkspace.createOrSelectStack(
|
|
4529
|
+
{
|
|
4530
|
+
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
4531
|
+
projectName: "wraps-email",
|
|
4532
|
+
program: async () => {
|
|
4533
|
+
const result2 = await deployEmailStack(stackConfig);
|
|
4534
|
+
return {
|
|
4535
|
+
roleArn: result2.roleArn,
|
|
4536
|
+
configSetName: result2.configSetName,
|
|
4537
|
+
tableName: result2.tableName,
|
|
4538
|
+
region: result2.region,
|
|
4539
|
+
lambdaFunctions: result2.lambdaFunctions,
|
|
4540
|
+
domain: result2.domain,
|
|
4541
|
+
dkimTokens: result2.dkimTokens,
|
|
4542
|
+
customTrackingDomain: result2.customTrackingDomain
|
|
4543
|
+
};
|
|
4544
|
+
}
|
|
4545
|
+
},
|
|
4546
|
+
{
|
|
4547
|
+
workDir: getPulumiWorkDir(),
|
|
4548
|
+
envVars: {
|
|
4549
|
+
PULUMI_CONFIG_PASSPHRASE: "",
|
|
4550
|
+
AWS_REGION: region
|
|
4551
|
+
},
|
|
4552
|
+
secretsProvider: "passphrase"
|
|
4553
|
+
}
|
|
4554
|
+
);
|
|
4555
|
+
await stack.setConfig("aws:region", { value: region });
|
|
4556
|
+
await stack.refresh({ onOutput: () => {
|
|
4557
|
+
} });
|
|
4558
|
+
const result = await stack.preview({ diff: true });
|
|
4559
|
+
return result;
|
|
4560
|
+
}
|
|
4561
|
+
);
|
|
4562
|
+
displayPreview({
|
|
4563
|
+
changeSummary: previewResult.changeSummary,
|
|
4564
|
+
commandName: "wraps email config"
|
|
4565
|
+
});
|
|
4566
|
+
clack3.outro(
|
|
4567
|
+
pc4.green("Preview complete. Run without --preview to update.")
|
|
4568
|
+
);
|
|
4569
|
+
return;
|
|
4570
|
+
} catch (error) {
|
|
4571
|
+
if (error.message?.includes("stack is currently locked")) {
|
|
4572
|
+
throw errors.stackLocked();
|
|
4573
|
+
}
|
|
4574
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4450
4577
|
let outputs;
|
|
4451
4578
|
try {
|
|
4452
4579
|
outputs = await progress.execute(
|
|
@@ -4484,6 +4611,8 @@ ${pc4.bold("Current Configuration:")}
|
|
|
4484
4611
|
metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`
|
|
4485
4612
|
);
|
|
4486
4613
|
await stack.setConfig("aws:region", { value: region });
|
|
4614
|
+
await stack.refresh({ onOutput: () => {
|
|
4615
|
+
} });
|
|
4487
4616
|
const upResult = await stack.up({ onOutput: () => {
|
|
4488
4617
|
} });
|
|
4489
4618
|
const pulumiOutputs = upResult.outputs;
|
|
@@ -4500,12 +4629,8 @@ ${pc4.bold("Current Configuration:")}
|
|
|
4500
4629
|
}
|
|
4501
4630
|
);
|
|
4502
4631
|
} catch (error) {
|
|
4503
|
-
clack3.log.error("Infrastructure update failed");
|
|
4504
4632
|
if (error.message?.includes("stack is currently locked")) {
|
|
4505
|
-
|
|
4506
|
-
clack3.log.info("To fix this, run:");
|
|
4507
|
-
clack3.log.info(` ${pc4.cyan("rm -rf ~/.wraps/pulumi/.pulumi/locks")}`);
|
|
4508
|
-
clack3.log.info("\nThen try running wraps update again.");
|
|
4633
|
+
throw errors.stackLocked();
|
|
4509
4634
|
}
|
|
4510
4635
|
throw new Error(`Pulumi update failed: ${error.message}`);
|
|
4511
4636
|
}
|
|
@@ -4546,6 +4671,7 @@ import * as pulumi6 from "@pulumi/pulumi";
|
|
|
4546
4671
|
import pc6 from "picocolors";
|
|
4547
4672
|
init_presets();
|
|
4548
4673
|
init_aws();
|
|
4674
|
+
init_errors();
|
|
4549
4675
|
init_prompts();
|
|
4550
4676
|
|
|
4551
4677
|
// src/utils/shared/scanner.ts
|
|
@@ -4776,7 +4902,11 @@ async function scanAWSResources(region) {
|
|
|
4776
4902
|
|
|
4777
4903
|
// src/commands/email/connect.ts
|
|
4778
4904
|
async function connect(options) {
|
|
4779
|
-
clack5.intro(
|
|
4905
|
+
clack5.intro(
|
|
4906
|
+
pc6.bold(
|
|
4907
|
+
options.preview ? "Wraps Connect Preview" : "Wraps Connect - Link Existing Infrastructure"
|
|
4908
|
+
)
|
|
4909
|
+
);
|
|
4780
4910
|
const progress = new DeploymentProgress();
|
|
4781
4911
|
const wasAutoInstalled = await progress.execute(
|
|
4782
4912
|
"Checking Pulumi CLI installation",
|
|
@@ -4854,7 +4984,7 @@ async function connect(options) {
|
|
|
4854
4984
|
if (domainIdentities.length > 0) {
|
|
4855
4985
|
emailConfig.domain = domainIdentities[0];
|
|
4856
4986
|
}
|
|
4857
|
-
if (!options.yes) {
|
|
4987
|
+
if (!(options.yes || options.preview)) {
|
|
4858
4988
|
const confirmed = await confirmConnect();
|
|
4859
4989
|
if (!confirmed) {
|
|
4860
4990
|
clack5.cancel("Connection cancelled.");
|
|
@@ -4867,6 +4997,59 @@ async function connect(options) {
|
|
|
4867
4997
|
vercel: vercelConfig,
|
|
4868
4998
|
emailConfig
|
|
4869
4999
|
};
|
|
5000
|
+
if (options.preview) {
|
|
5001
|
+
try {
|
|
5002
|
+
const previewResult = await progress.execute(
|
|
5003
|
+
"Generating infrastructure preview",
|
|
5004
|
+
async () => {
|
|
5005
|
+
await ensurePulumiWorkDir();
|
|
5006
|
+
const stack = await pulumi6.automation.LocalWorkspace.createOrSelectStack(
|
|
5007
|
+
{
|
|
5008
|
+
stackName: `wraps-${identity.accountId}-${region}`,
|
|
5009
|
+
projectName: "wraps-email",
|
|
5010
|
+
program: async () => {
|
|
5011
|
+
const result2 = await deployEmailStack(stackConfig);
|
|
5012
|
+
return {
|
|
5013
|
+
roleArn: result2.roleArn,
|
|
5014
|
+
configSetName: result2.configSetName,
|
|
5015
|
+
tableName: result2.tableName,
|
|
5016
|
+
region: result2.region,
|
|
5017
|
+
lambdaFunctions: result2.lambdaFunctions,
|
|
5018
|
+
domain: result2.domain,
|
|
5019
|
+
dkimTokens: result2.dkimTokens,
|
|
5020
|
+
customTrackingDomain: result2.customTrackingDomain
|
|
5021
|
+
};
|
|
5022
|
+
}
|
|
5023
|
+
},
|
|
5024
|
+
{
|
|
5025
|
+
workDir: getPulumiWorkDir(),
|
|
5026
|
+
envVars: {
|
|
5027
|
+
PULUMI_CONFIG_PASSPHRASE: "",
|
|
5028
|
+
AWS_REGION: region
|
|
5029
|
+
},
|
|
5030
|
+
secretsProvider: "passphrase"
|
|
5031
|
+
}
|
|
5032
|
+
);
|
|
5033
|
+
await stack.setConfig("aws:region", { value: region });
|
|
5034
|
+
const result = await stack.preview({ diff: true });
|
|
5035
|
+
return result;
|
|
5036
|
+
}
|
|
5037
|
+
);
|
|
5038
|
+
displayPreview({
|
|
5039
|
+
changeSummary: previewResult.changeSummary,
|
|
5040
|
+
commandName: "wraps email connect"
|
|
5041
|
+
});
|
|
5042
|
+
clack5.outro(
|
|
5043
|
+
pc6.green("Preview complete. Run without --preview to connect.")
|
|
5044
|
+
);
|
|
5045
|
+
return;
|
|
5046
|
+
} catch (error) {
|
|
5047
|
+
if (error.message?.includes("stack is currently locked")) {
|
|
5048
|
+
throw errors.stackLocked();
|
|
5049
|
+
}
|
|
5050
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
5051
|
+
}
|
|
5052
|
+
}
|
|
4870
5053
|
let outputs;
|
|
4871
5054
|
try {
|
|
4872
5055
|
outputs = await progress.execute(
|
|
@@ -4920,12 +5103,8 @@ async function connect(options) {
|
|
|
4920
5103
|
}
|
|
4921
5104
|
);
|
|
4922
5105
|
} catch (error) {
|
|
4923
|
-
clack5.log.error("Infrastructure deployment failed");
|
|
4924
5106
|
if (error.message?.includes("stack is currently locked")) {
|
|
4925
|
-
|
|
4926
|
-
clack5.log.info("To fix this, run:");
|
|
4927
|
-
clack5.log.info(` ${pc6.cyan("rm -rf ~/.wraps/pulumi/.pulumi/locks")}`);
|
|
4928
|
-
clack5.log.info("\nThen try running wraps email connect again.");
|
|
5107
|
+
throw errors.stackLocked();
|
|
4929
5108
|
}
|
|
4930
5109
|
throw new Error(`Pulumi deployment failed: ${error.message}`);
|
|
4931
5110
|
}
|
|
@@ -5423,9 +5602,14 @@ import pc8 from "picocolors";
|
|
|
5423
5602
|
init_costs();
|
|
5424
5603
|
init_presets();
|
|
5425
5604
|
init_aws();
|
|
5605
|
+
init_errors();
|
|
5426
5606
|
init_prompts();
|
|
5427
5607
|
async function init(options) {
|
|
5428
|
-
clack7.intro(
|
|
5608
|
+
clack7.intro(
|
|
5609
|
+
pc8.bold(
|
|
5610
|
+
options.preview ? "Wraps Email Infrastructure Preview" : "Wraps Email Infrastructure Setup"
|
|
5611
|
+
)
|
|
5612
|
+
);
|
|
5429
5613
|
const progress = new DeploymentProgress();
|
|
5430
5614
|
const wasAutoInstalled = await progress.execute(
|
|
5431
5615
|
"Checking Pulumi CLI installation",
|
|
@@ -5508,7 +5692,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5508
5692
|
if (vercelConfig) {
|
|
5509
5693
|
metadata.vercel = vercelConfig;
|
|
5510
5694
|
}
|
|
5511
|
-
if (!options.yes) {
|
|
5695
|
+
if (!(options.yes || options.preview)) {
|
|
5512
5696
|
const confirmed = await confirmDeploy();
|
|
5513
5697
|
if (!confirmed) {
|
|
5514
5698
|
clack7.cancel("Deployment cancelled.");
|
|
@@ -5521,6 +5705,64 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5521
5705
|
vercel: vercelConfig,
|
|
5522
5706
|
emailConfig
|
|
5523
5707
|
};
|
|
5708
|
+
if (options.preview) {
|
|
5709
|
+
try {
|
|
5710
|
+
const previewResult = await progress.execute(
|
|
5711
|
+
"Generating infrastructure preview",
|
|
5712
|
+
async () => {
|
|
5713
|
+
await ensurePulumiWorkDir();
|
|
5714
|
+
const stack = await pulumi7.automation.LocalWorkspace.createOrSelectStack(
|
|
5715
|
+
{
|
|
5716
|
+
stackName: `wraps-${identity.accountId}-${region}`,
|
|
5717
|
+
projectName: "wraps-email",
|
|
5718
|
+
program: async () => {
|
|
5719
|
+
const result2 = await deployEmailStack(stackConfig);
|
|
5720
|
+
return {
|
|
5721
|
+
roleArn: result2.roleArn,
|
|
5722
|
+
configSetName: result2.configSetName,
|
|
5723
|
+
tableName: result2.tableName,
|
|
5724
|
+
region: result2.region,
|
|
5725
|
+
lambdaFunctions: result2.lambdaFunctions,
|
|
5726
|
+
domain: result2.domain,
|
|
5727
|
+
dkimTokens: result2.dkimTokens,
|
|
5728
|
+
customTrackingDomain: result2.customTrackingDomain,
|
|
5729
|
+
mailFromDomain: result2.mailFromDomain,
|
|
5730
|
+
archiveArn: result2.archiveArn,
|
|
5731
|
+
archivingEnabled: result2.archivingEnabled,
|
|
5732
|
+
archiveRetention: result2.archiveRetention
|
|
5733
|
+
};
|
|
5734
|
+
}
|
|
5735
|
+
},
|
|
5736
|
+
{
|
|
5737
|
+
workDir: getPulumiWorkDir(),
|
|
5738
|
+
envVars: {
|
|
5739
|
+
PULUMI_CONFIG_PASSPHRASE: "",
|
|
5740
|
+
AWS_REGION: region
|
|
5741
|
+
},
|
|
5742
|
+
secretsProvider: "passphrase"
|
|
5743
|
+
}
|
|
5744
|
+
);
|
|
5745
|
+
await stack.setConfig("aws:region", { value: region });
|
|
5746
|
+
const result = await stack.preview({ diff: true });
|
|
5747
|
+
return result;
|
|
5748
|
+
}
|
|
5749
|
+
);
|
|
5750
|
+
displayPreview({
|
|
5751
|
+
changeSummary: previewResult.changeSummary,
|
|
5752
|
+
costEstimate: costSummary,
|
|
5753
|
+
commandName: "wraps email init"
|
|
5754
|
+
});
|
|
5755
|
+
clack7.outro(
|
|
5756
|
+
pc8.green("Preview complete. Run without --preview to deploy.")
|
|
5757
|
+
);
|
|
5758
|
+
return;
|
|
5759
|
+
} catch (error) {
|
|
5760
|
+
if (error.message?.includes("stack is currently locked")) {
|
|
5761
|
+
throw errors.stackLocked();
|
|
5762
|
+
}
|
|
5763
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
5764
|
+
}
|
|
5765
|
+
}
|
|
5524
5766
|
let outputs;
|
|
5525
5767
|
try {
|
|
5526
5768
|
outputs = await progress.execute(
|
|
@@ -5584,12 +5826,8 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
|
|
|
5584
5826
|
}
|
|
5585
5827
|
);
|
|
5586
5828
|
} catch (error) {
|
|
5587
|
-
clack7.log.error("Infrastructure deployment failed");
|
|
5588
5829
|
if (error.message?.includes("stack is currently locked")) {
|
|
5589
|
-
|
|
5590
|
-
clack7.log.info("To fix this, run:");
|
|
5591
|
-
clack7.log.info(` ${pc8.cyan("rm -rf ~/.wraps/pulumi/.pulumi/locks")}`);
|
|
5592
|
-
clack7.log.info("\nThen try running wraps email init again.");
|
|
5830
|
+
throw errors.stackLocked();
|
|
5593
5831
|
}
|
|
5594
5832
|
throw new Error(`Pulumi deployment failed: ${error.message}`);
|
|
5595
5833
|
}
|
|
@@ -5650,7 +5888,11 @@ import * as clack8 from "@clack/prompts";
|
|
|
5650
5888
|
import * as pulumi8 from "@pulumi/pulumi";
|
|
5651
5889
|
import pc9 from "picocolors";
|
|
5652
5890
|
async function restore(options) {
|
|
5653
|
-
clack8.intro(
|
|
5891
|
+
clack8.intro(
|
|
5892
|
+
pc9.bold(
|
|
5893
|
+
options.preview ? "Wraps Restore Preview" : "Wraps Restore - Remove Wraps Infrastructure"
|
|
5894
|
+
)
|
|
5895
|
+
);
|
|
5654
5896
|
clack8.log.info(
|
|
5655
5897
|
`${pc9.yellow("Note:")} This will remove all Wraps-managed infrastructure.`
|
|
5656
5898
|
);
|
|
@@ -5697,7 +5939,7 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
5697
5939
|
}
|
|
5698
5940
|
console.log(` ${pc9.cyan("\u2713")} IAM Role (wraps-email-role)`);
|
|
5699
5941
|
console.log("");
|
|
5700
|
-
if (!options.force) {
|
|
5942
|
+
if (!(options.force || options.preview)) {
|
|
5701
5943
|
const confirmed = await clack8.confirm({
|
|
5702
5944
|
message: "Proceed with removal? This cannot be undone.",
|
|
5703
5945
|
initialValue: false
|
|
@@ -5707,6 +5949,50 @@ ${pc9.bold("The following Wraps resources will be removed:")}
|
|
|
5707
5949
|
process.exit(0);
|
|
5708
5950
|
}
|
|
5709
5951
|
}
|
|
5952
|
+
if (options.preview) {
|
|
5953
|
+
if (metadata.services.email?.pulumiStackName) {
|
|
5954
|
+
try {
|
|
5955
|
+
const previewResult = await progress.execute(
|
|
5956
|
+
"Generating removal preview",
|
|
5957
|
+
async () => {
|
|
5958
|
+
const stack = await pulumi8.automation.LocalWorkspace.selectStack(
|
|
5959
|
+
{
|
|
5960
|
+
stackName: metadata.services.email.pulumiStackName,
|
|
5961
|
+
projectName: "wraps-email",
|
|
5962
|
+
program: async () => {
|
|
5963
|
+
}
|
|
5964
|
+
// Empty program for destroy
|
|
5965
|
+
},
|
|
5966
|
+
{
|
|
5967
|
+
workDir: getPulumiWorkDir(),
|
|
5968
|
+
envVars: {
|
|
5969
|
+
PULUMI_CONFIG_PASSPHRASE: "",
|
|
5970
|
+
AWS_REGION: region
|
|
5971
|
+
},
|
|
5972
|
+
secretsProvider: "passphrase"
|
|
5973
|
+
}
|
|
5974
|
+
);
|
|
5975
|
+
const result = await stack.preview({ diff: true });
|
|
5976
|
+
return result;
|
|
5977
|
+
}
|
|
5978
|
+
);
|
|
5979
|
+
displayPreview({
|
|
5980
|
+
changeSummary: previewResult.changeSummary,
|
|
5981
|
+
costEstimate: "Monthly cost after removal: $0.00",
|
|
5982
|
+
commandName: "wraps email restore"
|
|
5983
|
+
});
|
|
5984
|
+
clack8.outro(
|
|
5985
|
+
pc9.green(
|
|
5986
|
+
"Preview complete. Run without --preview to remove infrastructure."
|
|
5987
|
+
)
|
|
5988
|
+
);
|
|
5989
|
+
return;
|
|
5990
|
+
} catch (error) {
|
|
5991
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
5992
|
+
}
|
|
5993
|
+
}
|
|
5994
|
+
return;
|
|
5995
|
+
}
|
|
5710
5996
|
if (metadata.services.email?.pulumiStackName) {
|
|
5711
5997
|
await progress.execute("Removing Wraps infrastructure", async () => {
|
|
5712
5998
|
try {
|
|
@@ -5762,9 +6048,14 @@ import pc10 from "picocolors";
|
|
|
5762
6048
|
init_costs();
|
|
5763
6049
|
init_presets();
|
|
5764
6050
|
init_aws();
|
|
6051
|
+
init_errors();
|
|
5765
6052
|
init_prompts();
|
|
5766
6053
|
async function upgrade(options) {
|
|
5767
|
-
clack9.intro(
|
|
6054
|
+
clack9.intro(
|
|
6055
|
+
pc10.bold(
|
|
6056
|
+
options.preview ? "Wraps Upgrade Preview" : "Wraps Upgrade - Enhance Your Email Infrastructure"
|
|
6057
|
+
)
|
|
6058
|
+
);
|
|
5768
6059
|
const progress = new DeploymentProgress();
|
|
5769
6060
|
const wasAutoInstalled = await progress.execute(
|
|
5770
6061
|
"Checking Pulumi CLI installation",
|
|
@@ -6394,7 +6685,7 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6394
6685
|
);
|
|
6395
6686
|
}
|
|
6396
6687
|
console.log("");
|
|
6397
|
-
if (!options.yes) {
|
|
6688
|
+
if (!(options.yes || options.preview)) {
|
|
6398
6689
|
const confirmed = await clack9.confirm({
|
|
6399
6690
|
message: "Proceed with upgrade?",
|
|
6400
6691
|
initialValue: true
|
|
@@ -6416,6 +6707,73 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6416
6707
|
vercel: vercelConfig,
|
|
6417
6708
|
emailConfig: updatedConfig
|
|
6418
6709
|
};
|
|
6710
|
+
if (options.preview) {
|
|
6711
|
+
try {
|
|
6712
|
+
const previewResult = await progress.execute(
|
|
6713
|
+
"Generating upgrade preview",
|
|
6714
|
+
async () => {
|
|
6715
|
+
await ensurePulumiWorkDir();
|
|
6716
|
+
const stack = await pulumi9.automation.LocalWorkspace.createOrSelectStack(
|
|
6717
|
+
{
|
|
6718
|
+
stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
|
|
6719
|
+
projectName: "wraps-email",
|
|
6720
|
+
program: async () => {
|
|
6721
|
+
const result2 = await deployEmailStack(stackConfig);
|
|
6722
|
+
return {
|
|
6723
|
+
roleArn: result2.roleArn,
|
|
6724
|
+
configSetName: result2.configSetName,
|
|
6725
|
+
tableName: result2.tableName,
|
|
6726
|
+
region: result2.region,
|
|
6727
|
+
lambdaFunctions: result2.lambdaFunctions,
|
|
6728
|
+
domain: result2.domain,
|
|
6729
|
+
dkimTokens: result2.dkimTokens,
|
|
6730
|
+
customTrackingDomain: result2.customTrackingDomain,
|
|
6731
|
+
httpsTrackingEnabled: result2.httpsTrackingEnabled,
|
|
6732
|
+
cloudFrontDomain: result2.cloudFrontDomain,
|
|
6733
|
+
acmCertificateValidationRecords: result2.acmCertificateValidationRecords,
|
|
6734
|
+
archiveArn: result2.archiveArn,
|
|
6735
|
+
archivingEnabled: result2.archivingEnabled,
|
|
6736
|
+
archiveRetention: result2.archiveRetention
|
|
6737
|
+
};
|
|
6738
|
+
}
|
|
6739
|
+
},
|
|
6740
|
+
{
|
|
6741
|
+
workDir: getPulumiWorkDir(),
|
|
6742
|
+
envVars: {
|
|
6743
|
+
PULUMI_CONFIG_PASSPHRASE: "",
|
|
6744
|
+
AWS_REGION: region
|
|
6745
|
+
},
|
|
6746
|
+
secretsProvider: "passphrase"
|
|
6747
|
+
}
|
|
6748
|
+
);
|
|
6749
|
+
await stack.setConfig("aws:region", { value: region });
|
|
6750
|
+
await stack.refresh({ onOutput: () => {
|
|
6751
|
+
} });
|
|
6752
|
+
const result = await stack.preview({ diff: true });
|
|
6753
|
+
return result;
|
|
6754
|
+
}
|
|
6755
|
+
);
|
|
6756
|
+
const costComparison = [
|
|
6757
|
+
`Current: ${formatCost(currentCostData.total.monthly)}/mo`,
|
|
6758
|
+
`After upgrade: ${formatCost(newCostData.total.monthly)}/mo`,
|
|
6759
|
+
costDiff > 0 ? `Change: +${formatCost(costDiff)}/mo` : costDiff < 0 ? `Change: -${formatCost(Math.abs(costDiff))}/mo` : "Change: No cost difference"
|
|
6760
|
+
].join("\n");
|
|
6761
|
+
displayPreview({
|
|
6762
|
+
changeSummary: previewResult.changeSummary,
|
|
6763
|
+
costEstimate: costComparison,
|
|
6764
|
+
commandName: "wraps email upgrade"
|
|
6765
|
+
});
|
|
6766
|
+
clack9.outro(
|
|
6767
|
+
pc10.green("Preview complete. Run without --preview to upgrade.")
|
|
6768
|
+
);
|
|
6769
|
+
return;
|
|
6770
|
+
} catch (error) {
|
|
6771
|
+
if (error.message?.includes("stack is currently locked")) {
|
|
6772
|
+
throw errors.stackLocked();
|
|
6773
|
+
}
|
|
6774
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
6775
|
+
}
|
|
6776
|
+
}
|
|
6419
6777
|
let outputs;
|
|
6420
6778
|
try {
|
|
6421
6779
|
outputs = await progress.execute(
|
|
@@ -6483,12 +6841,8 @@ ${pc10.bold("Cost Impact:")}`);
|
|
|
6483
6841
|
}
|
|
6484
6842
|
);
|
|
6485
6843
|
} catch (error) {
|
|
6486
|
-
clack9.log.error("Infrastructure upgrade failed");
|
|
6487
6844
|
if (error.message?.includes("stack is currently locked")) {
|
|
6488
|
-
|
|
6489
|
-
clack9.log.info("To fix this, run:");
|
|
6490
|
-
clack9.log.info(` ${pc10.cyan("rm -rf ~/.wraps/pulumi/.pulumi/locks")}`);
|
|
6491
|
-
clack9.log.info("\nThen try running wraps upgrade again.");
|
|
6845
|
+
throw errors.stackLocked();
|
|
6492
6846
|
}
|
|
6493
6847
|
throw new Error(`Pulumi upgrade failed: ${error.message}`);
|
|
6494
6848
|
}
|
|
@@ -7885,14 +8239,18 @@ import * as clack11 from "@clack/prompts";
|
|
|
7885
8239
|
import * as pulumi11 from "@pulumi/pulumi";
|
|
7886
8240
|
import pc12 from "picocolors";
|
|
7887
8241
|
async function destroy(options) {
|
|
7888
|
-
clack11.intro(
|
|
8242
|
+
clack11.intro(
|
|
8243
|
+
pc12.bold(
|
|
8244
|
+
options.preview ? "Wraps Destruction Preview" : "Wraps Email Infrastructure Teardown"
|
|
8245
|
+
)
|
|
8246
|
+
);
|
|
7889
8247
|
const progress = new DeploymentProgress();
|
|
7890
8248
|
const identity = await progress.execute(
|
|
7891
8249
|
"Validating AWS credentials",
|
|
7892
8250
|
async () => validateAWSCredentials()
|
|
7893
8251
|
);
|
|
7894
8252
|
const region = await getAWSRegion();
|
|
7895
|
-
if (!options.force) {
|
|
8253
|
+
if (!(options.force || options.preview)) {
|
|
7896
8254
|
const confirmed = await clack11.confirm({
|
|
7897
8255
|
message: pc12.red(
|
|
7898
8256
|
"Are you sure you want to destroy all Wraps infrastructure?"
|
|
@@ -7904,6 +8262,44 @@ async function destroy(options) {
|
|
|
7904
8262
|
process.exit(0);
|
|
7905
8263
|
}
|
|
7906
8264
|
}
|
|
8265
|
+
if (options.preview) {
|
|
8266
|
+
try {
|
|
8267
|
+
const previewResult = await progress.execute(
|
|
8268
|
+
"Generating destruction preview",
|
|
8269
|
+
async () => {
|
|
8270
|
+
await ensurePulumiWorkDir();
|
|
8271
|
+
const stackName = `wraps-${identity.accountId}-${region}`;
|
|
8272
|
+
let stack;
|
|
8273
|
+
try {
|
|
8274
|
+
stack = await pulumi11.automation.LocalWorkspace.selectStack({
|
|
8275
|
+
stackName,
|
|
8276
|
+
workDir: getPulumiWorkDir()
|
|
8277
|
+
});
|
|
8278
|
+
} catch (_error) {
|
|
8279
|
+
throw new Error("No Wraps infrastructure found to preview");
|
|
8280
|
+
}
|
|
8281
|
+
const result = await stack.preview({ diff: true });
|
|
8282
|
+
return result;
|
|
8283
|
+
}
|
|
8284
|
+
);
|
|
8285
|
+
displayPreview({
|
|
8286
|
+
changeSummary: previewResult.changeSummary,
|
|
8287
|
+
costEstimate: "Monthly cost after destruction: $0.00",
|
|
8288
|
+
commandName: "wraps destroy"
|
|
8289
|
+
});
|
|
8290
|
+
clack11.outro(
|
|
8291
|
+
pc12.green("Preview complete. Run without --preview to destroy.")
|
|
8292
|
+
);
|
|
8293
|
+
return;
|
|
8294
|
+
} catch (error) {
|
|
8295
|
+
progress.stop();
|
|
8296
|
+
if (error.message.includes("No Wraps infrastructure found")) {
|
|
8297
|
+
clack11.log.warn("No Wraps infrastructure found to preview");
|
|
8298
|
+
process.exit(0);
|
|
8299
|
+
}
|
|
8300
|
+
throw new Error(`Preview failed: ${error.message}`);
|
|
8301
|
+
}
|
|
8302
|
+
}
|
|
7907
8303
|
try {
|
|
7908
8304
|
await progress.execute(
|
|
7909
8305
|
"Destroying infrastructure (this may take 2-3 minutes)",
|
|
@@ -8142,7 +8538,9 @@ function showHelp() {
|
|
|
8142
8538
|
` ${pc15.cyan("email connect")} Connect to existing AWS SES`
|
|
8143
8539
|
);
|
|
8144
8540
|
console.log(` ${pc15.cyan("email domains verify")} Verify domain DNS records`);
|
|
8145
|
-
console.log(
|
|
8541
|
+
console.log(
|
|
8542
|
+
` ${pc15.cyan("email sync")} Apply CLI updates to infrastructure`
|
|
8543
|
+
);
|
|
8146
8544
|
console.log(` ${pc15.cyan("email upgrade")} Add features`);
|
|
8147
8545
|
console.log(
|
|
8148
8546
|
` ${pc15.cyan("email restore")} Restore original configuration
|
|
@@ -8172,6 +8570,9 @@ function showHelp() {
|
|
|
8172
8570
|
console.log(` ${pc15.dim("--preset")} Configuration preset`);
|
|
8173
8571
|
console.log(` ${pc15.dim("-y, --yes")} Skip confirmation prompts`);
|
|
8174
8572
|
console.log(` ${pc15.dim("-f, --force")} Force destructive operations`);
|
|
8573
|
+
console.log(
|
|
8574
|
+
` ${pc15.dim("--preview")} Preview changes without deploying`
|
|
8575
|
+
);
|
|
8175
8576
|
console.log(` ${pc15.dim("-v, --version")} Show version number
|
|
8176
8577
|
`);
|
|
8177
8578
|
console.log(
|
|
@@ -8231,6 +8632,11 @@ args.options([
|
|
|
8231
8632
|
name: "noOpen",
|
|
8232
8633
|
description: "Don't open browser automatically",
|
|
8233
8634
|
defaultValue: false
|
|
8635
|
+
},
|
|
8636
|
+
{
|
|
8637
|
+
name: "preview",
|
|
8638
|
+
description: "Preview changes without deploying",
|
|
8639
|
+
defaultValue: false
|
|
8234
8640
|
}
|
|
8235
8641
|
]);
|
|
8236
8642
|
var flags = args.parse(process.argv);
|
|
@@ -8292,13 +8698,15 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
|
|
|
8292
8698
|
region: flags.region,
|
|
8293
8699
|
domain: flags.domain,
|
|
8294
8700
|
preset: flags.preset,
|
|
8295
|
-
yes: flags.yes
|
|
8701
|
+
yes: flags.yes,
|
|
8702
|
+
preview: flags.preview
|
|
8296
8703
|
});
|
|
8297
8704
|
} else {
|
|
8298
8705
|
await connect({
|
|
8299
8706
|
provider: flags.provider,
|
|
8300
8707
|
region: flags.region,
|
|
8301
|
-
yes: flags.yes
|
|
8708
|
+
yes: flags.yes,
|
|
8709
|
+
preview: flags.preview
|
|
8302
8710
|
});
|
|
8303
8711
|
}
|
|
8304
8712
|
}
|
|
@@ -8336,32 +8744,38 @@ async function run() {
|
|
|
8336
8744
|
region: flags.region,
|
|
8337
8745
|
domain: flags.domain,
|
|
8338
8746
|
preset: flags.preset,
|
|
8339
|
-
yes: flags.yes
|
|
8747
|
+
yes: flags.yes,
|
|
8748
|
+
preview: flags.preview
|
|
8340
8749
|
});
|
|
8341
8750
|
break;
|
|
8342
8751
|
case "connect":
|
|
8343
8752
|
await connect({
|
|
8344
8753
|
provider: flags.provider,
|
|
8345
8754
|
region: flags.region,
|
|
8346
|
-
yes: flags.yes
|
|
8755
|
+
yes: flags.yes,
|
|
8756
|
+
preview: flags.preview
|
|
8347
8757
|
});
|
|
8348
8758
|
break;
|
|
8349
8759
|
case "config":
|
|
8760
|
+
case "sync":
|
|
8350
8761
|
await config({
|
|
8351
8762
|
region: flags.region,
|
|
8352
|
-
yes: flags.yes
|
|
8763
|
+
yes: flags.yes,
|
|
8764
|
+
preview: flags.preview
|
|
8353
8765
|
});
|
|
8354
8766
|
break;
|
|
8355
8767
|
case "upgrade":
|
|
8356
8768
|
await upgrade({
|
|
8357
8769
|
region: flags.region,
|
|
8358
|
-
yes: flags.yes
|
|
8770
|
+
yes: flags.yes,
|
|
8771
|
+
preview: flags.preview
|
|
8359
8772
|
});
|
|
8360
8773
|
break;
|
|
8361
8774
|
case "restore":
|
|
8362
8775
|
await restore({
|
|
8363
8776
|
region: flags.region,
|
|
8364
|
-
force: flags.force
|
|
8777
|
+
force: flags.force,
|
|
8778
|
+
preview: flags.preview
|
|
8365
8779
|
});
|
|
8366
8780
|
break;
|
|
8367
8781
|
case "domains": {
|
|
@@ -8503,7 +8917,8 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
|
|
|
8503
8917
|
break;
|
|
8504
8918
|
case "destroy":
|
|
8505
8919
|
await destroy({
|
|
8506
|
-
force: flags.force
|
|
8920
|
+
force: flags.force,
|
|
8921
|
+
preview: flags.preview
|
|
8507
8922
|
});
|
|
8508
8923
|
break;
|
|
8509
8924
|
case "completion":
|