@wraps.dev/cli 2.18.6 → 2.18.7

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
@@ -1252,6 +1252,7 @@ var init_aws_detection = __esm({
1252
1252
  var errors_exports = {};
1253
1253
  __export(errors_exports, {
1254
1254
  WrapsError: () => WrapsError,
1255
+ awsErrorToWrapsError: () => awsErrorToWrapsError,
1255
1256
  classifyDNSError: () => classifyDNSError,
1256
1257
  errors: () => errors,
1257
1258
  handleCLIError: () => handleCLIError,
@@ -1408,7 +1409,7 @@ function handleCLIError(error, command) {
1408
1409
  const parsed = parseAWSError(error);
1409
1410
  code = `AWS_${parsed.code}`;
1410
1411
  trackError(code, cmdContext, { action: parsed.action });
1411
- const wrapsErr = awsErrorToWrapsError(parsed.code, parsed.action);
1412
+ const wrapsErr = awsErrorToWrapsError(parsed.code, parsed.action, error);
1412
1413
  message = wrapsErr.message;
1413
1414
  suggestion = wrapsErr.suggestion;
1414
1415
  docsUrl = wrapsErr.docsUrl;
@@ -1463,7 +1464,7 @@ ${pc5.yellow("Suggestion:")}`);
1463
1464
  if (isAWSError(error)) {
1464
1465
  const { code, action } = parseAWSError(error);
1465
1466
  trackError(`AWS_${code}`, cmdContext, { action });
1466
- const wrapsError = awsErrorToWrapsError(code, action);
1467
+ const wrapsError = awsErrorToWrapsError(code, action, error);
1467
1468
  clack4.log.error(wrapsError.message);
1468
1469
  if (wrapsError.suggestion) {
1469
1470
  console.log(`
@@ -1528,8 +1529,9 @@ ${pc5.dim("If this persists, please report at:")}`);
1528
1529
  `);
1529
1530
  process.exit(1);
1530
1531
  }
1531
- function awsErrorToWrapsError(code, action) {
1532
+ function awsErrorToWrapsError(code, action, originalError) {
1532
1533
  switch (code) {
1534
+ // Credential / token errors — these mean the request never reached the API
1533
1535
  case "ExpiredTokenException":
1534
1536
  case "TokenRefreshRequired":
1535
1537
  case "SSOTokenExpired":
@@ -1537,7 +1539,9 @@ function awsErrorToWrapsError(code, action) {
1537
1539
  case "InvalidClientTokenId":
1538
1540
  case "InvalidAccessKeyId":
1539
1541
  case "SignatureDoesNotMatch":
1542
+ case "UnrecognizedClientException":
1540
1543
  return errors.accessKeyInvalid();
1544
+ // IAM permission errors — request reached AWS but was denied
1541
1545
  case "AccessDenied":
1542
1546
  case "AccessDeniedException":
1543
1547
  case "UnauthorizedAccess":
@@ -1546,8 +1550,35 @@ function awsErrorToWrapsError(code, action) {
1546
1550
  "AWS resource",
1547
1551
  "Ensure your IAM user/role has the required permissions."
1548
1552
  );
1553
+ // SES SendEmail errors — request reached SES but was rejected
1554
+ case "MessageRejected":
1555
+ return errors.sesMessageRejected(sanitizeErrorMessage(originalError));
1556
+ case "MailFromDomainNotVerifiedException":
1557
+ return errors.sesMailFromNotVerified(sanitizeErrorMessage(originalError));
1558
+ case "AccountSendingPausedException":
1559
+ return errors.sesAccountSendingPaused();
1560
+ case "ConfigurationSetSendingPausedException":
1561
+ return errors.sesConfigSetSendingPaused();
1562
+ case "ConfigurationSetDoesNotExistException":
1563
+ return errors.sesConfigSetMissing(sanitizeErrorMessage(originalError));
1564
+ // Throughput / quota errors
1565
+ case "Throttling":
1566
+ case "ThrottlingException":
1567
+ case "TooManyRequestsException":
1568
+ return errors.awsThrottled(action);
1569
+ case "LimitExceededException":
1570
+ case "ServiceQuotaExceededException":
1571
+ return errors.awsLimitExceeded(
1572
+ action,
1573
+ sanitizeErrorMessage(originalError)
1574
+ );
1575
+ // Anything else — surface the real error instead of lying about credentials
1549
1576
  default:
1550
- return errors.noAWSCredentials();
1577
+ return errors.awsUnknownError(
1578
+ code,
1579
+ action,
1580
+ sanitizeErrorMessage(originalError)
1581
+ );
1551
1582
  }
1552
1583
  }
1553
1584
  function pulumiErrorToWrapsError(code, iamAction, service, resourceName, resourceType) {
@@ -1747,6 +1778,61 @@ View required SES permissions:
1747
1778
  wraps permissions --service email --json`,
1748
1779
  "https://wraps.dev/docs/guides/aws-setup/permissions"
1749
1780
  ),
1781
+ // SES SendEmail rejection errors — request reached SES but the send failed
1782
+ // for a reason unrelated to credentials.
1783
+ sesMessageRejected: (detail) => new WrapsError(
1784
+ `SES rejected the message: ${detail}`,
1785
+ "SES_MESSAGE_REJECTED",
1786
+ "Common causes:\n \u2022 Account is in the SES sandbox and the recipient is not a verified address\n \u2022 Sender identity (domain or email) is not verified for sending\n \u2022 The sender domain is verified for receiving but not for sending\n\nCheck status:\n wraps email status\n wraps email doctor\n\nRequest production access (exit sandbox):\n https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html",
1787
+ "https://wraps.dev/docs/guides/email/troubleshooting"
1788
+ ),
1789
+ sesMailFromNotVerified: (detail) => new WrapsError(
1790
+ `SES MAIL FROM domain is not verified: ${detail}`,
1791
+ "SES_MAIL_FROM_NOT_VERIFIED",
1792
+ "The custom MAIL FROM domain configured for this identity is not fully verified.\n\nCheck DNS records:\n wraps email verify\n\nOr remove the custom MAIL FROM domain in the SES console and retry.",
1793
+ "https://docs.aws.amazon.com/ses/latest/dg/mail-from.html"
1794
+ ),
1795
+ sesAccountSendingPaused: () => new WrapsError(
1796
+ "SES account-level sending is paused",
1797
+ "SES_ACCOUNT_SENDING_PAUSED",
1798
+ "Your SES account is currently paused from sending email. This is usually caused by:\n \u2022 A high bounce or complaint rate\n \u2022 An AWS-initiated review\n\nCheck the SES console \u2192 Reputation Dashboard for details, then resume sending once the issue is resolved.",
1799
+ "https://docs.aws.amazon.com/ses/latest/dg/reputationdashboard.html"
1800
+ ),
1801
+ sesConfigSetSendingPaused: () => new WrapsError(
1802
+ "SES configuration set sending is paused",
1803
+ "SES_CONFIG_SET_SENDING_PAUSED",
1804
+ "The configuration set used for this send has sending paused. Resume it in the SES console under Configuration Sets, or send without specifying the paused configuration set.",
1805
+ "https://docs.aws.amazon.com/ses/latest/dg/using-configuration-sets.html"
1806
+ ),
1807
+ sesConfigSetMissing: (detail) => new WrapsError(
1808
+ `SES configuration set does not exist: ${detail}`,
1809
+ "SES_CONFIG_SET_MISSING",
1810
+ "The configuration set referenced by this send does not exist in the current region. Create it in the SES console, switch regions, or remove the ConfigurationSetName from the request.",
1811
+ "https://docs.aws.amazon.com/ses/latest/dg/using-configuration-sets.html"
1812
+ ),
1813
+ // Generic AWS error fallbacks — used by awsErrorToWrapsError when no specific
1814
+ // mapping exists. These NEVER claim credentials are missing.
1815
+ awsThrottled: (action) => new WrapsError(
1816
+ `AWS request was throttled${action ? ` (${action})` : ""}`,
1817
+ "AWS_THROTTLED",
1818
+ "AWS is rate-limiting requests to this API. Wait a moment and retry.\n\nIf this happens repeatedly, request a service quota increase in the AWS console.",
1819
+ "https://docs.aws.amazon.com/general/latest/gr/api-retries.html"
1820
+ ),
1821
+ awsLimitExceeded: (action, detail) => new WrapsError(
1822
+ `AWS service limit exceeded${action ? ` (${action})` : ""}${detail ? `: ${detail}` : ""}`,
1823
+ "AWS_LIMIT_EXCEEDED",
1824
+ "You've hit a service quota for this AWS API.\n\nRequest a quota increase in the AWS console:\n Service Quotas \u2192 AWS Services \u2192 (your service)",
1825
+ "https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html"
1826
+ ),
1827
+ awsUnknownError: (code, action, detail) => new WrapsError(
1828
+ `AWS API error: ${code}${action ? ` (${action})` : ""}${detail ? ` \u2014 ${detail}` : ""}`,
1829
+ `AWS_${code}`,
1830
+ `This is an AWS API error, not a credentials problem. Look up "${code}" in the AWS documentation for the failing service.
1831
+
1832
+ If you believe this is a Wraps bug, report it at:
1833
+ https://github.com/wraps-team/wraps/issues`,
1834
+ "https://wraps.dev/docs/guides/aws-setup/troubleshooting"
1835
+ ),
1750
1836
  dynamoDBPermissionDenied: () => new WrapsError(
1751
1837
  "DynamoDB permission denied",
1752
1838
  "DYNAMODB_PERMISSION_DENIED",
@@ -21089,6 +21175,77 @@ Enable it: ${pc24.cyan("wraps email inbound init")}
21089
21175
  }
21090
21176
  console.log();
21091
21177
  }
21178
+ var NOT_VERIFIED_PATTERN = /not verified/i;
21179
+ function mapInboundTestSendError(error, ctx) {
21180
+ if (error instanceof WrapsError) {
21181
+ return error;
21182
+ }
21183
+ if (!(error instanceof Error)) {
21184
+ return new WrapsError(
21185
+ `Failed to send inbound test email to ${ctx.recipient}`,
21186
+ "INBOUND_TEST_SEND_FAILED",
21187
+ "An unexpected error occurred while sending the test email.\n\nCheck infrastructure status:\n wraps email status\n wraps email doctor",
21188
+ "https://wraps.dev/docs/guides/email/troubleshooting"
21189
+ );
21190
+ }
21191
+ const name = error.name;
21192
+ const message = error.message || "";
21193
+ if (name === "MailFromDomainNotVerifiedException") {
21194
+ return new WrapsError(
21195
+ `Custom MAIL FROM domain is not verified for ${ctx.domain}`,
21196
+ "INBOUND_TEST_MAIL_FROM_NOT_VERIFIED",
21197
+ `The MAIL FROM domain configured for "${ctx.domain}" is not fully verified.
21198
+
21199
+ Verify DNS records:
21200
+ wraps email verify
21201
+
21202
+ Or remove the custom MAIL FROM domain in the SES console and retry.`,
21203
+ "https://docs.aws.amazon.com/ses/latest/dg/mail-from.html"
21204
+ );
21205
+ }
21206
+ if (name === "MessageRejected" || name === "InvalidParameterValue" || NOT_VERIFIED_PATTERN.test(message)) {
21207
+ return new WrapsError(
21208
+ `SES rejected the inbound test send: ${message || name}`,
21209
+ "INBOUND_TEST_MESSAGE_REJECTED",
21210
+ [
21211
+ `Tried to send: ${ctx.source} \u2192 ${ctx.recipient} (region ${ctx.region})`,
21212
+ "",
21213
+ "Most likely causes:",
21214
+ ` \u2022 Your SES account is in the sandbox and ${ctx.recipient} is not a verified address`,
21215
+ ` \u2022 The sender domain "${ctx.domain}" is not verified for sending in ${ctx.region}`,
21216
+ ` \u2022 The receiving domain "${ctx.receivingDomain}" is verified for receiving (MX) but not for sending`,
21217
+ "",
21218
+ "Check status:",
21219
+ " wraps email status",
21220
+ " wraps email doctor",
21221
+ "",
21222
+ "Exit the SES sandbox to send to any address:",
21223
+ " https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html"
21224
+ ].join("\n"),
21225
+ "https://wraps.dev/docs/guides/email/troubleshooting"
21226
+ );
21227
+ }
21228
+ if (name === "AccountSendingPausedException" || name === "ConfigurationSetSendingPausedException") {
21229
+ return new WrapsError(
21230
+ "SES sending is paused for this account",
21231
+ "INBOUND_TEST_SENDING_PAUSED",
21232
+ "Your SES account or configuration set has sending paused, usually due to a high bounce or complaint rate.\n\nCheck the SES Reputation Dashboard in the AWS console and resume sending once the issue is resolved.",
21233
+ "https://docs.aws.amazon.com/ses/latest/dg/reputationdashboard.html"
21234
+ );
21235
+ }
21236
+ if (name === "AccessDeniedException" || name === "AccessDenied") {
21237
+ return new WrapsError(
21238
+ `IAM permission denied: ses:SendEmail in ${ctx.region}`,
21239
+ "INBOUND_TEST_PERMISSION_DENIED",
21240
+ `Your AWS credentials lack the "ses:SendEmail" permission in region ${ctx.region}.
21241
+
21242
+ View required SES permissions:
21243
+ wraps permissions --service email --json`,
21244
+ "https://wraps.dev/docs/guides/aws-setup/permissions"
21245
+ );
21246
+ }
21247
+ return error;
21248
+ }
21092
21249
  async function inboundTest(options) {
21093
21250
  if (!isJsonMode()) {
21094
21251
  clack22.intro(pc24.bold("Inbound Email Test"));
@@ -21112,34 +21269,45 @@ Enable it: ${pc24.cyan("wraps email inbound init")}
21112
21269
  const receivingDomain = inbound.receivingDomain || (inbound.subdomain ? `${inbound.subdomain}.${emailConfig.domain}` : emailConfig.domain || "");
21113
21270
  const bucketName = inbound.bucketName || `wraps-inbound-${identity.accountId}-${region}`;
21114
21271
  const testRecipient = `test@${receivingDomain}`;
21272
+ const testSource = `test@${emailConfig.domain}`;
21115
21273
  const testSubject = `Wraps Inbound Test - ${(/* @__PURE__ */ new Date()).toISOString()}`;
21116
21274
  await progress.execute(`Sending test email to ${testRecipient}`, async () => {
21117
21275
  const { SESClient: SESClient7, SendEmailCommand: SendEmailCommand2 } = await import("@aws-sdk/client-ses");
21118
21276
  const ses = new SESClient7({ region });
21119
- await ses.send(
21120
- new SendEmailCommand2({
21121
- Source: `test@${emailConfig.domain}`,
21122
- Destination: {
21123
- ToAddresses: [testRecipient]
21124
- },
21125
- Message: {
21126
- Subject: { Data: testSubject },
21127
- Body: {
21128
- Text: {
21129
- Data: "This is a test email from Wraps CLI to verify inbound email processing."
21130
- },
21131
- Html: {
21132
- Data: "<h1>Wraps Inbound Test</h1><p>This email was sent to verify inbound email processing is working correctly.</p>"
21277
+ try {
21278
+ await ses.send(
21279
+ new SendEmailCommand2({
21280
+ Source: testSource,
21281
+ Destination: {
21282
+ ToAddresses: [testRecipient]
21283
+ },
21284
+ Message: {
21285
+ Subject: { Data: testSubject },
21286
+ Body: {
21287
+ Text: {
21288
+ Data: "This is a test email from Wraps CLI to verify inbound email processing."
21289
+ },
21290
+ Html: {
21291
+ Data: "<h1>Wraps Inbound Test</h1><p>This email was sent to verify inbound email processing is working correctly.</p>"
21292
+ }
21133
21293
  }
21134
21294
  }
21135
- }
21136
- })
21137
- );
21295
+ })
21296
+ );
21297
+ } catch (error) {
21298
+ throw mapInboundTestSendError(error, {
21299
+ source: testSource,
21300
+ recipient: testRecipient,
21301
+ domain: emailConfig.domain || "",
21302
+ receivingDomain,
21303
+ region
21304
+ });
21305
+ }
21138
21306
  });
21139
- const spinner10 = clack22.spinner();
21140
- spinner10.start("Waiting for email to be processed...");
21141
21307
  const { S3Client: S3Client2, ListObjectsV2Command: ListObjectsV2Command2, GetObjectCommand: GetObjectCommand2 } = await import("@aws-sdk/client-s3");
21142
21308
  const s34 = new S3Client2({ region });
21309
+ const spinner10 = clack22.spinner();
21310
+ spinner10.start("Waiting for email to be processed...");
21143
21311
  let found = false;
21144
21312
  const startTime = Date.now();
21145
21313
  const timeout = 3e4;