@wraps.dev/cli 1.5.4 → 1.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -147,7 +147,7 @@ var require_package = __commonJS({
147
147
  "package.json"(exports, module) {
148
148
  module.exports = {
149
149
  name: "@wraps.dev/cli",
150
- version: "1.5.4",
150
+ version: "1.5.5",
151
151
  description: "CLI for deploying Wraps email infrastructure to your AWS account",
152
152
  type: "module",
153
153
  main: "./dist/cli.js",
@@ -4184,34 +4184,139 @@ ${pc2.bold("Dashboard:")} ${pc2.blue("https://app.wraps.dev")}`);
4184
4184
  console.log(`${pc2.bold("Docs:")} ${pc2.blue("https://wraps.dev/docs")}
4185
4185
  `);
4186
4186
  }
4187
- function displayPreview(outputs) {
4188
- console.log(pc2.yellow("\n--- PREVIEW MODE (no changes will be made) ---\n"));
4189
- const changes = outputs.changeSummary;
4190
- const summaryLines = [];
4191
- if (changes.create && changes.create > 0) {
4192
- summaryLines.push(` ${pc2.green("+")} ${changes.create} to create`);
4193
- }
4194
- if (changes.update && changes.update > 0) {
4195
- summaryLines.push(` ${pc2.yellow("~")} ${changes.update} to update`);
4196
- }
4197
- if (changes.delete && changes.delete > 0) {
4198
- summaryLines.push(` ${pc2.red("-")} ${changes.delete} to destroy`);
4199
- }
4200
- if (changes.same && changes.same > 0) {
4201
- summaryLines.push(` ${pc2.dim("=")} ${changes.same} unchanged`);
4187
+ function formatResourceType(type) {
4188
+ const typeMap = {
4189
+ "aws:iam/role:Role": "IAM Role",
4190
+ "aws:iam/policy:Policy": "IAM Policy",
4191
+ "aws:iam/rolePolicyAttachment:RolePolicyAttachment": "IAM Policy Attachment",
4192
+ "aws:iam/openIdConnectProvider:OpenIdConnectProvider": "OIDC Provider",
4193
+ "aws:ses/configurationSet:ConfigurationSet": "SES Configuration Set",
4194
+ "aws:ses/emailIdentity:EmailIdentity": "SES Email Identity",
4195
+ "aws:ses/eventDestination:EventDestination": "SES Event Destination",
4196
+ "aws:sesv2/configurationSetEventDestination:ConfigurationSetEventDestination": "SES Event Destination",
4197
+ "aws:dynamodb/table:Table": "DynamoDB Table",
4198
+ "aws:lambda/function:Function": "Lambda Function",
4199
+ "aws:lambda/eventSourceMapping:EventSourceMapping": "Lambda Event Source",
4200
+ "aws:sqs/queue:Queue": "SQS Queue",
4201
+ "aws:cloudwatch/eventRule:EventRule": "EventBridge Rule",
4202
+ "aws:cloudwatch/eventTarget:EventTarget": "EventBridge Target",
4203
+ "aws:sns/topic:Topic": "SNS Topic",
4204
+ "aws:sns/topicSubscription:TopicSubscription": "SNS Subscription",
4205
+ "aws:s3/bucket:Bucket": "S3 Bucket",
4206
+ "aws:s3/bucketPolicy:BucketPolicy": "S3 Bucket Policy",
4207
+ "aws:cloudfront/distribution:Distribution": "CloudFront Distribution",
4208
+ "aws:acm/certificate:Certificate": "ACM Certificate",
4209
+ "aws:acm/certificateValidation:CertificateValidation": "ACM Validation",
4210
+ "aws:route53/record:Record": "Route53 Record",
4211
+ "pulumi:pulumi:Stack": "Pulumi Stack"
4212
+ };
4213
+ return typeMap[type] || type.split(":").pop() || type;
4214
+ }
4215
+ function getOperationIcon(operation) {
4216
+ switch (operation) {
4217
+ case "create":
4218
+ return pc2.green("+");
4219
+ case "update":
4220
+ return pc2.yellow("~");
4221
+ case "delete":
4222
+ return pc2.red("-");
4223
+ case "replace":
4224
+ return pc2.magenta("\xB1");
4225
+ case "same":
4226
+ return pc2.dim("=");
4227
+ default:
4228
+ return " ";
4202
4229
  }
4203
- if (changes.replace && changes.replace > 0) {
4204
- summaryLines.push(` ${pc2.magenta("+-")} ${changes.replace} to replace`);
4230
+ }
4231
+ function getOperationLabel(operation) {
4232
+ switch (operation) {
4233
+ case "create":
4234
+ return pc2.green("CREATE");
4235
+ case "update":
4236
+ return pc2.yellow("UPDATE");
4237
+ case "delete":
4238
+ return pc2.red("DELETE");
4239
+ case "replace":
4240
+ return pc2.magenta("REPLACE");
4241
+ case "same":
4242
+ return pc2.dim("UNCHANGED");
4205
4243
  }
4206
- if (summaryLines.length > 0) {
4207
- clack2.note(summaryLines.join("\n"), "Resource Changes");
4244
+ }
4245
+ function displayPreview(outputs) {
4246
+ console.log(pc2.yellow("\n\u2501\u2501\u2501 PREVIEW MODE (no changes will be made) \u2501\u2501\u2501\n"));
4247
+ if (outputs.resourceChanges && outputs.resourceChanges.length > 0) {
4248
+ const grouped = /* @__PURE__ */ new Map();
4249
+ for (const resource of outputs.resourceChanges) {
4250
+ const existing = grouped.get(resource.operation) || [];
4251
+ existing.push(resource);
4252
+ grouped.set(resource.operation, existing);
4253
+ }
4254
+ const operationOrder = [
4255
+ "create",
4256
+ "update",
4257
+ "replace",
4258
+ "delete",
4259
+ "same"
4260
+ ];
4261
+ const sections = [];
4262
+ for (const operation of operationOrder) {
4263
+ const resources = grouped.get(operation);
4264
+ if (!resources || resources.length === 0) continue;
4265
+ if (operation === "same" && outputs.resourceChanges.length > 10) {
4266
+ sections.push(
4267
+ `${getOperationLabel(operation)} ${pc2.dim(`(${resources.length} resources)`)}`
4268
+ );
4269
+ continue;
4270
+ }
4271
+ const resourceLines = resources.map((r) => {
4272
+ const icon = getOperationIcon(operation);
4273
+ const typeLabel = pc2.dim(`(${formatResourceType(r.type)})`);
4274
+ let line = ` ${icon} ${r.name} ${typeLabel}`;
4275
+ if (r.diffs && r.diffs.length > 0 && operation === "update") {
4276
+ const diffStr = r.diffs.slice(0, 3).join(", ");
4277
+ const more = r.diffs.length > 3 ? ` +${r.diffs.length - 3} more` : "";
4278
+ line += `
4279
+ ${pc2.dim(`changed: ${diffStr}${more}`)}`;
4280
+ }
4281
+ return line;
4282
+ }).join("\n");
4283
+ sections.push(
4284
+ `${getOperationLabel(operation)} ${pc2.dim(`(${resources.length})`)}
4285
+ ${resourceLines}`
4286
+ );
4287
+ }
4288
+ if (sections.length > 0) {
4289
+ console.log(sections.join("\n\n"));
4290
+ console.log();
4291
+ }
4208
4292
  } else {
4209
- clack2.note("No changes detected", "Resource Changes");
4293
+ const changes = outputs.changeSummary;
4294
+ const summaryLines = [];
4295
+ if (changes.create && changes.create > 0) {
4296
+ summaryLines.push(` ${pc2.green("+")} ${changes.create} to create`);
4297
+ }
4298
+ if (changes.update && changes.update > 0) {
4299
+ summaryLines.push(` ${pc2.yellow("~")} ${changes.update} to update`);
4300
+ }
4301
+ if (changes.delete && changes.delete > 0) {
4302
+ summaryLines.push(` ${pc2.red("-")} ${changes.delete} to destroy`);
4303
+ }
4304
+ if (changes.same && changes.same > 0) {
4305
+ summaryLines.push(` ${pc2.dim("=")} ${changes.same} unchanged`);
4306
+ }
4307
+ if (changes.replace && changes.replace > 0) {
4308
+ summaryLines.push(` ${pc2.magenta("\xB1")} ${changes.replace} to replace`);
4309
+ }
4310
+ if (summaryLines.length > 0) {
4311
+ clack2.note(summaryLines.join("\n"), "Resource Changes");
4312
+ } else {
4313
+ clack2.note("No changes detected", "Resource Changes");
4314
+ }
4210
4315
  }
4211
4316
  if (outputs.costEstimate) {
4212
4317
  clack2.note(outputs.costEstimate, "Estimated Monthly Cost");
4213
4318
  }
4214
- console.log(pc2.yellow("\n--- END PREVIEW (no changes were made) ---\n"));
4319
+ console.log(pc2.yellow("\u2501\u2501\u2501 END PREVIEW (no changes were made) \u2501\u2501\u2501\n"));
4215
4320
  }
4216
4321
 
4217
4322
  // src/commands/dashboard/update-role.ts
@@ -4649,7 +4754,7 @@ async function roleExists(roleName) {
4649
4754
  await iam5.send(new GetRoleCommand2({ RoleName: roleName }));
4650
4755
  return true;
4651
4756
  } catch (error) {
4652
- if (error.name === "NoSuchEntityException") {
4757
+ if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity") {
4653
4758
  return false;
4654
4759
  }
4655
4760
  console.error("Error checking for existing IAM role:", error);
@@ -5257,6 +5362,66 @@ async function ensurePulumiInstalled() {
5257
5362
  }
5258
5363
  return false;
5259
5364
  }
5365
+ function mapOperationType(op) {
5366
+ switch (op) {
5367
+ case "create":
5368
+ return "create";
5369
+ case "update":
5370
+ return "update";
5371
+ case "delete":
5372
+ return "delete";
5373
+ case "replace":
5374
+ case "create-replacement":
5375
+ case "delete-replaced":
5376
+ return "replace";
5377
+ case "same":
5378
+ case "read":
5379
+ return "same";
5380
+ default:
5381
+ return "same";
5382
+ }
5383
+ }
5384
+ async function previewWithResourceChanges(stack, options) {
5385
+ const resourceChanges = [];
5386
+ const seenResources = /* @__PURE__ */ new Set();
5387
+ const result = await stack.preview({
5388
+ diff: options?.diff ?? true,
5389
+ onEvent: (event) => {
5390
+ if (event.resourcePreEvent) {
5391
+ const metadata = event.resourcePreEvent.metadata;
5392
+ if (metadata) {
5393
+ const resourceKey = `${metadata.type}::${metadata.urn}`;
5394
+ if (seenResources.has(resourceKey)) {
5395
+ return;
5396
+ }
5397
+ seenResources.add(resourceKey);
5398
+ if (metadata.type === "pulumi:pulumi:Stack") {
5399
+ return;
5400
+ }
5401
+ const operation = mapOperationType(metadata.op || "same");
5402
+ const urnParts = metadata.urn?.split("::") || [];
5403
+ const name = urnParts[urnParts.length - 1] || metadata.urn || "unknown";
5404
+ const diffs = [];
5405
+ if (metadata.diffs && metadata.diffs.length > 0) {
5406
+ for (const diff of metadata.diffs) {
5407
+ diffs.push(diff);
5408
+ }
5409
+ }
5410
+ resourceChanges.push({
5411
+ name,
5412
+ type: metadata.type || "unknown",
5413
+ operation,
5414
+ diffs: diffs.length > 0 ? diffs : void 0
5415
+ });
5416
+ }
5417
+ }
5418
+ }
5419
+ });
5420
+ return {
5421
+ ...result,
5422
+ resourceChanges
5423
+ };
5424
+ }
5260
5425
 
5261
5426
  // src/commands/email/config.ts
5262
5427
  async function config(options) {
@@ -5398,12 +5563,13 @@ ${pc4.bold("Current Configuration:")}
5398
5563
  await stack.setConfig("aws:region", { value: region });
5399
5564
  await stack.refresh({ onOutput: () => {
5400
5565
  } });
5401
- const result = await stack.preview({ diff: true });
5566
+ const result = await previewWithResourceChanges(stack, { diff: true });
5402
5567
  return result;
5403
5568
  }
5404
5569
  );
5405
5570
  displayPreview({
5406
5571
  changeSummary: previewResult.changeSummary,
5572
+ resourceChanges: previewResult.resourceChanges,
5407
5573
  commandName: "wraps email config"
5408
5574
  });
5409
5575
  clack3.outro(
@@ -5892,12 +6058,13 @@ async function connect(options) {
5892
6058
  }
5893
6059
  );
5894
6060
  await stack.setConfig("aws:region", { value: region });
5895
- const result = await stack.preview({ diff: true });
6061
+ const result = await previewWithResourceChanges(stack, { diff: true });
5896
6062
  return result;
5897
6063
  }
5898
6064
  );
5899
6065
  displayPreview({
5900
6066
  changeSummary: previewResult.changeSummary,
6067
+ resourceChanges: previewResult.resourceChanges,
5901
6068
  commandName: "wraps email connect"
5902
6069
  });
5903
6070
  clack5.outro(
@@ -6190,12 +6357,13 @@ async function emailDestroy(options) {
6190
6357
  } catch (_error) {
6191
6358
  throw new Error("No email infrastructure found to preview");
6192
6359
  }
6193
- const result = await stack.preview({ diff: true });
6360
+ const result = await previewWithResourceChanges(stack, { diff: true });
6194
6361
  return result;
6195
6362
  }
6196
6363
  );
6197
6364
  displayPreview({
6198
6365
  changeSummary: previewResult.changeSummary,
6366
+ resourceChanges: previewResult.resourceChanges,
6199
6367
  costEstimate: "Monthly cost after destruction: $0.00",
6200
6368
  commandName: "wraps email destroy"
6201
6369
  });
@@ -6909,12 +7077,13 @@ ${pc9.yellow(pc9.bold("Configuration Warnings:"))}`);
6909
7077
  }
6910
7078
  );
6911
7079
  await stack.setConfig("aws:region", { value: region });
6912
- const result = await stack.preview({ diff: true });
7080
+ const result = await previewWithResourceChanges(stack, { diff: true });
6913
7081
  return result;
6914
7082
  }
6915
7083
  );
6916
7084
  displayPreview({
6917
7085
  changeSummary: previewResult.changeSummary,
7086
+ resourceChanges: previewResult.resourceChanges,
6918
7087
  costEstimate: costSummary,
6919
7088
  commandName: "wraps email init"
6920
7089
  });
@@ -7213,12 +7382,13 @@ ${pc10.bold("The following Wraps resources will be removed:")}
7213
7382
  secretsProvider: "passphrase"
7214
7383
  }
7215
7384
  );
7216
- const result = await stack.preview({ diff: true });
7385
+ const result = await previewWithResourceChanges(stack, { diff: true });
7217
7386
  return result;
7218
7387
  }
7219
7388
  );
7220
7389
  displayPreview({
7221
7390
  changeSummary: previewResult.changeSummary,
7391
+ resourceChanges: previewResult.resourceChanges,
7222
7392
  costEstimate: "Monthly cost after removal: $0.00",
7223
7393
  commandName: "wraps email restore"
7224
7394
  });
@@ -8117,7 +8287,7 @@ ${pc12.bold("Cost Impact:")}`);
8117
8287
  await stack.setConfig("aws:region", { value: region });
8118
8288
  await stack.refresh({ onOutput: () => {
8119
8289
  } });
8120
- const result = await stack.preview({ diff: true });
8290
+ const result = await previewWithResourceChanges(stack, { diff: true });
8121
8291
  return result;
8122
8292
  }
8123
8293
  );
@@ -8128,6 +8298,7 @@ ${pc12.bold("Cost Impact:")}`);
8128
8298
  ].join("\n");
8129
8299
  displayPreview({
8130
8300
  changeSummary: previewResult.changeSummary,
8301
+ resourceChanges: previewResult.resourceChanges,
8131
8302
  costEstimate: costComparison,
8132
8303
  commandName: "wraps email upgrade"
8133
8304
  });
@@ -10662,7 +10833,7 @@ async function roleExists2(roleName) {
10662
10833
  await iam5.send(new GetRoleCommand2({ RoleName: roleName }));
10663
10834
  return true;
10664
10835
  } catch (error) {
10665
- if (error instanceof Error && "name" in error && error.name === "NoSuchEntityException") {
10836
+ if (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity") {
10666
10837
  return false;
10667
10838
  }
10668
10839
  return false;
@@ -11610,12 +11781,13 @@ async function smsDestroy(options) {
11610
11781
  } catch (_error) {
11611
11782
  throw new Error("No SMS infrastructure found to preview");
11612
11783
  }
11613
- const result = await stack.preview({ diff: true });
11784
+ const result = await previewWithResourceChanges(stack, { diff: true });
11614
11785
  return result;
11615
11786
  }
11616
11787
  );
11617
11788
  displayPreview({
11618
11789
  changeSummary: previewResult.changeSummary,
11790
+ resourceChanges: previewResult.resourceChanges,
11619
11791
  costEstimate: "Monthly cost after destruction: $0.00",
11620
11792
  commandName: "wraps sms destroy"
11621
11793
  });