@wraps.dev/cli 2.24.0 → 2.25.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 CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  > Deploy email, SMS, and CDN infrastructure to your AWS account.
4
4
 
5
+ **AI agents:** fetch [wraps.dev/llms-full.txt](https://wraps.dev/llms-full.txt) for the complete documentation in one file, or [wraps.dev/llms.txt](https://wraps.dev/llms.txt) for an agent-oriented index.
6
+
5
7
  ## What It Creates
6
8
 
7
9
  ### Email (`wraps email init`)
@@ -502,7 +504,7 @@ DMARC (TXT)
502
504
  Policy for how receivers handle emails failing DKIM/SPF checks
503
505
 
504
506
  TXT _dmarc.example.com
505
- → v=DMARC1; p=quarantine; rua=mailto:postmaster@example.com
507
+ → v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@example.com
506
508
  ```
507
509
 
508
510
  ## Hosting Provider Integration
Binary file
package/dist/cli.js CHANGED
@@ -822,7 +822,7 @@ function pulumiErrorToWrapsError(code, iamAction, service, resourceName, resourc
822
822
  );
823
823
  }
824
824
  }
825
- var WrapsError, errors;
825
+ var WrapsError, CREDENTIAL_OPTIONS, errors;
826
826
  var init_errors = __esm({
827
827
  "src/utils/shared/errors.ts"() {
828
828
  "use strict";
@@ -838,11 +838,12 @@ var init_errors = __esm({
838
838
  this.name = "WrapsError";
839
839
  }
840
840
  };
841
+ CREDENTIAL_OPTIONS = "Wraps couldn't find working AWS credentials. Any of these work:\n\nAWS SSO:\n aws configure sso\n aws sso login\n\nIAM access keys:\n aws configure\n\nEnvironment variables:\n export AWS_ACCESS_KEY_ID=<your-key>\n export AWS_SECRET_ACCESS_KEY=<your-secret>\n\nExisting profile:\n export AWS_PROFILE=<profile-name>";
841
842
  errors = {
842
843
  noAWSCredentials: () => new WrapsError(
843
844
  "AWS credentials not found",
844
845
  "NO_AWS_CREDENTIALS",
845
- "Run: aws configure\nOr set AWS_PROFILE environment variable",
846
+ CREDENTIAL_OPTIONS,
846
847
  "https://wraps.dev/docs/guides/aws-setup"
847
848
  ),
848
849
  stackExists: (stackName) => new WrapsError(
@@ -966,7 +967,7 @@ Or configure a new profile:
966
967
  credentialsFileMissing: () => new WrapsError(
967
968
  "AWS credentials file not found",
968
969
  "CREDENTIALS_FILE_MISSING",
969
- "Configure AWS credentials:\n aws configure\n\nOr set environment variables:\n export AWS_ACCESS_KEY_ID=<your-key>\n export AWS_SECRET_ACCESS_KEY=<your-secret>",
970
+ CREDENTIAL_OPTIONS,
970
971
  "https://wraps.dev/docs/guides/aws-setup/permissions"
971
972
  ),
972
973
  accessKeyInvalid: () => new WrapsError(
@@ -1191,22 +1192,27 @@ async function validateAWSCredentials() {
1191
1192
  async function validateAWSCredentialsWithDetails() {
1192
1193
  const state = await detectAWSState();
1193
1194
  const warnings = [];
1194
- if (state.sso.configured && state.sso.tokenStatus?.expired) {
1195
- const profile = state.sso.activeProfile?.name;
1196
- throw errors.ssoSessionExpired(profile);
1197
- }
1198
- if (state.sso.configured && state.sso.tokenStatus?.valid && state.sso.tokenStatus.minutesRemaining !== null && state.sso.tokenStatus.minutesRemaining < 15) {
1199
- const minutes = state.sso.tokenStatus.minutesRemaining;
1200
- const loginCmd = getSSOLoginCommand(state.sso.activeProfile?.name);
1201
- warnings.push(
1202
- `SSO session expires in ${minutes} minute${minutes !== 1 ? "s" : ""}. Run "${loginCmd}" to refresh.`
1203
- );
1204
- }
1205
- const currentProfile = getCurrentProfile();
1206
- if (currentProfile && currentProfile !== "default") {
1207
- const availableProfiles = getConfiguredProfiles();
1208
- if (!availableProfiles.includes(currentProfile)) {
1209
- throw errors.profileNotFound(currentProfile, availableProfiles);
1195
+ const hasStaticEnvCredentials = Boolean(
1196
+ process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY
1197
+ );
1198
+ if (!hasStaticEnvCredentials) {
1199
+ if (state.sso.configured && state.sso.tokenStatus?.expired) {
1200
+ const profile = state.sso.activeProfile?.name;
1201
+ throw errors.ssoSessionExpired(profile);
1202
+ }
1203
+ if (state.sso.configured && state.sso.tokenStatus?.valid && state.sso.tokenStatus.minutesRemaining !== null && state.sso.tokenStatus.minutesRemaining < 15) {
1204
+ const minutes = state.sso.tokenStatus.minutesRemaining;
1205
+ const loginCmd = getSSOLoginCommand(state.sso.activeProfile?.name);
1206
+ warnings.push(
1207
+ `SSO session expires in ${minutes} minute${minutes !== 1 ? "s" : ""}. Run "${loginCmd}" to refresh.`
1208
+ );
1209
+ }
1210
+ const currentProfile = getCurrentProfile();
1211
+ if (currentProfile && currentProfile !== "default") {
1212
+ const availableProfiles = getConfiguredProfiles();
1213
+ if (!availableProfiles.includes(currentProfile)) {
1214
+ throw errors.profileNotFound(currentProfile, availableProfiles);
1215
+ }
1210
1216
  }
1211
1217
  }
1212
1218
  const sts = new STSClient2({ region: "us-east-1" });
@@ -3047,7 +3053,7 @@ async function previewDNSChanges(hostedZoneId, domain, dkimTokens, region, custo
3047
3053
  });
3048
3054
  const dmarcName = `_dmarc.${domain}`;
3049
3055
  const dmarcRuaDomain = mailFromDomain || domain;
3050
- const proposedDMARC = `"v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}"`;
3056
+ const proposedDMARC = `"v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`;
3051
3057
  const existingDMARC = findExistingRecord(existingRecords, dmarcName, "TXT");
3052
3058
  const existingDMARCValue = getRecordValue(existingDMARC);
3053
3059
  let dmarcStatus = "new";
@@ -3209,7 +3215,7 @@ async function createSelectedDNSRecords(hostedZoneId, domain, dkimTokens, region
3209
3215
  TTL: 1800,
3210
3216
  ResourceRecords: [
3211
3217
  {
3212
- Value: `"v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}"`
3218
+ Value: `"v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`
3213
3219
  }
3214
3220
  ]
3215
3221
  }
@@ -3332,7 +3338,7 @@ async function createDNSRecords(hostedZoneId, domain, dkimTokens, region, custom
3332
3338
  TTL: 1800,
3333
3339
  ResourceRecords: [
3334
3340
  {
3335
- Value: `"v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}"`
3341
+ Value: `"v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`
3336
3342
  }
3337
3343
  ]
3338
3344
  }
@@ -3556,7 +3562,7 @@ var init_cloudflare = __esm({
3556
3562
  const dmarcSuccess = await this.createRecord(
3557
3563
  `_dmarc.${domain}`,
3558
3564
  "TXT",
3559
- `v=DMARC1; p=quarantine; rua=mailto:postmaster@${mailFromDomain || domain}`
3565
+ `v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${mailFromDomain || domain}`
3560
3566
  );
3561
3567
  if (dmarcSuccess) {
3562
3568
  recordsCreated++;
@@ -3942,7 +3948,7 @@ var init_vercel = __esm({
3942
3948
  const dmarcSuccess = await this.createRecord(
3943
3949
  `_dmarc.${domain}`,
3944
3950
  "TXT",
3945
- `v=DMARC1; p=quarantine; rua=mailto:postmaster@${mailFromDomain || domain}`
3951
+ `v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${mailFromDomain || domain}`
3946
3952
  );
3947
3953
  if (dmarcSuccess) {
3948
3954
  recordsCreated++;
@@ -4211,7 +4217,7 @@ function buildEmailDNSRecords(data) {
4211
4217
  records.push({
4212
4218
  name: `_dmarc.${domain}`,
4213
4219
  type: "TXT",
4214
- value: `v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}`,
4220
+ value: `v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}`,
4215
4221
  category: "dmarc"
4216
4222
  });
4217
4223
  if (customTrackingDomain) {
@@ -6263,7 +6269,7 @@ Verification should complete within a few minutes.`,
6263
6269
  ),
6264
6270
  "",
6265
6271
  pc10.bold("DMARC Record (TXT):"),
6266
- ` ${pc10.cyan(`_dmarc.${domain}`)} ${pc10.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}"`
6272
+ ` ${pc10.cyan(`_dmarc.${domain}`)} ${pc10.dim("TXT")} "v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`
6267
6273
  );
6268
6274
  if (outputs.mailFromDomain) {
6269
6275
  dnsLines.push(
@@ -6492,7 +6498,7 @@ ${domainStrings.join("\n")}`);
6492
6498
  ),
6493
6499
  "",
6494
6500
  pc10.bold("DMARC Record (TXT):"),
6495
- ` ${pc10.cyan(`_dmarc.${domain.domain}`)} ${pc10.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}"`
6501
+ ` ${pc10.cyan(`_dmarc.${domain.domain}`)} ${pc10.dim("TXT")} "v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`
6496
6502
  );
6497
6503
  }
6498
6504
  if (domain.mailFromDomain && domain.mailFromStatus !== "SUCCESS") {
@@ -13020,6 +13026,11 @@ async function init(options) {
13020
13026
  );
13021
13027
  }
13022
13028
  const progress = new DeploymentProgress();
13029
+ const identity = await progress.execute(
13030
+ "Validating AWS credentials",
13031
+ async () => validateAWSCredentials()
13032
+ );
13033
+ progress.info(`Connected to AWS account: ${pc12.cyan(identity.accountId)}`);
13023
13034
  const wasAutoInstalled = await progress.execute(
13024
13035
  "Checking Pulumi CLI installation",
13025
13036
  async () => await ensurePulumiInstalled()
@@ -13027,11 +13038,6 @@ async function init(options) {
13027
13038
  if (wasAutoInstalled) {
13028
13039
  progress.info("Pulumi CLI was automatically installed");
13029
13040
  }
13030
- const identity = await progress.execute(
13031
- "Validating AWS credentials",
13032
- async () => validateAWSCredentials()
13033
- );
13034
- progress.info(`Connected to AWS account: ${pc12.cyan(identity.accountId)}`);
13035
13041
  let provider = options.provider;
13036
13042
  if (!provider) {
13037
13043
  provider = await promptProvider();
@@ -15408,6 +15414,9 @@ async function checkDmarc(domain) {
15408
15414
  valid: false,
15409
15415
  policy: null,
15410
15416
  subdomainPolicy: null,
15417
+ nonExistentSubdomainPolicy: null,
15418
+ testing: false,
15419
+ psd: null,
15411
15420
  percentage: 100,
15412
15421
  reportingEnabled: false,
15413
15422
  ruaAddresses: [],
@@ -15460,6 +15469,21 @@ function parseDmarcRecord(record, result) {
15460
15469
  } else {
15461
15470
  result.subdomainPolicy = result.policy;
15462
15471
  }
15472
+ const np = tags.get("np");
15473
+ if (np) {
15474
+ if (["none", "quarantine", "reject"].includes(np)) {
15475
+ result.nonExistentSubdomainPolicy = np;
15476
+ } else {
15477
+ result.warnings.push(`Invalid non-existent subdomain policy (np): ${np}`);
15478
+ }
15479
+ }
15480
+ if (tags.get("t") === "y") {
15481
+ result.testing = true;
15482
+ }
15483
+ const psd = tags.get("psd");
15484
+ if (psd && ["y", "n", "u"].includes(psd)) {
15485
+ result.psd = psd;
15486
+ }
15463
15487
  const pct = tags.get("pct");
15464
15488
  if (pct) {
15465
15489
  const pctNum = Number.parseInt(pct, 10);
@@ -15468,7 +15492,9 @@ function parseDmarcRecord(record, result) {
15468
15492
  } else {
15469
15493
  result.percentage = pctNum;
15470
15494
  if (pctNum < 100) {
15471
- result.warnings.push(`Only ${pctNum}% of messages subject to policy`);
15495
+ result.warnings.push(
15496
+ `pct=${pctNum} is deprecated (DMARCbis receivers ignore it; enforcement is all-or-nothing). Use t=y while testing instead.`
15497
+ );
15472
15498
  }
15473
15499
  }
15474
15500
  }
@@ -15533,6 +15559,17 @@ function parseDmarcRecord(record, result) {
15533
15559
  if (result.subdomainPolicy === "none" && result.policy !== "none") {
15534
15560
  result.warnings.push("Subdomain policy is less strict than domain policy");
15535
15561
  }
15562
+ const enforcing = result.policy === "quarantine" || result.policy === "reject";
15563
+ if (result.testing && result.policy !== "none") {
15564
+ result.warnings.push(
15565
+ "t=y (testing) is set \u2014 receivers will not enforce the policy"
15566
+ );
15567
+ }
15568
+ if (enforcing && !result.nonExistentSubdomainPolicy) {
15569
+ result.warnings.push(
15570
+ "No np= set \u2014 add np=reject to block spoofing from non-existent subdomains (DMARCbis)"
15571
+ );
15572
+ }
15536
15573
  if (!result.reportingEnabled) {
15537
15574
  result.warnings.push("No aggregate reporting configured (rua=)");
15538
15575
  }
@@ -16686,11 +16723,18 @@ function collectDmarcIssues(dmarc, deductions, bonuses) {
16686
16723
  reason: "DMARC policy is none (not enforcing)"
16687
16724
  });
16688
16725
  }
16689
- if (dmarc.percentage < 100) {
16726
+ if (dmarc.testing && dmarc.policy !== "none") {
16690
16727
  deductions.push({
16691
16728
  check: "dmarc",
16692
16729
  points: 2,
16693
- reason: `DMARC pct=${dmarc.percentage} (not 100%)`
16730
+ reason: "DMARC testing mode (t=y) \u2014 receivers won't enforce the policy"
16731
+ });
16732
+ }
16733
+ if (dmarc.percentage < 100) {
16734
+ deductions.push({
16735
+ check: "dmarc",
16736
+ points: 1,
16737
+ reason: `DMARC pct=${dmarc.percentage} is deprecated (DMARCbis receivers ignore it; enforcement is inconsistent)`
16694
16738
  });
16695
16739
  }
16696
16740
  if (!dmarc.reportingEnabled) {
@@ -16707,6 +16751,13 @@ function collectDmarcIssues(dmarc, deductions, bonuses) {
16707
16751
  reason: "DMARC strict alignment configured"
16708
16752
  });
16709
16753
  }
16754
+ if ((dmarc.policy === "quarantine" || dmarc.policy === "reject") && dmarc.nonExistentSubdomainPolicy === "reject") {
16755
+ bonuses.push({
16756
+ check: "dmarc",
16757
+ points: 1,
16758
+ reason: "np=reject blocks non-existent subdomain spoofing (DMARCbis)"
16759
+ });
16760
+ }
16710
16761
  }
16711
16762
  function collectInfraIssues(checks, deductions, bonuses) {
16712
16763
  if (checks.mx.exists) {
@@ -24043,13 +24094,6 @@ async function init2(options) {
24043
24094
  );
24044
24095
  }
24045
24096
  const progress = new DeploymentProgress();
24046
- const wasAutoInstalled = await progress.execute(
24047
- "Checking Pulumi CLI installation",
24048
- async () => await ensurePulumiInstalled()
24049
- );
24050
- if (wasAutoInstalled) {
24051
- progress.info("Pulumi CLI was automatically installed");
24052
- }
24053
24097
  const credentialResult = await progress.execute(
24054
24098
  "Validating AWS credentials",
24055
24099
  async () => validateAWSCredentialsWithDetails()
@@ -24064,6 +24108,13 @@ async function init2(options) {
24064
24108
  `Using credentials from: ${pc29.dim(credentialResult.credentialSource)}`
24065
24109
  );
24066
24110
  }
24111
+ const wasAutoInstalled = await progress.execute(
24112
+ "Checking Pulumi CLI installation",
24113
+ async () => await ensurePulumiInstalled()
24114
+ );
24115
+ if (wasAutoInstalled) {
24116
+ progress.info("Pulumi CLI was automatically installed");
24117
+ }
24067
24118
  let provider = options.provider;
24068
24119
  if (!provider) {
24069
24120
  provider = options.quick ? "other" : await promptProvider();
@@ -40168,6 +40219,11 @@ async function init3(options) {
40168
40219
  clack55.intro(pc58.bold("Wraps SMS Infrastructure Setup"));
40169
40220
  }
40170
40221
  const progress = new DeploymentProgress();
40222
+ const identity = await progress.execute(
40223
+ "Validating AWS credentials",
40224
+ async () => validateAWSCredentials()
40225
+ );
40226
+ progress.info(`Connected to AWS account: ${pc58.cyan(identity.accountId)}`);
40171
40227
  const wasAutoInstalled = await progress.execute(
40172
40228
  "Checking Pulumi CLI installation",
40173
40229
  async () => await ensurePulumiInstalled()
@@ -40175,11 +40231,6 @@ async function init3(options) {
40175
40231
  if (wasAutoInstalled) {
40176
40232
  progress.info("Pulumi CLI was automatically installed");
40177
40233
  }
40178
- const identity = await progress.execute(
40179
- "Validating AWS credentials",
40180
- async () => validateAWSCredentials()
40181
- );
40182
- progress.info(`Connected to AWS account: ${pc58.cyan(identity.accountId)}`);
40183
40234
  const provider = options.provider || await promptProvider();
40184
40235
  const region = options.region || await promptRegion(await getAWSRegion());
40185
40236
  let vercelConfig;
@@ -42712,6 +42763,9 @@ async function support() {
42712
42763
  ` ${pc65.bold("GitHub:")} ${pc65.cyan("https://github.com/wraps-dev/wraps/issues")}`
42713
42764
  );
42714
42765
  console.log(` ${pc65.bold("Docs:")} ${pc65.cyan("https://wraps.dev/docs")}`);
42766
+ console.log(
42767
+ ` ${pc65.bold("AI docs:")} ${pc65.cyan("https://wraps.dev/llms-full.txt")} ${pc65.dim("(complete docs, one file)")}`
42768
+ );
42715
42769
  console.log();
42716
42770
  console.log(pc65.dim(" Response time: Usually within 24 hours"));
42717
42771
  console.log();
@@ -43363,7 +43417,10 @@ function showHelp() {
43363
43417
  console.log(` ${pc69.dim("-v, --version")} Show version number
43364
43418
  `);
43365
43419
  console.log(
43366
- `Run ${pc69.cyan("wraps <service> <command> --help")} for more information.
43420
+ `Run ${pc69.cyan("wraps <service> <command> --help")} for more information.`
43421
+ );
43422
+ console.log(
43423
+ `AI agents: complete docs at ${pc69.cyan("https://wraps.dev/llms-full.txt")}
43367
43424
  `
43368
43425
  );
43369
43426
  }