@wraps.dev/cli 2.11.2 → 2.11.4

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
@@ -18,6 +18,54 @@ var init_esm_shims = __esm({
18
18
  }
19
19
  });
20
20
 
21
+ // src/utils/shared/ci-detection.ts
22
+ function isCI() {
23
+ if (process.env.CI === "true" || process.env.CI === "1") {
24
+ return true;
25
+ }
26
+ const ciEnvVars = [
27
+ "GITHUB_ACTIONS",
28
+ // GitHub Actions
29
+ "GITLAB_CI",
30
+ // GitLab CI
31
+ "CIRCLECI",
32
+ // CircleCI
33
+ "TRAVIS",
34
+ // Travis CI
35
+ "JENKINS_URL",
36
+ // Jenkins
37
+ "BUILDKITE",
38
+ // Buildkite
39
+ "DRONE",
40
+ // Drone
41
+ "SEMAPHORE",
42
+ // Semaphore
43
+ "TEAMCITY_VERSION",
44
+ // TeamCity
45
+ "TF_BUILD",
46
+ // Azure Pipelines
47
+ "CODEBUILD_BUILD_ID",
48
+ // AWS CodeBuild
49
+ "NETLIFY",
50
+ // Netlify
51
+ "VERCEL",
52
+ // Vercel
53
+ "HEROKU_TEST_RUN_ID",
54
+ // Heroku CI
55
+ "BUDDY",
56
+ // Buddy
57
+ "BITBUCKET_BUILD_NUMBER"
58
+ // Bitbucket Pipelines
59
+ ];
60
+ return ciEnvVars.some((envVar) => process.env[envVar] !== void 0);
61
+ }
62
+ var init_ci_detection = __esm({
63
+ "src/utils/shared/ci-detection.ts"() {
64
+ "use strict";
65
+ init_esm_shims();
66
+ }
67
+ });
68
+
21
69
  // src/utils/shared/s3-state.ts
22
70
  var s3_state_exports = {};
23
71
  __export(s3_state_exports, {
@@ -369,414 +417,46 @@ var init_config = __esm({
369
417
  }
370
418
  });
371
419
 
372
- // src/utils/shared/aws-detection.ts
373
- import { execSync } from "child_process";
374
- import { existsSync as existsSync4, readdirSync, readFileSync } from "fs";
375
- import { homedir as homedir2 } from "os";
376
- import { join as join4 } from "path";
377
- import { GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
378
- async function isAWSCLIInstalled() {
379
- try {
380
- execSync("aws --version", { stdio: "pipe" });
381
- return true;
382
- } catch {
383
- return false;
384
- }
385
- }
386
- async function getAWSCLIVersion() {
387
- try {
388
- const output4 = execSync("aws --version", { encoding: "utf-8" });
389
- const match = output4.match(/aws-cli\/(\d+\.\d+\.\d+)/);
390
- return match ? match[1] : null;
391
- } catch {
392
- return null;
393
- }
394
- }
395
- function detectCredentialSource() {
396
- if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {
397
- return "environment";
398
- }
399
- if (process.env.AWS_SSO_ACCOUNT_ID || process.env.AWS_SSO_SESSION) {
400
- return "sso";
401
- }
402
- if (process.env.AWS_PROFILE) {
403
- return "profile";
404
- }
405
- const credentialsPath = join4(homedir2(), ".aws", "credentials");
406
- if (existsSync4(credentialsPath)) {
407
- const content = readFileSync(credentialsPath, "utf-8");
408
- if (content.includes("[default]")) {
409
- return "profile";
410
- }
411
- }
412
- const ssoCachePath = join4(homedir2(), ".aws", "sso", "cache");
413
- if (existsSync4(ssoCachePath)) {
414
- return "sso";
415
- }
416
- if (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || process.env.AWS_EXECUTION_ENV) {
417
- return "instance";
418
- }
419
- return null;
420
- }
421
- function detectHostingProvider() {
422
- if (process.env.VERCEL || process.env.VERCEL_ENV) {
423
- return "vercel";
424
- }
425
- if (process.env.RAILWAY_ENVIRONMENT || process.env.RAILWAY_PROJECT_ID) {
426
- return "railway";
427
- }
428
- if (process.env.NETLIFY || process.env.NETLIFY_DEV) {
429
- return "netlify";
430
- }
431
- if (process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.AWS_EXECUTION_ENV || process.env.ECS_CONTAINER_METADATA_URI || process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {
432
- return "aws";
433
- }
434
- return null;
435
- }
436
- async function validateCredentials() {
437
- try {
438
- const sts = new STSClient({ region: "us-east-1" });
439
- const identity = await sts.send(new GetCallerIdentityCommand({}));
440
- return identity.Account || null;
441
- } catch {
442
- return null;
443
- }
444
- }
445
- function getCurrentProfile() {
446
- return process.env.AWS_PROFILE || "default";
447
- }
448
- function getCurrentRegion() {
449
- if (process.env.AWS_REGION) {
450
- return process.env.AWS_REGION;
451
- }
452
- if (process.env.AWS_DEFAULT_REGION) {
453
- return process.env.AWS_DEFAULT_REGION;
454
- }
455
- try {
456
- const region = execSync("aws configure get region", {
457
- encoding: "utf-8",
458
- stdio: ["pipe", "pipe", "pipe"]
459
- }).trim();
460
- return region || null;
461
- } catch {
462
- return null;
463
- }
464
- }
465
- function parseSSOProfiles() {
466
- const configPath = join4(homedir2(), ".aws", "config");
467
- if (!existsSync4(configPath)) {
468
- return [];
469
- }
470
- const content = readFileSync(configPath, "utf-8");
471
- const profiles = [];
472
- const sessionMap = /* @__PURE__ */ new Map();
473
- const sessionSections = content.split(/^\[/m).filter(Boolean);
474
- for (const section of sessionSections) {
475
- const lines = section.split("\n");
476
- const header = lines[0]?.replace("]", "").trim();
477
- if (header?.startsWith("sso-session ")) {
478
- const sessionName = header.replace("sso-session ", "");
479
- const config2 = {};
480
- for (const line of lines.slice(1)) {
481
- const match = line.match(/^\s*([^=\s]+)\s*=\s*(.+?)\s*$/);
482
- if (match) {
483
- config2[match[1]] = match[2];
484
- }
420
+ // src/telemetry/config.ts
421
+ import Conf from "conf";
422
+ import { v4 as uuidv4 } from "uuid";
423
+ var CONFIG_DEFAULTS, TelemetryConfigManager;
424
+ var init_config2 = __esm({
425
+ "src/telemetry/config.ts"() {
426
+ "use strict";
427
+ init_esm_shims();
428
+ CONFIG_DEFAULTS = {
429
+ enabled: true,
430
+ anonymousId: uuidv4(),
431
+ notificationShown: false
432
+ };
433
+ TelemetryConfigManager = class {
434
+ config;
435
+ constructor(options) {
436
+ this.config = new Conf({
437
+ projectName: "wraps",
438
+ configName: "telemetry",
439
+ defaults: CONFIG_DEFAULTS,
440
+ cwd: options?.cwd
441
+ });
485
442
  }
486
- sessionMap.set(sessionName, {
487
- startUrl: config2.sso_start_url || "",
488
- region: config2.sso_region || ""
489
- });
490
- }
491
- }
492
- const sections = content.split(/^\[/m).filter(Boolean);
493
- for (const section of sections) {
494
- const lines = section.split("\n");
495
- const header = lines[0]?.replace("]", "").trim();
496
- if (!header?.startsWith("profile ") && header !== "default") {
497
- continue;
498
- }
499
- const profileName = header === "default" ? "default" : header.replace("profile ", "");
500
- const config2 = {};
501
- for (const line of lines.slice(1)) {
502
- const match = line.match(/^\s*([^=\s]+)\s*=\s*(.+?)\s*$/);
503
- if (match) {
504
- config2[match[1]] = match[2];
443
+ /**
444
+ * Check if telemetry is enabled
445
+ */
446
+ isEnabled() {
447
+ return this.config.get("enabled");
505
448
  }
506
- }
507
- if (config2.sso_start_url || config2.sso_session) {
508
- let ssoStartUrl = config2.sso_start_url || "";
509
- let ssoRegion = config2.sso_region || "";
510
- if (config2.sso_session) {
511
- const session = sessionMap.get(config2.sso_session);
512
- if (session) {
513
- ssoStartUrl = ssoStartUrl || session.startUrl;
514
- ssoRegion = ssoRegion || session.region;
515
- }
449
+ /**
450
+ * Enable or disable telemetry
451
+ */
452
+ setEnabled(enabled) {
453
+ this.config.set("enabled", enabled);
516
454
  }
517
- profiles.push({
518
- name: profileName,
519
- ssoStartUrl,
520
- ssoRegion,
521
- ssoAccountId: config2.sso_account_id || "",
522
- ssoRoleName: config2.sso_role_name || "",
523
- region: config2.region,
524
- ssoSession: config2.sso_session
525
- });
526
- }
527
- }
528
- return profiles;
529
- }
530
- function parseSSOSessions() {
531
- const configPath = join4(homedir2(), ".aws", "config");
532
- if (!existsSync4(configPath)) {
533
- return [];
534
- }
535
- const content = readFileSync(configPath, "utf-8");
536
- const sessions = [];
537
- const sections = content.split(/^\[/m).filter(Boolean);
538
- for (const section of sections) {
539
- const lines = section.split("\n");
540
- const header = lines[0]?.replace("]", "").trim();
541
- if (!header?.startsWith("sso-session ")) {
542
- continue;
543
- }
544
- const sessionName = header.replace("sso-session ", "");
545
- const config2 = {};
546
- for (const line of lines.slice(1)) {
547
- const match = line.match(/^\s*([^=\s]+)\s*=\s*(.+?)\s*$/);
548
- if (match) {
549
- config2[match[1]] = match[2];
550
- }
551
- }
552
- sessions.push({
553
- name: sessionName,
554
- ssoStartUrl: config2.sso_start_url || "",
555
- ssoRegion: config2.sso_region || "",
556
- ssoRegistrationScopes: config2.sso_registration_scopes?.split(",").map((s) => s.trim())
557
- });
558
- }
559
- return sessions;
560
- }
561
- function checkSSOTokenStatus(startUrl) {
562
- const ssoCachePath = join4(homedir2(), ".aws", "sso", "cache");
563
- if (!existsSync4(ssoCachePath)) {
564
- return {
565
- valid: false,
566
- expiresAt: null,
567
- expired: true,
568
- minutesRemaining: null,
569
- startUrl: null
570
- };
571
- }
572
- try {
573
- const cacheFiles = readdirSync(ssoCachePath).filter(
574
- (f) => f.endsWith(".json")
575
- );
576
- for (const file of cacheFiles) {
577
- const content = readFileSync(join4(ssoCachePath, file), "utf-8");
578
- const token = JSON.parse(content);
579
- if (!(token.accessToken && token.expiresAt)) {
580
- continue;
581
- }
582
- if (startUrl && token.startUrl !== startUrl) {
583
- continue;
584
- }
585
- const expiresAt = new Date(token.expiresAt);
586
- const now = /* @__PURE__ */ new Date();
587
- const expired = expiresAt <= now;
588
- const minutesRemaining = Math.floor(
589
- (expiresAt.getTime() - now.getTime()) / 6e4
590
- );
591
- return {
592
- valid: !expired,
593
- expiresAt,
594
- expired,
595
- minutesRemaining,
596
- startUrl: token.startUrl || null
597
- };
598
- }
599
- } catch {
600
- }
601
- return {
602
- valid: false,
603
- expiresAt: null,
604
- expired: true,
605
- minutesRemaining: null,
606
- startUrl: null
607
- };
608
- }
609
- function getActiveSSOProfile(profiles) {
610
- const currentProfile = process.env.AWS_PROFILE || "default";
611
- return profiles.find((p) => p.name === currentProfile) || null;
612
- }
613
- function getSSOLoginCommand(profile) {
614
- if (profile && profile !== "default") {
615
- return `aws sso login --profile ${profile}`;
616
- }
617
- return "aws sso login";
618
- }
619
- function formatSSOProfile(profile) {
620
- return `${profile.name} (${profile.ssoAccountId} / ${profile.ssoRoleName})`;
621
- }
622
- async function detectAWSState() {
623
- const [cliInstalled, cliVersion, accountId] = await Promise.all([
624
- isAWSCLIInstalled(),
625
- getAWSCLIVersion(),
626
- validateCredentials()
627
- ]);
628
- const credentialSource = detectCredentialSource();
629
- const detectedProvider = detectHostingProvider();
630
- const region = getCurrentRegion();
631
- const profileName = getCurrentProfile();
632
- const ssoProfiles = parseSSOProfiles();
633
- const ssoSessions = parseSSOSessions();
634
- const activeProfile = getActiveSSOProfile(ssoProfiles);
635
- const tokenStatus = ssoProfiles.length > 0 ? checkSSOTokenStatus(activeProfile?.ssoStartUrl) : null;
636
- const isUsingSSO = credentialSource === "sso" || activeProfile !== null && accountId !== null;
637
- return {
638
- cliInstalled,
639
- cliVersion,
640
- credentialsConfigured: accountId !== null,
641
- credentialSource: isUsingSSO ? "sso" : accountId !== null ? credentialSource : null,
642
- profileName,
643
- accountId,
644
- detectedProvider,
645
- region,
646
- sso: {
647
- configured: ssoProfiles.length > 0,
648
- profiles: ssoProfiles,
649
- sessions: ssoSessions,
650
- tokenStatus,
651
- activeProfile: isUsingSSO ? activeProfile : null
652
- }
653
- };
654
- }
655
- function hasCredentialsFile() {
656
- const credentialsPath = join4(homedir2(), ".aws", "credentials");
657
- return existsSync4(credentialsPath);
658
- }
659
- function hasConfigFile() {
660
- const configPath = join4(homedir2(), ".aws", "config");
661
- return existsSync4(configPath);
662
- }
663
- function getConfiguredProfiles() {
664
- const profiles = [];
665
- const credentialsPath = join4(homedir2(), ".aws", "credentials");
666
- if (existsSync4(credentialsPath)) {
667
- const content = readFileSync(credentialsPath, "utf-8");
668
- const matches = content.matchAll(/\[([^\]]+)\]/g);
669
- for (const match of matches) {
670
- profiles.push(match[1]);
671
- }
672
- }
673
- const configPath = join4(homedir2(), ".aws", "config");
674
- if (existsSync4(configPath)) {
675
- const content = readFileSync(configPath, "utf-8");
676
- const matches = content.matchAll(/\[profile ([^\]]+)\]/g);
677
- for (const match of matches) {
678
- if (!profiles.includes(match[1])) {
679
- profiles.push(match[1]);
680
- }
681
- }
682
- }
683
- return profiles;
684
- }
685
- var init_aws_detection = __esm({
686
- "src/utils/shared/aws-detection.ts"() {
687
- "use strict";
688
- init_esm_shims();
689
- }
690
- });
691
-
692
- // src/utils/shared/ci-detection.ts
693
- function isCI() {
694
- if (process.env.CI === "true" || process.env.CI === "1") {
695
- return true;
696
- }
697
- const ciEnvVars = [
698
- "GITHUB_ACTIONS",
699
- // GitHub Actions
700
- "GITLAB_CI",
701
- // GitLab CI
702
- "CIRCLECI",
703
- // CircleCI
704
- "TRAVIS",
705
- // Travis CI
706
- "JENKINS_URL",
707
- // Jenkins
708
- "BUILDKITE",
709
- // Buildkite
710
- "DRONE",
711
- // Drone
712
- "SEMAPHORE",
713
- // Semaphore
714
- "TEAMCITY_VERSION",
715
- // TeamCity
716
- "TF_BUILD",
717
- // Azure Pipelines
718
- "CODEBUILD_BUILD_ID",
719
- // AWS CodeBuild
720
- "NETLIFY",
721
- // Netlify
722
- "VERCEL",
723
- // Vercel
724
- "HEROKU_TEST_RUN_ID",
725
- // Heroku CI
726
- "BUDDY",
727
- // Buddy
728
- "BITBUCKET_BUILD_NUMBER"
729
- // Bitbucket Pipelines
730
- ];
731
- return ciEnvVars.some((envVar) => process.env[envVar] !== void 0);
732
- }
733
- var init_ci_detection = __esm({
734
- "src/utils/shared/ci-detection.ts"() {
735
- "use strict";
736
- init_esm_shims();
737
- }
738
- });
739
-
740
- // src/telemetry/config.ts
741
- import Conf from "conf";
742
- import { v4 as uuidv4 } from "uuid";
743
- var CONFIG_DEFAULTS, TelemetryConfigManager;
744
- var init_config2 = __esm({
745
- "src/telemetry/config.ts"() {
746
- "use strict";
747
- init_esm_shims();
748
- CONFIG_DEFAULTS = {
749
- enabled: true,
750
- anonymousId: uuidv4(),
751
- notificationShown: false
752
- };
753
- TelemetryConfigManager = class {
754
- config;
755
- constructor(options) {
756
- this.config = new Conf({
757
- projectName: "wraps",
758
- configName: "telemetry",
759
- defaults: CONFIG_DEFAULTS,
760
- cwd: options?.cwd
761
- });
762
- }
763
- /**
764
- * Check if telemetry is enabled
765
- */
766
- isEnabled() {
767
- return this.config.get("enabled");
768
- }
769
- /**
770
- * Enable or disable telemetry
771
- */
772
- setEnabled(enabled) {
773
- this.config.set("enabled", enabled);
774
- }
775
- /**
776
- * Get the anonymous user ID
777
- */
778
- getAnonymousId() {
779
- return this.config.get("anonymousId");
455
+ /**
456
+ * Get the anonymous user ID
457
+ */
458
+ getAnonymousId() {
459
+ return this.config.get("anonymousId");
780
460
  }
781
461
  /**
782
462
  * Check if the first-run notification has been shown
@@ -811,10 +491,10 @@ var init_config2 = __esm({
811
491
  });
812
492
 
813
493
  // src/telemetry/client.ts
814
- import { readFileSync as readFileSync2 } from "fs";
815
- import { dirname, join as join5 } from "path";
494
+ import { readFileSync } from "fs";
495
+ import { dirname, join as join4 } from "path";
816
496
  import { fileURLToPath as fileURLToPath2 } from "url";
817
- import pc4 from "picocolors";
497
+ import pc from "picocolors";
818
498
  function getTelemetryClient() {
819
499
  if (!telemetryInstance) {
820
500
  telemetryInstance = new TelemetryClient();
@@ -1041,91 +721,411 @@ var init_client = __esm({
1041
721
  if (this.hasShownFooter) {
1042
722
  return false;
1043
723
  }
1044
- this.hasShownFooter = true;
1045
- console.log();
1046
- console.log(pc4.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1047
- console.log("\u{1F4CA} Wraps Platform \u2014 analytics, templates, automations");
1048
- console.log(` From $10/mo \u2192 ${pc4.cyan("https://wraps.dev/platform")}`);
1049
- console.log();
1050
- console.log(`\u{1F4AC} ${pc4.cyan("hey@wraps.sh")}`);
1051
- console.log(pc4.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1052
- return true;
724
+ this.hasShownFooter = true;
725
+ console.log();
726
+ console.log(pc.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
727
+ console.log("\u{1F4CA} Wraps Platform \u2014 analytics, templates, automations");
728
+ console.log(` From $10/mo \u2192 ${pc.cyan("https://wraps.dev/platform")}`);
729
+ console.log();
730
+ console.log(`\u{1F4AC} ${pc.cyan("hey@wraps.sh")}`);
731
+ console.log(pc.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
732
+ return true;
733
+ }
734
+ /**
735
+ * Get CLI version from package.json
736
+ */
737
+ getCLIVersion() {
738
+ try {
739
+ const __filename3 = fileURLToPath2(import.meta.url);
740
+ const __dirname4 = dirname(__filename3);
741
+ const pkg = JSON.parse(
742
+ readFileSync(join4(__dirname4, "../package.json"), "utf-8")
743
+ );
744
+ return pkg.version;
745
+ } catch {
746
+ return "unknown";
747
+ }
748
+ }
749
+ };
750
+ telemetryInstance = null;
751
+ }
752
+ });
753
+
754
+ // src/telemetry/events.ts
755
+ function trackCommand(command, metadata) {
756
+ const client = getTelemetryClient();
757
+ const sanitized = metadata ? { ...metadata } : {};
758
+ sanitized.domain = void 0;
759
+ sanitized.accountId = void 0;
760
+ sanitized.email = void 0;
761
+ client.track(`command:${command}`, sanitized);
762
+ }
763
+ function trackServiceInit(service, success, metadata) {
764
+ const client = getTelemetryClient();
765
+ client.track("service:init", {
766
+ service,
767
+ success,
768
+ ...metadata
769
+ });
770
+ }
771
+ function trackServiceDeployed(service, metadata) {
772
+ const client = getTelemetryClient();
773
+ client.track("service:deployed", {
774
+ service,
775
+ ...metadata
776
+ });
777
+ }
778
+ function trackError(errorCode, command, metadata) {
779
+ const client = getTelemetryClient();
780
+ client.track("error:occurred", {
781
+ error_code: errorCode,
782
+ command,
783
+ ...metadata
784
+ });
785
+ }
786
+ function trackFeature(feature, metadata) {
787
+ const client = getTelemetryClient();
788
+ client.track(`feature:${feature}`, metadata || {});
789
+ }
790
+ function trackServiceUpgrade(service, metadata) {
791
+ const client = getTelemetryClient();
792
+ client.track("service:upgraded", {
793
+ service,
794
+ ...metadata
795
+ });
796
+ }
797
+ function trackServiceRemoved(service, metadata) {
798
+ const client = getTelemetryClient();
799
+ client.track("service:removed", {
800
+ service,
801
+ ...metadata
802
+ });
803
+ }
804
+ var init_events = __esm({
805
+ "src/telemetry/events.ts"() {
806
+ "use strict";
807
+ init_esm_shims();
808
+ init_client();
809
+ }
810
+ });
811
+
812
+ // src/utils/shared/aws-detection.ts
813
+ import { execSync } from "child_process";
814
+ import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2 } from "fs";
815
+ import { homedir as homedir2 } from "os";
816
+ import { join as join5 } from "path";
817
+ import { GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
818
+ async function isAWSCLIInstalled() {
819
+ try {
820
+ execSync("aws --version", { stdio: "pipe" });
821
+ return true;
822
+ } catch {
823
+ return false;
824
+ }
825
+ }
826
+ async function getAWSCLIVersion() {
827
+ try {
828
+ const output4 = execSync("aws --version", { encoding: "utf-8" });
829
+ const match = output4.match(/aws-cli\/(\d+\.\d+\.\d+)/);
830
+ return match ? match[1] : null;
831
+ } catch {
832
+ return null;
833
+ }
834
+ }
835
+ function detectCredentialSource() {
836
+ if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {
837
+ return "environment";
838
+ }
839
+ if (process.env.AWS_SSO_ACCOUNT_ID || process.env.AWS_SSO_SESSION) {
840
+ return "sso";
841
+ }
842
+ if (process.env.AWS_PROFILE) {
843
+ return "profile";
844
+ }
845
+ const credentialsPath = join5(homedir2(), ".aws", "credentials");
846
+ if (existsSync4(credentialsPath)) {
847
+ const content = readFileSync2(credentialsPath, "utf-8");
848
+ if (content.includes("[default]")) {
849
+ return "profile";
850
+ }
851
+ }
852
+ const ssoCachePath = join5(homedir2(), ".aws", "sso", "cache");
853
+ if (existsSync4(ssoCachePath)) {
854
+ return "sso";
855
+ }
856
+ if (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || process.env.AWS_EXECUTION_ENV) {
857
+ return "instance";
858
+ }
859
+ return null;
860
+ }
861
+ function detectHostingProvider() {
862
+ if (process.env.VERCEL || process.env.VERCEL_ENV) {
863
+ return "vercel";
864
+ }
865
+ if (process.env.RAILWAY_ENVIRONMENT || process.env.RAILWAY_PROJECT_ID) {
866
+ return "railway";
867
+ }
868
+ if (process.env.NETLIFY || process.env.NETLIFY_DEV) {
869
+ return "netlify";
870
+ }
871
+ if (process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.AWS_EXECUTION_ENV || process.env.ECS_CONTAINER_METADATA_URI || process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {
872
+ return "aws";
873
+ }
874
+ return null;
875
+ }
876
+ async function validateCredentials() {
877
+ try {
878
+ const sts = new STSClient({ region: "us-east-1" });
879
+ const identity = await sts.send(new GetCallerIdentityCommand({}));
880
+ return identity.Account || null;
881
+ } catch {
882
+ return null;
883
+ }
884
+ }
885
+ function getCurrentProfile() {
886
+ return process.env.AWS_PROFILE || "default";
887
+ }
888
+ function getCurrentRegion() {
889
+ if (process.env.AWS_REGION) {
890
+ return process.env.AWS_REGION;
891
+ }
892
+ if (process.env.AWS_DEFAULT_REGION) {
893
+ return process.env.AWS_DEFAULT_REGION;
894
+ }
895
+ try {
896
+ const region = execSync("aws configure get region", {
897
+ encoding: "utf-8",
898
+ stdio: ["pipe", "pipe", "pipe"]
899
+ }).trim();
900
+ return region || null;
901
+ } catch {
902
+ return null;
903
+ }
904
+ }
905
+ function parseSSOProfiles() {
906
+ const configPath = join5(homedir2(), ".aws", "config");
907
+ if (!existsSync4(configPath)) {
908
+ return [];
909
+ }
910
+ const content = readFileSync2(configPath, "utf-8");
911
+ const profiles = [];
912
+ const sessionMap = /* @__PURE__ */ new Map();
913
+ const sessionSections = content.split(/^\[/m).filter(Boolean);
914
+ for (const section of sessionSections) {
915
+ const lines = section.split("\n");
916
+ const header = lines[0]?.replace("]", "").trim();
917
+ if (header?.startsWith("sso-session ")) {
918
+ const sessionName = header.replace("sso-session ", "");
919
+ const config2 = {};
920
+ for (const line of lines.slice(1)) {
921
+ const match = line.match(/^\s*([^=\s]+)\s*=\s*(.+?)\s*$/);
922
+ if (match) {
923
+ config2[match[1]] = match[2];
924
+ }
1053
925
  }
1054
- /**
1055
- * Get CLI version from package.json
1056
- */
1057
- getCLIVersion() {
1058
- try {
1059
- const __filename3 = fileURLToPath2(import.meta.url);
1060
- const __dirname4 = dirname(__filename3);
1061
- const pkg = JSON.parse(
1062
- readFileSync2(join5(__dirname4, "../package.json"), "utf-8")
1063
- );
1064
- return pkg.version;
1065
- } catch {
1066
- return "unknown";
926
+ sessionMap.set(sessionName, {
927
+ startUrl: config2.sso_start_url || "",
928
+ region: config2.sso_region || ""
929
+ });
930
+ }
931
+ }
932
+ const sections = content.split(/^\[/m).filter(Boolean);
933
+ for (const section of sections) {
934
+ const lines = section.split("\n");
935
+ const header = lines[0]?.replace("]", "").trim();
936
+ if (!header?.startsWith("profile ") && header !== "default") {
937
+ continue;
938
+ }
939
+ const profileName = header === "default" ? "default" : header.replace("profile ", "");
940
+ const config2 = {};
941
+ for (const line of lines.slice(1)) {
942
+ const match = line.match(/^\s*([^=\s]+)\s*=\s*(.+?)\s*$/);
943
+ if (match) {
944
+ config2[match[1]] = match[2];
945
+ }
946
+ }
947
+ if (config2.sso_start_url || config2.sso_session) {
948
+ let ssoStartUrl = config2.sso_start_url || "";
949
+ let ssoRegion = config2.sso_region || "";
950
+ if (config2.sso_session) {
951
+ const session = sessionMap.get(config2.sso_session);
952
+ if (session) {
953
+ ssoStartUrl = ssoStartUrl || session.startUrl;
954
+ ssoRegion = ssoRegion || session.region;
1067
955
  }
1068
956
  }
957
+ profiles.push({
958
+ name: profileName,
959
+ ssoStartUrl,
960
+ ssoRegion,
961
+ ssoAccountId: config2.sso_account_id || "",
962
+ ssoRoleName: config2.sso_role_name || "",
963
+ region: config2.region,
964
+ ssoSession: config2.sso_session
965
+ });
966
+ }
967
+ }
968
+ return profiles;
969
+ }
970
+ function parseSSOSessions() {
971
+ const configPath = join5(homedir2(), ".aws", "config");
972
+ if (!existsSync4(configPath)) {
973
+ return [];
974
+ }
975
+ const content = readFileSync2(configPath, "utf-8");
976
+ const sessions = [];
977
+ const sections = content.split(/^\[/m).filter(Boolean);
978
+ for (const section of sections) {
979
+ const lines = section.split("\n");
980
+ const header = lines[0]?.replace("]", "").trim();
981
+ if (!header?.startsWith("sso-session ")) {
982
+ continue;
983
+ }
984
+ const sessionName = header.replace("sso-session ", "");
985
+ const config2 = {};
986
+ for (const line of lines.slice(1)) {
987
+ const match = line.match(/^\s*([^=\s]+)\s*=\s*(.+?)\s*$/);
988
+ if (match) {
989
+ config2[match[1]] = match[2];
990
+ }
991
+ }
992
+ sessions.push({
993
+ name: sessionName,
994
+ ssoStartUrl: config2.sso_start_url || "",
995
+ ssoRegion: config2.sso_region || "",
996
+ ssoRegistrationScopes: config2.sso_registration_scopes?.split(",").map((s) => s.trim())
997
+ });
998
+ }
999
+ return sessions;
1000
+ }
1001
+ function checkSSOTokenStatus(startUrl) {
1002
+ const ssoCachePath = join5(homedir2(), ".aws", "sso", "cache");
1003
+ if (!existsSync4(ssoCachePath)) {
1004
+ return {
1005
+ valid: false,
1006
+ expiresAt: null,
1007
+ expired: true,
1008
+ minutesRemaining: null,
1009
+ startUrl: null
1069
1010
  };
1070
- telemetryInstance = null;
1071
1011
  }
1072
- });
1073
-
1074
- // src/telemetry/events.ts
1075
- function trackCommand(command, metadata) {
1076
- const client = getTelemetryClient();
1077
- const sanitized = metadata ? { ...metadata } : {};
1078
- sanitized.domain = void 0;
1079
- sanitized.accountId = void 0;
1080
- sanitized.email = void 0;
1081
- client.track(`command:${command}`, sanitized);
1012
+ try {
1013
+ const cacheFiles = readdirSync(ssoCachePath).filter(
1014
+ (f) => f.endsWith(".json")
1015
+ );
1016
+ for (const file of cacheFiles) {
1017
+ const content = readFileSync2(join5(ssoCachePath, file), "utf-8");
1018
+ const token = JSON.parse(content);
1019
+ if (!(token.accessToken && token.expiresAt)) {
1020
+ continue;
1021
+ }
1022
+ if (startUrl && token.startUrl !== startUrl) {
1023
+ continue;
1024
+ }
1025
+ const expiresAt = new Date(token.expiresAt);
1026
+ const now = /* @__PURE__ */ new Date();
1027
+ const expired = expiresAt <= now;
1028
+ const minutesRemaining = Math.floor(
1029
+ (expiresAt.getTime() - now.getTime()) / 6e4
1030
+ );
1031
+ return {
1032
+ valid: !expired,
1033
+ expiresAt,
1034
+ expired,
1035
+ minutesRemaining,
1036
+ startUrl: token.startUrl || null
1037
+ };
1038
+ }
1039
+ } catch {
1040
+ }
1041
+ return {
1042
+ valid: false,
1043
+ expiresAt: null,
1044
+ expired: true,
1045
+ minutesRemaining: null,
1046
+ startUrl: null
1047
+ };
1082
1048
  }
1083
- function trackServiceInit(service, success, metadata) {
1084
- const client = getTelemetryClient();
1085
- client.track("service:init", {
1086
- service,
1087
- success,
1088
- ...metadata
1089
- });
1049
+ function getActiveSSOProfile(profiles) {
1050
+ const currentProfile = process.env.AWS_PROFILE || "default";
1051
+ return profiles.find((p) => p.name === currentProfile) || null;
1090
1052
  }
1091
- function trackServiceDeployed(service, metadata) {
1092
- const client = getTelemetryClient();
1093
- client.track("service:deployed", {
1094
- service,
1095
- ...metadata
1096
- });
1053
+ function getSSOLoginCommand(profile) {
1054
+ if (profile && profile !== "default") {
1055
+ return `aws sso login --profile ${profile}`;
1056
+ }
1057
+ return "aws sso login";
1097
1058
  }
1098
- function trackError(errorCode, command, metadata) {
1099
- const client = getTelemetryClient();
1100
- client.track("error:occurred", {
1101
- error_code: errorCode,
1102
- command,
1103
- ...metadata
1104
- });
1059
+ function formatSSOProfile(profile) {
1060
+ return `${profile.name} (${profile.ssoAccountId} / ${profile.ssoRoleName})`;
1105
1061
  }
1106
- function trackFeature(feature, metadata) {
1107
- const client = getTelemetryClient();
1108
- client.track(`feature:${feature}`, metadata || {});
1062
+ async function detectAWSState() {
1063
+ const [cliInstalled, cliVersion, accountId] = await Promise.all([
1064
+ isAWSCLIInstalled(),
1065
+ getAWSCLIVersion(),
1066
+ validateCredentials()
1067
+ ]);
1068
+ const credentialSource = detectCredentialSource();
1069
+ const detectedProvider = detectHostingProvider();
1070
+ const region = getCurrentRegion();
1071
+ const profileName = getCurrentProfile();
1072
+ const ssoProfiles = parseSSOProfiles();
1073
+ const ssoSessions = parseSSOSessions();
1074
+ const activeProfile = getActiveSSOProfile(ssoProfiles);
1075
+ const tokenStatus = ssoProfiles.length > 0 ? checkSSOTokenStatus(activeProfile?.ssoStartUrl) : null;
1076
+ const isUsingSSO = credentialSource === "sso" || activeProfile !== null && accountId !== null;
1077
+ return {
1078
+ cliInstalled,
1079
+ cliVersion,
1080
+ credentialsConfigured: accountId !== null,
1081
+ credentialSource: isUsingSSO ? "sso" : accountId !== null ? credentialSource : null,
1082
+ profileName,
1083
+ accountId,
1084
+ detectedProvider,
1085
+ region,
1086
+ sso: {
1087
+ configured: ssoProfiles.length > 0,
1088
+ profiles: ssoProfiles,
1089
+ sessions: ssoSessions,
1090
+ tokenStatus,
1091
+ activeProfile: isUsingSSO ? activeProfile : null
1092
+ }
1093
+ };
1109
1094
  }
1110
- function trackServiceUpgrade(service, metadata) {
1111
- const client = getTelemetryClient();
1112
- client.track("service:upgraded", {
1113
- service,
1114
- ...metadata
1115
- });
1095
+ function hasCredentialsFile() {
1096
+ const credentialsPath = join5(homedir2(), ".aws", "credentials");
1097
+ return existsSync4(credentialsPath);
1116
1098
  }
1117
- function trackServiceRemoved(service, metadata) {
1118
- const client = getTelemetryClient();
1119
- client.track("service:removed", {
1120
- service,
1121
- ...metadata
1122
- });
1099
+ function hasConfigFile() {
1100
+ const configPath = join5(homedir2(), ".aws", "config");
1101
+ return existsSync4(configPath);
1123
1102
  }
1124
- var init_events = __esm({
1125
- "src/telemetry/events.ts"() {
1103
+ function getConfiguredProfiles() {
1104
+ const profiles = [];
1105
+ const credentialsPath = join5(homedir2(), ".aws", "credentials");
1106
+ if (existsSync4(credentialsPath)) {
1107
+ const content = readFileSync2(credentialsPath, "utf-8");
1108
+ const matches = content.matchAll(/\[([^\]]+)\]/g);
1109
+ for (const match of matches) {
1110
+ profiles.push(match[1]);
1111
+ }
1112
+ }
1113
+ const configPath = join5(homedir2(), ".aws", "config");
1114
+ if (existsSync4(configPath)) {
1115
+ const content = readFileSync2(configPath, "utf-8");
1116
+ const matches = content.matchAll(/\[profile ([^\]]+)\]/g);
1117
+ for (const match of matches) {
1118
+ if (!profiles.includes(match[1])) {
1119
+ profiles.push(match[1]);
1120
+ }
1121
+ }
1122
+ }
1123
+ return profiles;
1124
+ }
1125
+ var init_aws_detection = __esm({
1126
+ "src/utils/shared/aws-detection.ts"() {
1126
1127
  "use strict";
1127
1128
  init_esm_shims();
1128
- init_client();
1129
1129
  }
1130
1130
  });
1131
1131
 
@@ -4880,9 +4880,36 @@ var init_lambda = __esm({
4880
4880
  // src/infrastructure/resources/acm.ts
4881
4881
  var acm_exports = {};
4882
4882
  __export(acm_exports, {
4883
+ checkCertificateValidation: () => checkCertificateValidation,
4883
4884
  createACMCertificate: () => createACMCertificate
4884
4885
  });
4886
+ import { ACMClient as ACMClient2, DescribeCertificateCommand as DescribeCertificateCommand2 } from "@aws-sdk/client-acm";
4885
4887
  import * as aws12 from "@pulumi/aws";
4888
+ async function checkCertificateValidation(domain) {
4889
+ try {
4890
+ const acm3 = new ACMClient2({ region: "us-east-1" });
4891
+ const { ListCertificatesCommand } = await import("@aws-sdk/client-acm");
4892
+ const listResponse = await acm3.send(
4893
+ new ListCertificatesCommand({
4894
+ CertificateStatuses: ["ISSUED"]
4895
+ })
4896
+ );
4897
+ const cert = listResponse.CertificateSummaryList?.find(
4898
+ (c) => c.DomainName === domain
4899
+ );
4900
+ if (cert?.CertificateArn) {
4901
+ const describeResponse = await acm3.send(
4902
+ new DescribeCertificateCommand2({
4903
+ CertificateArn: cert.CertificateArn
4904
+ })
4905
+ );
4906
+ return describeResponse.Certificate?.Status === "ISSUED";
4907
+ }
4908
+ return false;
4909
+ } catch (error) {
4910
+ return false;
4911
+ }
4912
+ }
4886
4913
  async function createACMCertificate(config2) {
4887
4914
  const usEast1Provider = new aws12.Provider("acm-us-east-1", {
4888
4915
  region: "us-east-1"
@@ -5618,6 +5645,10 @@ var init_eventbridge_inbound = __esm({
5618
5645
  });
5619
5646
 
5620
5647
  // src/utils/dns/cloudflare.ts
5648
+ var cloudflare_exports = {};
5649
+ __export(cloudflare_exports, {
5650
+ CloudflareDNSClient: () => CloudflareDNSClient
5651
+ });
5621
5652
  var CLOUDFLARE_API_BASE, CloudflareDNSClient;
5622
5653
  var init_cloudflare = __esm({
5623
5654
  "src/utils/dns/cloudflare.ts"() {
@@ -5844,6 +5875,92 @@ var init_cloudflare = __esm({
5844
5875
  };
5845
5876
  }
5846
5877
  }
5878
+ /**
5879
+ * Get the zone name (domain) for this zone ID
5880
+ */
5881
+ async getZoneName() {
5882
+ try {
5883
+ const response = await fetch(
5884
+ `${CLOUDFLARE_API_BASE}/zones/${this.zoneId}`,
5885
+ {
5886
+ headers: {
5887
+ Authorization: `Bearer ${this.apiToken}`,
5888
+ "Content-Type": "application/json"
5889
+ }
5890
+ }
5891
+ );
5892
+ const data = await response.json();
5893
+ return data.success ? data.result.name : null;
5894
+ } catch {
5895
+ return null;
5896
+ }
5897
+ }
5898
+ /**
5899
+ * Get all CAA records for the zone
5900
+ */
5901
+ async getCAARecords() {
5902
+ const zoneName = await this.getZoneName();
5903
+ if (!zoneName) return [];
5904
+ const result = await this.request(
5905
+ "/dns_records?type=CAA"
5906
+ );
5907
+ if (!(result.success && result.result)) {
5908
+ return [];
5909
+ }
5910
+ return result.result.filter((r) => r.type === "CAA").map((r) => {
5911
+ const match = r.content.match(/^(\d+)\s+(\w+)\s+"?([^"]+)"?$/);
5912
+ if (match) {
5913
+ return {
5914
+ flags: Number.parseInt(match[1], 10),
5915
+ tag: match[2],
5916
+ value: match[3]
5917
+ };
5918
+ }
5919
+ return null;
5920
+ }).filter(
5921
+ (r) => r !== null
5922
+ );
5923
+ }
5924
+ /**
5925
+ * Check if Amazon is allowed to issue certificates based on CAA records
5926
+ */
5927
+ async isAmazonCAAAllowed() {
5928
+ const caaRecords = await this.getCAARecords();
5929
+ const issueRecords = caaRecords.filter(
5930
+ (r) => r.tag === "issue" || r.tag === "issuewild"
5931
+ );
5932
+ if (issueRecords.length === 0) {
5933
+ return { allowed: true, hasCAA: false, existingCAs: [] };
5934
+ }
5935
+ const existingCAs = issueRecords.map((r) => r.value);
5936
+ const amazonAllowed = existingCAs.some(
5937
+ (ca) => ca.includes("amazon.com") || ca.includes("amazontrust.com")
5938
+ );
5939
+ return { allowed: amazonAllowed, hasCAA: true, existingCAs };
5940
+ }
5941
+ /**
5942
+ * Add a CAA record to allow Amazon to issue certificates
5943
+ */
5944
+ async addAmazonCAARecord() {
5945
+ const zoneName = await this.getZoneName();
5946
+ if (!zoneName) return false;
5947
+ const body = {
5948
+ name: zoneName,
5949
+ type: "CAA",
5950
+ data: {
5951
+ flags: 0,
5952
+ tag: "issue",
5953
+ value: "amazon.com"
5954
+ },
5955
+ ttl: 1800
5956
+ };
5957
+ const result = await this.request(
5958
+ "/dns_records",
5959
+ "POST",
5960
+ body
5961
+ );
5962
+ return result.success;
5963
+ }
5847
5964
  async verifyRecords(data) {
5848
5965
  const { domain, dkimTokens, mailFromDomain, region } = data;
5849
5966
  const missing = [];
@@ -5891,6 +6008,10 @@ var init_cloudflare = __esm({
5891
6008
  });
5892
6009
 
5893
6010
  // src/utils/dns/vercel.ts
6011
+ var vercel_exports = {};
6012
+ __export(vercel_exports, {
6013
+ VercelDNSClient: () => VercelDNSClient
6014
+ });
5894
6015
  var VERCEL_API_BASE, VercelDNSClient;
5895
6016
  var init_vercel = __esm({
5896
6017
  "src/utils/dns/vercel.ts"() {
@@ -6122,6 +6243,68 @@ var init_vercel = __esm({
6122
6243
  };
6123
6244
  }
6124
6245
  }
6246
+ /**
6247
+ * Get all CAA records for the domain
6248
+ */
6249
+ async getCAARecords() {
6250
+ const result = await this.request(
6251
+ `/v4/domains/${this.domain}/records`
6252
+ );
6253
+ if (result.error || !result.records) {
6254
+ return [];
6255
+ }
6256
+ return result.records.filter((r) => r.type === "CAA").map((r) => {
6257
+ const match = r.value.match(/^(\d+)\s+(\w+)\s+"?([^"]+)"?$/);
6258
+ if (match) {
6259
+ return {
6260
+ flags: Number.parseInt(match[1], 10),
6261
+ tag: match[2],
6262
+ value: match[3]
6263
+ };
6264
+ }
6265
+ return null;
6266
+ }).filter(
6267
+ (r) => r !== null
6268
+ );
6269
+ }
6270
+ /**
6271
+ * Check if Amazon is allowed to issue certificates based on CAA records
6272
+ * Returns true if:
6273
+ * - No CAA records exist (any CA can issue)
6274
+ * - CAA records exist and include amazon.com or amazontrust.com
6275
+ */
6276
+ async isAmazonCAAAllowed() {
6277
+ const caaRecords = await this.getCAARecords();
6278
+ const issueRecords = caaRecords.filter(
6279
+ (r) => r.tag === "issue" || r.tag === "issuewild"
6280
+ );
6281
+ if (issueRecords.length === 0) {
6282
+ return { allowed: true, hasCAA: false, existingCAs: [] };
6283
+ }
6284
+ const existingCAs = issueRecords.map((r) => r.value);
6285
+ const amazonAllowed = existingCAs.some(
6286
+ (ca) => ca.includes("amazon.com") || ca.includes("amazontrust.com")
6287
+ );
6288
+ return { allowed: amazonAllowed, hasCAA: true, existingCAs };
6289
+ }
6290
+ /**
6291
+ * Add a CAA record to allow Amazon to issue certificates
6292
+ */
6293
+ async addAmazonCAARecord() {
6294
+ const body = {
6295
+ name: "@",
6296
+ // Root domain
6297
+ type: "CAA",
6298
+ value: '0 issue "amazon.com"',
6299
+ ttl: 1800
6300
+ };
6301
+ const result = await this.request(
6302
+ `/v2/domains/${this.domain}/records`,
6303
+ "POST",
6304
+ body
6305
+ );
6306
+ return !result.error;
6307
+ }
6125
6308
  async verifyRecords(data) {
6126
6309
  const { domain, dkimTokens, mailFromDomain, region } = data;
6127
6310
  const missing = [];
@@ -6687,6 +6870,76 @@ var init_dns = __esm({
6687
6870
  }
6688
6871
  });
6689
6872
 
6873
+ // src/utils/dns/caa.ts
6874
+ var caa_exports = {};
6875
+ __export(caa_exports, {
6876
+ ensureAmazonCAAAllowed: () => ensureAmazonCAAAllowed
6877
+ });
6878
+ async function ensureAmazonCAAAllowed(credentials, domain) {
6879
+ if (credentials.provider === "manual" || credentials.provider === "route53") {
6880
+ return { success: true, wasAlreadyAllowed: true, recordCreated: false };
6881
+ }
6882
+ try {
6883
+ if (credentials.provider === "vercel") {
6884
+ const { VercelDNSClient: VercelDNSClient2 } = await Promise.resolve().then(() => (init_vercel(), vercel_exports));
6885
+ const client = new VercelDNSClient2(
6886
+ domain,
6887
+ credentials.token,
6888
+ credentials.teamId
6889
+ );
6890
+ const caaStatus = await client.isAmazonCAAAllowed();
6891
+ if (caaStatus.hasCAA && caaStatus.allowed) {
6892
+ return { success: true, wasAlreadyAllowed: true, recordCreated: false };
6893
+ }
6894
+ const added = await client.addAmazonCAARecord();
6895
+ if (added) {
6896
+ return { success: true, wasAlreadyAllowed: false, recordCreated: true };
6897
+ }
6898
+ return {
6899
+ success: false,
6900
+ wasAlreadyAllowed: false,
6901
+ recordCreated: false,
6902
+ error: "Failed to create CAA record in Vercel DNS"
6903
+ };
6904
+ }
6905
+ if (credentials.provider === "cloudflare") {
6906
+ const { CloudflareDNSClient: CloudflareDNSClient2 } = await Promise.resolve().then(() => (init_cloudflare(), cloudflare_exports));
6907
+ const client = new CloudflareDNSClient2(
6908
+ credentials.zoneId,
6909
+ credentials.token
6910
+ );
6911
+ const caaStatus = await client.isAmazonCAAAllowed();
6912
+ if (caaStatus.allowed) {
6913
+ return { success: true, wasAlreadyAllowed: true, recordCreated: false };
6914
+ }
6915
+ const added = await client.addAmazonCAARecord();
6916
+ if (added) {
6917
+ return { success: true, wasAlreadyAllowed: false, recordCreated: true };
6918
+ }
6919
+ return {
6920
+ success: false,
6921
+ wasAlreadyAllowed: false,
6922
+ recordCreated: false,
6923
+ error: "Failed to create CAA record in Cloudflare DNS"
6924
+ };
6925
+ }
6926
+ return { success: true, wasAlreadyAllowed: true, recordCreated: false };
6927
+ } catch (error) {
6928
+ return {
6929
+ success: false,
6930
+ wasAlreadyAllowed: false,
6931
+ recordCreated: false,
6932
+ error: error instanceof Error ? error.message : "Unknown error"
6933
+ };
6934
+ }
6935
+ }
6936
+ var init_caa = __esm({
6937
+ "src/utils/dns/caa.ts"() {
6938
+ "use strict";
6939
+ init_esm_shims();
6940
+ }
6941
+ });
6942
+
6690
6943
  // src/utils/shared/assume-role.ts
6691
6944
  var assume_role_exports = {};
6692
6945
  __export(assume_role_exports, {
@@ -7016,6 +7269,7 @@ import pc49 from "picocolors";
7016
7269
 
7017
7270
  // src/commands/auth/login.ts
7018
7271
  init_esm_shims();
7272
+ init_events();
7019
7273
  init_config();
7020
7274
  import * as clack from "@clack/prompts";
7021
7275
  import { createAuthClient } from "better-auth/client";
@@ -7024,7 +7278,7 @@ import {
7024
7278
  organizationClient
7025
7279
  } from "better-auth/client/plugins";
7026
7280
  import open from "open";
7027
- import pc from "picocolors";
7281
+ import pc2 from "picocolors";
7028
7282
  function createCliAuthClient(baseURL) {
7029
7283
  return createAuthClient({
7030
7284
  baseURL,
@@ -7052,6 +7306,7 @@ async function fetchOrganizations(baseURL, token) {
7052
7306
  }
7053
7307
  }
7054
7308
  async function login(options) {
7309
+ const startTime = Date.now();
7055
7310
  if (options.token) {
7056
7311
  await saveAuthConfig({
7057
7312
  auth: {
@@ -7059,6 +7314,11 @@ async function login(options) {
7059
7314
  tokenType: "api-key"
7060
7315
  }
7061
7316
  });
7317
+ trackCommand("auth:login", {
7318
+ success: true,
7319
+ duration_ms: Date.now() - startTime,
7320
+ method: "api-key"
7321
+ });
7062
7322
  if (options.json) {
7063
7323
  console.log(JSON.stringify({ success: true, tokenType: "api-key" }));
7064
7324
  } else {
@@ -7066,7 +7326,7 @@ async function login(options) {
7066
7326
  }
7067
7327
  return;
7068
7328
  }
7069
- clack.intro(pc.bold("Wraps \u203A Sign In"));
7329
+ clack.intro(pc2.bold("Wraps \u203A Sign In"));
7070
7330
  const baseURL = process.env.WRAPS_API_URL || "https://app.wraps.dev";
7071
7331
  const authClient = createCliAuthClient(baseURL);
7072
7332
  const spinner8 = clack.spinner();
@@ -7074,6 +7334,8 @@ async function login(options) {
7074
7334
  client_id: "wraps-cli"
7075
7335
  });
7076
7336
  if (codeError || !codeData) {
7337
+ trackCommand("auth:login", { success: false, duration_ms: Date.now() - startTime, method: "device" });
7338
+ trackError("DEVICE_AUTH_FAILED", "auth:login", { step: "request_code" });
7077
7339
  clack.log.error("Failed to start device authorization.");
7078
7340
  process.exit(1);
7079
7341
  }
@@ -7085,8 +7347,8 @@ async function login(options) {
7085
7347
  expires_in
7086
7348
  } = codeData;
7087
7349
  const formatted = `${user_code.slice(0, 4)}-${user_code.slice(4)}`;
7088
- clack.log.info(`Your code: ${pc.bold(pc.cyan(formatted))}`);
7089
- clack.log.info(`Visit: ${pc.underline(`${baseURL}/device`)}`);
7350
+ clack.log.info(`Your code: ${pc2.bold(pc2.cyan(formatted))}`);
7351
+ clack.log.info(`Visit: ${pc2.underline(`${baseURL}/device`)}`);
7090
7352
  try {
7091
7353
  await open(`${baseURL}/device?user_code=${user_code}`);
7092
7354
  clack.log.info("Opening browser...");
@@ -7116,9 +7378,14 @@ async function login(options) {
7116
7378
  organizations: organizations.length > 0 ? organizations : void 0
7117
7379
  }
7118
7380
  });
7381
+ trackCommand("auth:login", {
7382
+ success: true,
7383
+ duration_ms: Date.now() - startTime,
7384
+ method: "device"
7385
+ });
7119
7386
  clack.log.success("Signed in successfully.");
7120
7387
  if (organizations.length === 1) {
7121
- clack.log.info(`Organization: ${pc.cyan(organizations[0].name)}`);
7388
+ clack.log.info(`Organization: ${pc2.cyan(organizations[0].name)}`);
7122
7389
  } else if (organizations.length > 1) {
7123
7390
  clack.log.info(`${organizations.length} organizations available`);
7124
7391
  }
@@ -7143,6 +7410,8 @@ async function login(options) {
7143
7410
  continue;
7144
7411
  }
7145
7412
  if (errorCode === "access_denied") {
7413
+ trackCommand("auth:login", { success: false, duration_ms: Date.now() - startTime, method: "device" });
7414
+ trackError("ACCESS_DENIED", "auth:login", { step: "poll_token" });
7146
7415
  spinner8.stop("Denied.");
7147
7416
  clack.log.error("Authorization was denied.");
7148
7417
  process.exit(1);
@@ -7152,6 +7421,8 @@ async function login(options) {
7152
7421
  }
7153
7422
  }
7154
7423
  }
7424
+ trackCommand("auth:login", { success: false, duration_ms: Date.now() - startTime, method: "device" });
7425
+ trackError("DEVICE_CODE_EXPIRED", "auth:login", { step: "poll_token" });
7155
7426
  spinner8.stop("Expired.");
7156
7427
  clack.log.error("Device code expired. Run `wraps auth login` to try again.");
7157
7428
  process.exit(1);
@@ -7159,32 +7430,37 @@ async function login(options) {
7159
7430
 
7160
7431
  // src/commands/auth/logout.ts
7161
7432
  init_esm_shims();
7433
+ init_events();
7162
7434
  init_config();
7163
7435
  import * as clack2 from "@clack/prompts";
7164
- import pc2 from "picocolors";
7436
+ import pc3 from "picocolors";
7165
7437
  async function logout() {
7166
- clack2.intro(pc2.bold("Wraps \u203A Sign Out"));
7438
+ clack2.intro(pc3.bold("Wraps \u203A Sign Out"));
7167
7439
  const config2 = await readAuthConfig();
7168
7440
  if (!config2?.auth?.token) {
7441
+ trackCommand("auth:logout", { success: true, already_logged_out: true });
7169
7442
  clack2.log.info("Not signed in.");
7170
7443
  return;
7171
7444
  }
7172
7445
  await clearAuthConfig();
7446
+ trackCommand("auth:logout", { success: true });
7173
7447
  clack2.log.success("Signed out. Token removed from ~/.wraps/config.json");
7174
7448
  }
7175
7449
 
7176
7450
  // src/commands/auth/status.ts
7177
7451
  init_esm_shims();
7452
+ init_events();
7178
7453
  init_config();
7179
7454
  import * as clack3 from "@clack/prompts";
7180
- import pc3 from "picocolors";
7455
+ import pc4 from "picocolors";
7181
7456
  async function authStatus(options) {
7182
7457
  const config2 = await readAuthConfig();
7183
7458
  if (!config2?.auth?.token) {
7459
+ trackCommand("auth:status", { success: true, authenticated: false });
7184
7460
  if (options.json) {
7185
7461
  console.log(JSON.stringify({ authenticated: false }));
7186
7462
  } else {
7187
- clack3.intro(pc3.bold("Wraps \u203A Auth Status"));
7463
+ clack3.intro(pc4.bold("Wraps \u203A Auth Status"));
7188
7464
  clack3.log.info("Not signed in. Run `wraps auth login` to authenticate.");
7189
7465
  }
7190
7466
  return;
@@ -7201,16 +7477,18 @@ async function authStatus(options) {
7201
7477
  })
7202
7478
  );
7203
7479
  } else {
7204
- clack3.intro(pc3.bold("Wraps \u203A Auth Status"));
7480
+ clack3.intro(pc4.bold("Wraps \u203A Auth Status"));
7205
7481
  clack3.log.info(`Token: ${masked} (${tokenType})`);
7206
7482
  if (expiresAt) {
7207
7483
  clack3.log.info(`Expires: ${new Date(expiresAt).toLocaleDateString()}`);
7208
7484
  }
7209
7485
  }
7486
+ trackCommand("auth:status", { success: true, authenticated: true });
7210
7487
  }
7211
7488
 
7212
7489
  // src/commands/aws/doctor.ts
7213
7490
  init_esm_shims();
7491
+ init_events();
7214
7492
  init_aws();
7215
7493
  init_aws_detection();
7216
7494
  import * as clack5 from "@clack/prompts";
@@ -7501,6 +7779,7 @@ function generateSuggestions(results, state) {
7501
7779
  return suggestions;
7502
7780
  }
7503
7781
  async function doctor() {
7782
+ const startTime = Date.now();
7504
7783
  clack5.intro(pc6.bold("AWS Setup Diagnostics"));
7505
7784
  const spinner8 = clack5.spinner();
7506
7785
  spinner8.start("Running diagnostics...");
@@ -7528,6 +7807,13 @@ async function doctor() {
7528
7807
  console.log(` ${pc6.dim("-")} ${suggestion}`);
7529
7808
  }
7530
7809
  }
7810
+ trackCommand("aws:doctor", {
7811
+ success: true,
7812
+ duration_ms: Date.now() - startTime,
7813
+ pass_count: passCount,
7814
+ fail_count: failCount,
7815
+ warn_count: warnCount
7816
+ });
7531
7817
  console.log();
7532
7818
  clack5.outro(
7533
7819
  failCount > 0 ? pc6.dim("Run `wraps aws setup` to fix issues") : pc6.dim("Ready to deploy: wraps email init")
@@ -7536,6 +7822,7 @@ async function doctor() {
7536
7822
 
7537
7823
  // src/commands/aws/setup.ts
7538
7824
  init_esm_shims();
7825
+ init_events();
7539
7826
  init_aws_detection();
7540
7827
  init_prompts();
7541
7828
  import * as clack7 from "@clack/prompts";
@@ -8087,6 +8374,7 @@ function showNextSteps(_state) {
8087
8374
  console.log();
8088
8375
  }
8089
8376
  async function setup(_options = {}) {
8377
+ const startTime = Date.now();
8090
8378
  clack7.intro(pc8.bold("AWS Setup Wizard"));
8091
8379
  const spinner8 = clack7.spinner();
8092
8380
  spinner8.start("Checking your AWS setup...");
@@ -8106,6 +8394,10 @@ async function setup(_options = {}) {
8106
8394
  await runCredentialSetup();
8107
8395
  }
8108
8396
  }
8397
+ trackCommand("aws:setup", {
8398
+ success: true,
8399
+ duration_ms: Date.now() - startTime
8400
+ });
8109
8401
  clack7.outro(pc8.dim("Run `wraps aws doctor` to verify your setup"));
8110
8402
  }
8111
8403
 
@@ -10753,10 +11045,10 @@ async function cdnSync(options) {
10753
11045
  });
10754
11046
  const stackOutputs = await checkStack.outputs();
10755
11047
  if (stackOutputs.acmCertificateArn?.value) {
10756
- const { ACMClient: ACMClient2, DescribeCertificateCommand: DescribeCertificateCommand2 } = await import("@aws-sdk/client-acm");
10757
- const acmClient = new ACMClient2({ region: "us-east-1" });
11048
+ const { ACMClient: ACMClient3, DescribeCertificateCommand: DescribeCertificateCommand3 } = await import("@aws-sdk/client-acm");
11049
+ const acmClient = new ACMClient3({ region: "us-east-1" });
10758
11050
  const certResponse = await acmClient.send(
10759
- new DescribeCertificateCommand2({
11051
+ new DescribeCertificateCommand3({
10760
11052
  CertificateArn: stackOutputs.acmCertificateArn.value
10761
11053
  })
10762
11054
  );
@@ -10941,12 +11233,12 @@ Current configuration:
10941
11233
  process.exit(0);
10942
11234
  }
10943
11235
  progress.start("Checking certificate validation status");
10944
- const { ACMClient: ACMClient2, DescribeCertificateCommand: DescribeCertificateCommand2 } = await import("@aws-sdk/client-acm");
10945
- const acmClient = new ACMClient2({ region: "us-east-1" });
11236
+ const { ACMClient: ACMClient3, DescribeCertificateCommand: DescribeCertificateCommand3 } = await import("@aws-sdk/client-acm");
11237
+ const acmClient = new ACMClient3({ region: "us-east-1" });
10946
11238
  let certStatus;
10947
11239
  try {
10948
11240
  const certResponse = await acmClient.send(
10949
- new DescribeCertificateCommand2({
11241
+ new DescribeCertificateCommand3({
10950
11242
  CertificateArn: stackOutputs.acmCertificateArn.value
10951
11243
  })
10952
11244
  );
@@ -11142,10 +11434,10 @@ async function checkDNSRecord(hostname, expectedValue) {
11142
11434
  }
11143
11435
  async function checkCertificateStatus(certificateArn) {
11144
11436
  try {
11145
- const { ACMClient: ACMClient2, DescribeCertificateCommand: DescribeCertificateCommand2 } = await import("@aws-sdk/client-acm");
11146
- const acm3 = new ACMClient2({ region: "us-east-1" });
11437
+ const { ACMClient: ACMClient3, DescribeCertificateCommand: DescribeCertificateCommand3 } = await import("@aws-sdk/client-acm");
11438
+ const acm3 = new ACMClient3({ region: "us-east-1" });
11147
11439
  const result = await acm3.send(
11148
- new DescribeCertificateCommand2({ CertificateArn: certificateArn })
11440
+ new DescribeCertificateCommand3({ CertificateArn: certificateArn })
11149
11441
  );
11150
11442
  const cert = result.Certificate;
11151
11443
  const validationStatus = cert?.DomainValidationOptions?.[0]?.ValidationStatus;
@@ -15296,6 +15588,7 @@ async function deployEmailStack(config2) {
15296
15588
  });
15297
15589
  let cloudFrontResources;
15298
15590
  let acmResources;
15591
+ let skipCloudFront = false;
15299
15592
  if (emailConfig.tracking?.enabled && emailConfig.tracking.customRedirectDomain && emailConfig.tracking.httpsEnabled) {
15300
15593
  const { findHostedZone: findHostedZone2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
15301
15594
  const hostedZone = await findHostedZone2(
@@ -15307,16 +15600,27 @@ async function deployEmailStack(config2) {
15307
15600
  domain: emailConfig.tracking.customRedirectDomain,
15308
15601
  hostedZoneId: hostedZone?.id
15309
15602
  });
15310
- const { createCloudFrontTracking: createCloudFrontTracking2 } = await Promise.resolve().then(() => (init_cloudfront(), cloudfront_exports));
15311
- const certificateArn = acmResources.certificateValidation ? acmResources.certificateValidation.certificateArn : acmResources.certificate.arn;
15312
- cloudFrontResources = await createCloudFrontTracking2({
15313
- customTrackingDomain: emailConfig.tracking.customRedirectDomain,
15314
- region: config2.region,
15315
- certificateArn,
15316
- hostedZoneId: hostedZone?.id,
15317
- // Pass hosted zone ID for automatic DNS record creation
15318
- wafEnabled: emailConfig.tracking.wafEnabled
15319
- });
15603
+ if (!hostedZone) {
15604
+ const { checkCertificateValidation: checkCertificateValidation2 } = await Promise.resolve().then(() => (init_acm(), acm_exports));
15605
+ const isValidated = await checkCertificateValidation2(
15606
+ emailConfig.tracking.customRedirectDomain
15607
+ );
15608
+ if (!isValidated) {
15609
+ skipCloudFront = true;
15610
+ }
15611
+ }
15612
+ if (!skipCloudFront) {
15613
+ const { createCloudFrontTracking: createCloudFrontTracking2 } = await Promise.resolve().then(() => (init_cloudfront(), cloudfront_exports));
15614
+ const certificateArn = acmResources.certificateValidation ? acmResources.certificateValidation.certificateArn : acmResources.certificate.arn;
15615
+ cloudFrontResources = await createCloudFrontTracking2({
15616
+ customTrackingDomain: emailConfig.tracking.customRedirectDomain,
15617
+ region: config2.region,
15618
+ certificateArn,
15619
+ hostedZoneId: hostedZone?.id,
15620
+ // Pass hosted zone ID for automatic DNS record creation
15621
+ wafEnabled: emailConfig.tracking.wafEnabled
15622
+ });
15623
+ }
15320
15624
  }
15321
15625
  let sesResources;
15322
15626
  if (emailConfig.tracking?.enabled || emailConfig.eventTracking?.enabled) {
@@ -15329,11 +15633,19 @@ async function deployEmailStack(config2) {
15329
15633
  if (!mailFromDomain && emailConfig.mailFromSubdomain && emailConfig.domain) {
15330
15634
  mailFromDomain = `${emailConfig.mailFromSubdomain}.${emailConfig.domain}`;
15331
15635
  }
15636
+ const effectiveTrackingConfig = skipCloudFront && emailConfig.tracking ? {
15637
+ enabled: emailConfig.tracking.enabled,
15638
+ opens: emailConfig.tracking.opens,
15639
+ clicks: emailConfig.tracking.clicks,
15640
+ customRedirectDomain: emailConfig.tracking.customRedirectDomain,
15641
+ httpsEnabled: false
15642
+ // Use OPTIONAL until CloudFront is ready
15643
+ } : emailConfig.tracking;
15332
15644
  sesResources = await createSESResources({
15333
15645
  domain: emailConfig.domain,
15334
15646
  mailFromDomain,
15335
15647
  region: config2.region,
15336
- trackingConfig: emailConfig.tracking,
15648
+ trackingConfig: effectiveTrackingConfig,
15337
15649
  eventTypes: emailConfig.eventTracking?.events,
15338
15650
  eventTrackingEnabled: emailConfig.eventTracking?.enabled,
15339
15651
  // Pass flag to create EventBridge destination
@@ -15461,6 +15773,8 @@ async function deployEmailStack(config2) {
15461
15773
  dlqUrl: sqsResources?.dlq.url,
15462
15774
  customTrackingDomain: sesResources?.customTrackingDomain,
15463
15775
  httpsTrackingEnabled: emailConfig.tracking?.httpsEnabled,
15776
+ httpsTrackingPending: skipCloudFront,
15777
+ // True if HTTPS requested but cert not validated yet
15464
15778
  cloudFrontDomain: cloudFrontResources?.domainName,
15465
15779
  acmCertificateValidationRecords: acmResources?.validationRecords,
15466
15780
  mailFromDomain: sesResources?.mailFromDomain,
@@ -18987,6 +19301,7 @@ Run ${pc24.cyan("wraps email init")} to deploy email infrastructure.
18987
19301
 
18988
19302
  // src/commands/email/templates/init.ts
18989
19303
  init_esm_shims();
19304
+ init_events();
18990
19305
  init_config();
18991
19306
  init_errors();
18992
19307
  import { existsSync as existsSync7 } from "fs";
@@ -18995,6 +19310,7 @@ import { join as join8 } from "path";
18995
19310
  import * as clack24 from "@clack/prompts";
18996
19311
  import pc25 from "picocolors";
18997
19312
  async function templatesInit(options) {
19313
+ const startTime = Date.now();
18998
19314
  const cwd = process.cwd();
18999
19315
  const wrapsDir = join8(cwd, "wraps");
19000
19316
  if (!options.json) {
@@ -19130,6 +19446,10 @@ wraps/.wraps/
19130
19446
  );
19131
19447
  return;
19132
19448
  }
19449
+ trackCommand("email:templates:init", {
19450
+ success: true,
19451
+ duration_ms: Date.now() - startTime
19452
+ });
19133
19453
  console.log();
19134
19454
  clack24.log.success(pc25.green("Templates as Code initialized!"));
19135
19455
  console.log();
@@ -19336,6 +19656,7 @@ const unsubscribeLink = {
19336
19656
 
19337
19657
  // src/commands/email/templates/preview.ts
19338
19658
  init_esm_shims();
19659
+ init_events();
19339
19660
  import { existsSync as existsSync9, watch } from "fs";
19340
19661
  import { join as join10 } from "path";
19341
19662
  import * as clack25 from "@clack/prompts";
@@ -19582,6 +19903,10 @@ async function templatesPreview(options) {
19582
19903
  const port = options.port || await getPort2({ port: [3333, 3334, 3335, 3336, 3337] });
19583
19904
  const server = app.listen(port, () => {
19584
19905
  const url = `http://localhost:${port}`;
19906
+ trackCommand("email:templates:preview", {
19907
+ success: true,
19908
+ template_count: templateFiles.length
19909
+ });
19585
19910
  clack25.log.success(`Preview server running at ${pc26.cyan(url)}`);
19586
19911
  if (options.template) {
19587
19912
  clack25.log.info(`Previewing: ${pc26.cyan(options.template)}`);
@@ -19781,6 +20106,7 @@ function renderErrorPage(err) {
19781
20106
 
19782
20107
  // src/commands/email/templates/push.ts
19783
20108
  init_esm_shims();
20109
+ init_events();
19784
20110
  import { createHash } from "crypto";
19785
20111
  import { existsSync as existsSync10 } from "fs";
19786
20112
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile6 } from "fs/promises";
@@ -19790,6 +20116,7 @@ import pc27 from "picocolors";
19790
20116
  init_config();
19791
20117
  init_errors();
19792
20118
  async function templatesPush(options) {
20119
+ const startTime = Date.now();
19793
20120
  const cwd = process.cwd();
19794
20121
  const wrapsDir = join11(cwd, "wraps");
19795
20122
  const configPath = join11(wrapsDir, "wraps.config.ts");
@@ -19955,6 +20282,13 @@ async function templatesPush(options) {
19955
20282
  }
19956
20283
  console.log();
19957
20284
  }
20285
+ trackCommand("email:templates:push", {
20286
+ success: compileErrors.length === 0,
20287
+ duration_ms: Date.now() - startTime,
20288
+ pushed_count: compiled.length,
20289
+ unchanged_count: unchanged.length,
20290
+ error_count: compileErrors.length
20291
+ });
19958
20292
  }
19959
20293
  async function compileTemplate(filePath, slug, source, sourceHash, wrapsDir) {
19960
20294
  const { build: build2 } = await import("esbuild");
@@ -21408,6 +21742,56 @@ ${pc28.bold("Cost Impact:")}`);
21408
21742
  const stackConfig = buildEmailStackConfig(metadata, region, {
21409
21743
  emailConfig: updatedConfig
21410
21744
  });
21745
+ if (updatedConfig.tracking?.httpsEnabled && updatedConfig.tracking.customRedirectDomain) {
21746
+ const trackingDomainParts = updatedConfig.tracking.customRedirectDomain.split(".");
21747
+ const parentDomain = trackingDomainParts.length > 2 ? trackingDomainParts.slice(-2).join(".") : updatedConfig.tracking.customRedirectDomain;
21748
+ let dnsProvider = metadata.services.email?.dnsProvider;
21749
+ if (!dnsProvider) {
21750
+ const availableProviders = await progress.execute(
21751
+ "Detecting DNS provider for CAA check",
21752
+ async () => await detectAvailableDNSProviders(parentDomain, region)
21753
+ );
21754
+ const detectedProvider = availableProviders.find(
21755
+ (p) => p.detected && p.provider !== "manual"
21756
+ );
21757
+ if (detectedProvider) {
21758
+ dnsProvider = detectedProvider.provider;
21759
+ if (metadata.services.email) {
21760
+ metadata.services.email.dnsProvider = dnsProvider;
21761
+ }
21762
+ }
21763
+ }
21764
+ if (dnsProvider && dnsProvider !== "manual" && dnsProvider !== "route53") {
21765
+ const credResult = await getDNSCredentials(
21766
+ dnsProvider,
21767
+ parentDomain,
21768
+ region
21769
+ );
21770
+ if (credResult.valid && credResult.credentials) {
21771
+ const { ensureAmazonCAAAllowed: ensureAmazonCAAAllowed2 } = await Promise.resolve().then(() => (init_caa(), caa_exports));
21772
+ const caaResult = await progress.execute(
21773
+ "Checking CAA records for certificate issuance",
21774
+ async () => await ensureAmazonCAAAllowed2(credResult.credentials, parentDomain)
21775
+ );
21776
+ if (caaResult.recordCreated) {
21777
+ progress.info(
21778
+ `Added CAA record to allow Amazon certificate issuance for ${pc28.cyan(parentDomain)}`
21779
+ );
21780
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
21781
+ } else if (!caaResult.success) {
21782
+ clack27.log.warn(
21783
+ `Could not verify CAA records: ${caaResult.error || "Unknown error"}`
21784
+ );
21785
+ clack27.log.info(
21786
+ pc28.dim(
21787
+ "If certificate issuance fails, you may need to add a CAA record manually:"
21788
+ )
21789
+ );
21790
+ clack27.log.info(pc28.dim(` ${parentDomain} CAA 0 issue "amazon.com"`));
21791
+ }
21792
+ }
21793
+ }
21794
+ }
21411
21795
  if (options.preview) {
21412
21796
  try {
21413
21797
  const previewResult = await progress.execute(
@@ -21508,6 +21892,7 @@ ${pc28.bold("Cost Impact:")}`);
21508
21892
  dkimTokens: result.dkimTokens,
21509
21893
  customTrackingDomain: result.customTrackingDomain,
21510
21894
  httpsTrackingEnabled: result.httpsTrackingEnabled,
21895
+ httpsTrackingPending: result.httpsTrackingPending,
21511
21896
  cloudFrontDomain: result.cloudFrontDomain,
21512
21897
  acmCertificateValidationRecords: result.acmCertificateValidationRecords,
21513
21898
  archiveArn: result.archiveArn,
@@ -21549,6 +21934,7 @@ ${pc28.bold("Cost Impact:")}`);
21549
21934
  dkimTokens: pulumiOutputs.dkimTokens?.value,
21550
21935
  customTrackingDomain: pulumiOutputs.customTrackingDomain?.value,
21551
21936
  httpsTrackingEnabled: pulumiOutputs.httpsTrackingEnabled?.value,
21937
+ httpsTrackingPending: pulumiOutputs.httpsTrackingPending?.value,
21552
21938
  cloudFrontDomain: pulumiOutputs.cloudFrontDomain?.value,
21553
21939
  acmCertificateValidationRecords: pulumiOutputs.acmCertificateValidationRecords?.value,
21554
21940
  archiveArn: pulumiOutputs.archiveArn?.value,
@@ -21703,14 +22089,92 @@ ${pc28.bold("Add these DNS records to your DNS provider:")}
21703
22089
  if (outputs.httpsTrackingEnabled && outputs.acmCertificateValidationRecords) {
21704
22090
  acmValidationRecords.push(...outputs.acmCertificateValidationRecords);
21705
22091
  }
21706
- const needsCertificateValidation = outputs.httpsTrackingEnabled && acmValidationRecords.length > 0 && !outputs.cloudFrontDomain;
22092
+ let acmDnsAutoCreated = false;
22093
+ if (outputs.httpsTrackingPending && acmValidationRecords.length > 0 && outputs.customTrackingDomain) {
22094
+ const trackingDnsProvider = metadata.services.email?.dnsProvider;
22095
+ if (trackingDnsProvider && trackingDnsProvider !== "manual") {
22096
+ const trackingDomainParts = outputs.customTrackingDomain.split(".");
22097
+ const parentDomain = trackingDomainParts.length > 2 ? trackingDomainParts.slice(-2).join(".") : outputs.customTrackingDomain;
22098
+ const credResult = await progress.execute(
22099
+ `Validating ${getDNSProviderDisplayName(trackingDnsProvider)} credentials for ACM validation`,
22100
+ async () => await getDNSCredentials(trackingDnsProvider, parentDomain, region)
22101
+ );
22102
+ if (credResult.valid && credResult.credentials) {
22103
+ try {
22104
+ progress.start(
22105
+ `Creating ACM validation DNS record in ${getDNSProviderDisplayName(trackingDnsProvider)}`
22106
+ );
22107
+ if (credResult.credentials.provider === "vercel") {
22108
+ const { VercelDNSClient: VercelDNSClient2 } = await Promise.resolve().then(() => (init_vercel(), vercel_exports));
22109
+ const client = new VercelDNSClient2(
22110
+ parentDomain,
22111
+ credResult.credentials.token,
22112
+ credResult.credentials.teamId
22113
+ );
22114
+ const result = await client.createRecords(
22115
+ acmValidationRecords.map((r) => ({
22116
+ name: r.name,
22117
+ type: r.type,
22118
+ value: r.value
22119
+ }))
22120
+ );
22121
+ if (result.success) {
22122
+ progress.succeed(
22123
+ `Created ACM validation DNS record in ${getDNSProviderDisplayName(trackingDnsProvider)}`
22124
+ );
22125
+ acmDnsAutoCreated = true;
22126
+ progress.info(
22127
+ "Certificate validation usually takes 5-30 minutes. Run this command again after validation completes."
22128
+ );
22129
+ } else {
22130
+ progress.fail(
22131
+ `Failed to create ACM validation record: ${result.errors?.join(", ")}`
22132
+ );
22133
+ }
22134
+ } else if (credResult.credentials.provider === "cloudflare") {
22135
+ const { CloudflareDNSClient: CloudflareDNSClient2 } = await Promise.resolve().then(() => (init_cloudflare(), cloudflare_exports));
22136
+ const client = new CloudflareDNSClient2(
22137
+ credResult.credentials.zoneId,
22138
+ credResult.credentials.token
22139
+ );
22140
+ const result = await client.createRecords(
22141
+ acmValidationRecords.map((r) => ({
22142
+ name: r.name,
22143
+ type: r.type,
22144
+ value: r.value
22145
+ }))
22146
+ );
22147
+ if (result.success) {
22148
+ progress.succeed(
22149
+ `Created ACM validation DNS record in ${getDNSProviderDisplayName(trackingDnsProvider)}`
22150
+ );
22151
+ acmDnsAutoCreated = true;
22152
+ progress.info(
22153
+ "Certificate validation usually takes 5-30 minutes. Run this command again after validation completes."
22154
+ );
22155
+ } else {
22156
+ progress.fail(
22157
+ `Failed to create ACM validation record: ${result.errors?.join(", ")}`
22158
+ );
22159
+ }
22160
+ }
22161
+ } catch (error) {
22162
+ progress.fail(
22163
+ `Failed to create ACM validation record: ${error.message}`
22164
+ );
22165
+ }
22166
+ }
22167
+ }
22168
+ }
22169
+ const needsCertificateValidation = outputs.httpsTrackingPending || outputs.httpsTrackingEnabled && acmValidationRecords.length > 0 && !outputs.cloudFrontDomain;
21707
22170
  displaySuccess({
21708
22171
  roleArn: outputs.roleArn,
21709
22172
  configSetName: outputs.configSetName,
21710
22173
  region: outputs.region,
21711
22174
  tableName: outputs.tableName,
21712
22175
  trackingDomainDnsRecords: trackingDomainDnsRecords.length > 0 ? trackingDomainDnsRecords : void 0,
21713
- acmValidationRecords: acmValidationRecords.length > 0 ? acmValidationRecords : void 0,
22176
+ // Only show ACM validation records if they weren't auto-created
22177
+ acmValidationRecords: acmValidationRecords.length > 0 && !acmDnsAutoCreated ? acmValidationRecords : void 0,
21714
22178
  customTrackingDomain: outputs.customTrackingDomain,
21715
22179
  httpsTrackingEnabled: outputs.httpsTrackingEnabled
21716
22180
  });
@@ -21730,16 +22194,27 @@ ${pc28.green("\u2713")} ${pc28.bold("Upgrade complete!")}
21730
22194
  }
21731
22195
  if (needsCertificateValidation) {
21732
22196
  console.log(pc28.bold("\u26A0\uFE0F HTTPS Tracking - Next Steps:\n"));
21733
- console.log(
21734
- " 1. Add the SSL certificate validation DNS record shown above to your DNS provider"
21735
- );
21736
- console.log(
21737
- " 2. Wait for DNS propagation and certificate validation (5-30 minutes)"
21738
- );
21739
- console.log(
21740
- ` 3. Run ${pc28.cyan("wraps email upgrade")} again to complete CloudFront setup
22197
+ if (acmDnsAutoCreated) {
22198
+ console.log(
22199
+ ` 1. ${pc28.green("\u2713")} ACM validation DNS record created automatically`
22200
+ );
22201
+ console.log(" 2. Wait for certificate validation (5-30 minutes)");
22202
+ console.log(
22203
+ ` 3. Run ${pc28.cyan("wraps email upgrade")} again to complete CloudFront setup
21741
22204
  `
21742
- );
22205
+ );
22206
+ } else {
22207
+ console.log(
22208
+ " 1. Add the SSL certificate validation DNS record shown above to your DNS provider"
22209
+ );
22210
+ console.log(
22211
+ " 2. Wait for DNS propagation and certificate validation (5-30 minutes)"
22212
+ );
22213
+ console.log(
22214
+ ` 3. Run ${pc28.cyan("wraps email upgrade")} again to complete CloudFront setup
22215
+ `
22216
+ );
22217
+ }
21743
22218
  console.log(
21744
22219
  pc28.dim(
21745
22220
  " Note: CloudFront distribution will be created once the certificate is validated.\n"
@@ -21835,6 +22310,7 @@ ${pc28.green("\u2713")} ${pc28.bold("Upgrade complete!")}
21835
22310
 
21836
22311
  // src/commands/email/workflows/push.ts
21837
22312
  init_esm_shims();
22313
+ init_events();
21838
22314
  import { existsSync as existsSync12 } from "fs";
21839
22315
  import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile8 } from "fs/promises";
21840
22316
  import { join as join13 } from "path";
@@ -22564,6 +23040,7 @@ function validateTemplateReferences(steps, localTemplateSlugs) {
22564
23040
  init_config();
22565
23041
  init_errors();
22566
23042
  async function workflowsPush(options) {
23043
+ const startTime = Date.now();
22567
23044
  const cwd = process.cwd();
22568
23045
  const wrapsDir = join13(cwd, "wraps");
22569
23046
  const configPath = join13(wrapsDir, "wraps.config.ts");
@@ -22808,6 +23285,13 @@ async function workflowsPush(options) {
22808
23285
  }
22809
23286
  console.log();
22810
23287
  }
23288
+ trackCommand("email:workflows:push", {
23289
+ success: conflicts.length === 0 && pushed.length > 0,
23290
+ duration_ms: Date.now() - startTime,
23291
+ pushed_count: pushed.length,
23292
+ unchanged_count: unchanged.length,
23293
+ conflict_count: conflicts.length
23294
+ });
22811
23295
  }
22812
23296
  async function pushToAPI2(workflows, token, _org, progress, force) {
22813
23297
  if (!token) {
@@ -22956,12 +23440,14 @@ async function saveLockfile2(path3, lockfile) {
22956
23440
 
22957
23441
  // src/commands/email/workflows/validate.ts
22958
23442
  init_esm_shims();
23443
+ init_events();
22959
23444
  import { existsSync as existsSync13 } from "fs";
22960
23445
  import { join as join14 } from "path";
22961
23446
  import * as clack29 from "@clack/prompts";
22962
23447
  import pc30 from "picocolors";
22963
23448
  init_errors();
22964
23449
  async function workflowsValidate(options) {
23450
+ const startTime = Date.now();
22965
23451
  const cwd = process.cwd();
22966
23452
  const wrapsDir = join14(cwd, "wraps");
22967
23453
  const configPath = join14(wrapsDir, "wraps.config.ts");
@@ -23110,13 +23596,22 @@ async function workflowsValidate(options) {
23110
23596
  }
23111
23597
  console.log();
23112
23598
  }
23599
+ trackCommand("email:workflows:validate", {
23600
+ success: parseErrors.length === 0 && validationResults.every((r) => r.valid),
23601
+ duration_ms: Date.now() - startTime,
23602
+ valid_count: validationResults.filter((r) => r.valid).length,
23603
+ invalid_count: validationResults.filter((r) => !r.valid).length,
23604
+ parse_error_count: parseErrors.length
23605
+ });
23113
23606
  }
23114
23607
 
23115
23608
  // src/commands/news.ts
23116
23609
  init_esm_shims();
23610
+ init_events();
23117
23611
  import * as clack30 from "@clack/prompts";
23118
23612
  import pc31 from "picocolors";
23119
23613
  async function news() {
23614
+ trackCommand("news", { success: true });
23120
23615
  clack30.intro(pc31.bold("What's New in Wraps"));
23121
23616
  console.log();
23122
23617
  console.log(" See the latest updates, features, and improvements:");
@@ -24496,6 +24991,7 @@ async function platform() {
24496
24991
 
24497
24992
  // src/commands/platform/update-role.ts
24498
24993
  init_esm_shims();
24994
+ init_events();
24499
24995
  init_aws();
24500
24996
  init_metadata();
24501
24997
  import {
@@ -24506,6 +25002,7 @@ import {
24506
25002
  import { confirm as confirm13, intro as intro32, isCancel as isCancel19, log as log31, outro as outro19 } from "@clack/prompts";
24507
25003
  import pc35 from "picocolors";
24508
25004
  async function updateRole(options) {
25005
+ const startTime = Date.now();
24509
25006
  intro32(pc35.bold("Update Platform Access Role"));
24510
25007
  const progress = new DeploymentProgress();
24511
25008
  const identity = await progress.execute(
@@ -24633,6 +25130,11 @@ Run ${pc35.cyan("wraps email init")} to deploy infrastructure first.
24633
25130
  }
24634
25131
  progress.stop();
24635
25132
  const actionVerb = roleExists4 ? "updated" : "created";
25133
+ trackCommand("platform:update-role", {
25134
+ success: true,
25135
+ duration_ms: Date.now() - startTime,
25136
+ action: actionVerb
25137
+ });
24636
25138
  outro19(pc35.green(`\u2713 Platform access role ${actionVerb} successfully`));
24637
25139
  console.log(`
24638
25140
  ${pc35.bold("Permissions:")}`);
@@ -27637,11 +28139,13 @@ async function dashboard(options) {
27637
28139
 
27638
28140
  // src/commands/shared/destroy.ts
27639
28141
  init_esm_shims();
28142
+ init_events();
27640
28143
  init_aws();
27641
28144
  init_metadata();
27642
28145
  import * as clack34 from "@clack/prompts";
27643
28146
  import pc37 from "picocolors";
27644
28147
  async function destroy(options) {
28148
+ trackCommand("destroy", { success: true });
27645
28149
  clack34.intro(pc37.bold("Wraps Infrastructure Teardown"));
27646
28150
  const spinner8 = clack34.spinner();
27647
28151
  spinner8.start("Validating AWS credentials");
@@ -29712,6 +30216,7 @@ ${pc40.yellow(pc40.bold("Important Notes:"))}`);
29712
30216
 
29713
30217
  // src/commands/sms/register.ts
29714
30218
  init_esm_shims();
30219
+ init_events();
29715
30220
  init_aws();
29716
30221
  init_metadata();
29717
30222
  import * as clack38 from "@clack/prompts";
@@ -29753,6 +30258,7 @@ async function getRegistrationStatus(region, registrationId) {
29753
30258
  }
29754
30259
  }
29755
30260
  async function smsRegister(options) {
30261
+ const startTime = Date.now();
29756
30262
  clack38.intro(pc41.bold("Wraps SMS - Toll-Free Registration"));
29757
30263
  const progress = new DeploymentProgress();
29758
30264
  const identity = await progress.execute(
@@ -29871,6 +30377,10 @@ async function smsRegister(options) {
29871
30377
  console.log("When you're ready, go to:");
29872
30378
  console.log(` ${pc41.cyan(consoleUrl)}`);
29873
30379
  }
30380
+ trackCommand("sms:register", {
30381
+ success: true,
30382
+ duration_ms: Date.now() - startTime
30383
+ });
29874
30384
  clack38.outro(pc41.dim("Good luck with your registration!"));
29875
30385
  }
29876
30386
 
@@ -31646,9 +32156,11 @@ Run ${pc46.cyan(`wraps sms verify-number --phone-number ${phoneNumber} --resend`
31646
32156
 
31647
32157
  // src/commands/support.ts
31648
32158
  init_esm_shims();
32159
+ init_events();
31649
32160
  import * as clack44 from "@clack/prompts";
31650
32161
  import pc47 from "picocolors";
31651
32162
  async function support() {
32163
+ trackCommand("support", { success: true });
31652
32164
  clack44.intro(pc47.bold("Get Help with Wraps"));
31653
32165
  console.log();
31654
32166
  console.log(` ${pc47.bold("Email:")} ${pc47.cyan("hey@wraps.sh")}`);