@wraps.dev/cli 3.7.0 → 3.9.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 CHANGED
@@ -27936,6 +27936,30 @@ function displayStatus(status2) {
27936
27936
  `${pc10.bold("Integration:")} ${pc10.cyan(status2.integrationLevel)}`,
27937
27937
  `${pc10.bold("Region:")} ${pc10.cyan(status2.region)}`
27938
27938
  ];
27939
+ if (status2.sending) {
27940
+ const { sandbox, sandboxUncertain } = status2.sending;
27941
+ if (sandboxUncertain) {
27942
+ infoLines.push(
27943
+ `${pc10.bold("Sending:")} ${pc10.yellow("could not determine (SES unreachable)")}`
27944
+ );
27945
+ } else if (sandbox) {
27946
+ infoLines.push(
27947
+ `${pc10.bold("Sending:")} ${pc10.yellow("SANDBOX")} ${pc10.dim("\u2014 you can only send to verified addresses")}`
27948
+ );
27949
+ } else {
27950
+ infoLines.push(`${pc10.bold("Sending:")} ${pc10.green("production")}`);
27951
+ }
27952
+ if (status2.sending.sentLast24Hours !== void 0 || status2.sending.max24HourSend !== void 0 || status2.sending.maxSendRate !== void 0) {
27953
+ infoLines.push(
27954
+ `${pc10.bold("Quota:")} ${pc10.cyan(`${status2.sending.sentLast24Hours ?? 0}/${status2.sending.max24HourSend ?? 0}`)} sent (24h) ${pc10.dim(`\xB7 max rate ${status2.sending.maxSendRate ?? 0}/s`)}`
27955
+ );
27956
+ }
27957
+ }
27958
+ if (status2.cloudFormationStacks && status2.cloudFormationStacks.length > 0) {
27959
+ infoLines.push(
27960
+ `${pc10.bold("CloudFormation stacks:")} ${pc10.cyan(status2.cloudFormationStacks.join(", "))}`
27961
+ );
27962
+ }
27939
27963
  if (status2.domains.length > 0) {
27940
27964
  const PURPOSE_DISPLAY = {
27941
27965
  transactional: "Transactional",
@@ -37368,6 +37392,11 @@ function buildConsolePolicyDocument(emailConfig, smsConfig) {
37368
37392
  // SES v2 API for listing/getting email identities (domains)
37369
37393
  "ses:ListEmailIdentities",
37370
37394
  "ses:GetEmailIdentity",
37395
+ // Write, deliberately. verifyOwnEmailIdentity in
37396
+ // apps/web/src/actions/ses-onboarding.ts creates the operator's own
37397
+ // address as an SES identity so a sandboxed account can complete a
37398
+ // real first send. This is the ONLY identity write the role grants.
37399
+ "ses:CreateEmailIdentity",
37371
37400
  // SES v2 API for configuration set scanning (needed by dashboard).
37372
37401
  // List is what makes Get reachable — Get takes a name only List can find.
37373
37402
  "ses:ListConfigurationSets",
@@ -37418,6 +37447,18 @@ function buildConsolePolicyDocument(emailConfig, smsConfig) {
37418
37447
  Resource: "*"
37419
37448
  });
37420
37449
  }
37450
+ const suppressionList = emailConfig?.suppressionList;
37451
+ if (suppressionList?.enabled !== false) {
37452
+ statements.push({
37453
+ Effect: "Allow",
37454
+ Action: [
37455
+ "ses:ListSuppressedDestinations",
37456
+ "ses:GetSuppressedDestination",
37457
+ "ses:DeleteSuppressedDestination"
37458
+ ],
37459
+ Resource: "*"
37460
+ });
37461
+ }
37421
37462
  statements.push({
37422
37463
  Effect: "Allow",
37423
37464
  Action: [
@@ -51799,6 +51840,7 @@ async function emailStatus(options) {
51799
51840
  }
51800
51841
  }
51801
51842
  let stackOutputs = {};
51843
+ let cloudFormationStacks;
51802
51844
  try {
51803
51845
  await ensurePulumiWorkDir({ accountId: identity.accountId, region });
51804
51846
  const stack = await pulumi24.automation.LocalWorkspace.selectStack({
@@ -51807,17 +51849,32 @@ async function emailStatus(options) {
51807
51849
  });
51808
51850
  stackOutputs = await stack.outputs();
51809
51851
  } catch (_error) {
51810
- progress.stop();
51811
- clack35.log.error("No email infrastructure found");
51812
- console.log(
51813
- `
51852
+ const cfCheck = await findWrapsCloudFormationStacks(region);
51853
+ if (cfCheck.checked && cfCheck.stacks.length > 0) {
51854
+ cloudFormationStacks = cfCheck.stacks;
51855
+ } else {
51856
+ progress.stop();
51857
+ clack35.log.error("No email infrastructure found");
51858
+ if (!cfCheck.checked) {
51859
+ console.log(
51860
+ pc37.dim(
51861
+ "\nCould not check for CloudFormation stacks (missing cloudformation:DescribeStacks).\n"
51862
+ )
51863
+ );
51864
+ }
51865
+ console.log(
51866
+ `
51814
51867
  Run ${pc37.cyan("wraps email init")} to deploy email infrastructure.
51815
51868
  `
51816
- );
51817
- process.exit(1);
51818
- return;
51869
+ );
51870
+ process.exit(1);
51871
+ return;
51872
+ }
51819
51873
  }
51820
- const domains = await listSESDomains(region);
51874
+ const [domains, sesAccountStatus] = await Promise.all([
51875
+ listSESDomains(region),
51876
+ getSESAccountStatus(region)
51877
+ ]);
51821
51878
  const { SESv2Client: SESv2Client12, GetEmailIdentityCommand: GetEmailIdentityCommand7 } = await import("@aws-sdk/client-sesv2");
51822
51879
  const sesv2Client = new SESv2Client12({ region });
51823
51880
  const metadata = await loadConnectionMetadata(identity.accountId, region);
@@ -51880,7 +51937,16 @@ Run ${pc37.cyan("wraps email init")} to deploy email infrastructure.
51880
51937
  customTrackingDomain: stackOutputs.customTrackingDomain?.value,
51881
51938
  httpsEnabled: stackOutputs.httpsTrackingEnabled?.value,
51882
51939
  cloudFrontDomain: stackOutputs.cloudFrontDomain?.value
51883
- } : void 0
51940
+ } : void 0,
51941
+ sending: {
51942
+ sandbox: sesAccountStatus.isSandbox,
51943
+ sandboxUncertain: sesAccountStatus.sandboxUncertain ?? false,
51944
+ max24HourSend: sesAccountStatus.sendQuota?.max24HourSend,
51945
+ maxSendRate: sesAccountStatus.sendQuota?.maxSendRate,
51946
+ sentLast24Hours: sesAccountStatus.sendQuota?.sentLast24Hours,
51947
+ enforcementStatus: sesAccountStatus.enforcementStatus
51948
+ },
51949
+ cloudFormationStacks
51884
51950
  };
51885
51951
  if (isJsonMode()) {
51886
51952
  jsonSuccess("email.status", statusData);
@@ -52734,6 +52800,32 @@ import { join as join14 } from "path";
52734
52800
 
52735
52801
  // ../template-render/src/mustache-case.ts
52736
52802
  init_esm_shims();
52803
+ var HANDLEBARS_BLOCK_HELPERS = /* @__PURE__ */ new Set(["if", "unless", "each", "with"]);
52804
+ function normalizePlainTextMustaches(text14, canonicalVars) {
52805
+ const canonicalByLower = /* @__PURE__ */ new Map();
52806
+ for (const name of canonicalVars) {
52807
+ canonicalByLower.set(name.toLowerCase(), name);
52808
+ }
52809
+ function restoreIdentifier(name) {
52810
+ return canonicalByLower.get(name.toLowerCase()) ?? name;
52811
+ }
52812
+ return text14.replace(
52813
+ /\{\{([#/]?)([A-Z][A-Z0-9_]*(?:\.[A-Z0-9_]+)*)((?:\s+[A-Z0-9_.]+)*)\s*\}\}/g,
52814
+ (match, sigil, name, args) => {
52815
+ const lower = name.toLowerCase();
52816
+ const isHelper = lower === "else" || HANDLEBARS_BLOCK_HELPERS.has(lower);
52817
+ const restoredArgs = args.split(/\s+/).filter(Boolean).map(restoreIdentifier).join(" ");
52818
+ if (isHelper) {
52819
+ const suffix = restoredArgs ? ` ${restoredArgs}` : "";
52820
+ return `{{${sigil}${lower}${suffix}}}`;
52821
+ }
52822
+ if (!args && canonicalByLower.has(lower)) {
52823
+ return `{{${sigil}${canonicalByLower.get(lower)}}}`;
52824
+ }
52825
+ return match;
52826
+ }
52827
+ );
52828
+ }
52737
52829
  function toSesVariableName(name) {
52738
52830
  if (!name.includes(".")) {
52739
52831
  return name;
@@ -53207,37 +53299,6 @@ import pc40 from "picocolors";
53207
53299
 
53208
53300
  // src/utils/email/template-render.ts
53209
53301
  init_esm_shims();
53210
-
53211
- // src/utils/email/template-mustache-case.ts
53212
- init_esm_shims();
53213
- var HANDLEBARS_BLOCK_HELPERS = /* @__PURE__ */ new Set(["if", "unless", "each", "with"]);
53214
- function normalizePlainTextMustaches(text14, canonicalVars) {
53215
- const canonicalByLower = /* @__PURE__ */ new Map();
53216
- for (const name of canonicalVars) {
53217
- canonicalByLower.set(name.toLowerCase(), name);
53218
- }
53219
- function restoreIdentifier(name) {
53220
- return canonicalByLower.get(name.toLowerCase()) ?? name;
53221
- }
53222
- return text14.replace(
53223
- /\{\{([#/]?)([A-Z][A-Z0-9_]*(?:\.[A-Z0-9_]+)*)((?:\s+[A-Z0-9_.]+)*)\s*\}\}/g,
53224
- (match, sigil, name, args) => {
53225
- const lower = name.toLowerCase();
53226
- const isHelper = lower === "else" || HANDLEBARS_BLOCK_HELPERS.has(lower);
53227
- const restoredArgs = args.split(/\s+/).filter(Boolean).map(restoreIdentifier).join(" ");
53228
- if (isHelper) {
53229
- const suffix = restoredArgs ? ` ${restoredArgs}` : "";
53230
- return `{{${sigil}${lower}${suffix}}}`;
53231
- }
53232
- if (!args && canonicalByLower.has(lower)) {
53233
- return `{{${sigil}${canonicalByLower.get(lower)}}}`;
53234
- }
53235
- return match;
53236
- }
53237
- );
53238
- }
53239
-
53240
- // src/utils/email/template-render.ts
53241
53302
  async function renderTemplateWithProxy(Component) {
53242
53303
  const accessedProps = /* @__PURE__ */ new Set();
53243
53304
  const props = new Proxy({}, {