@jaypie/constructs 1.2.9 → 1.2.10

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/esm/index.js CHANGED
@@ -3,7 +3,6 @@ import { Tags, Stack, Fn, CfnOutput, SecretValue, Duration, RemovalPolicy, CfnSt
3
3
  import * as s3 from 'aws-cdk-lib/aws-s3';
4
4
  import { Bucket, StorageClass, BucketAccessControl, EventType } from 'aws-cdk-lib/aws-s3';
5
5
  import { Construct } from 'constructs';
6
- import * as acm from 'aws-cdk-lib/aws-certificatemanager';
7
6
  import * as apiGateway from 'aws-cdk-lib/aws-apigateway';
8
7
  import * as route53 from 'aws-cdk-lib/aws-route53';
9
8
  import { TxtRecord, NsRecord, MxRecord, CnameRecord, ARecord, RecordTarget, HostedZone } from 'aws-cdk-lib/aws-route53';
@@ -12,6 +11,7 @@ import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
12
11
  import { DatadogLambda } from 'datadog-cdk-constructs-v2';
13
12
  import { ConfigurationError } from '@jaypie/errors';
14
13
  import { Role, PolicyStatement, Policy, FederatedPrincipal, Effect, ServicePrincipal, ManagedPolicy } from 'aws-cdk-lib/aws-iam';
14
+ import * as acm from 'aws-cdk-lib/aws-certificatemanager';
15
15
  import * as lambda from 'aws-cdk-lib/aws-lambda';
16
16
  import * as logDestinations from 'aws-cdk-lib/aws-logs-destinations';
17
17
  import * as s3n from 'aws-cdk-lib/aws-s3-notifications';
@@ -444,6 +444,111 @@ function extendDatadogRole(scope, options) {
444
444
  return datadogCustomPolicy;
445
445
  }
446
446
 
447
+ // Cache: Stack -> (domain -> certificate)
448
+ // Using WeakMap for automatic garbage collection when stacks are destroyed
449
+ const certificateCache = new WeakMap();
450
+ /**
451
+ * Resolves a certificate based on input type.
452
+ *
453
+ * Key behavior: When certificate is `true`, the certificate is created at the
454
+ * STACK level (not construct level) and cached by domain name. This allows
455
+ * swapping between constructs (e.g., JaypieDistribution to JaypieApiGateway)
456
+ * without recreating the certificate.
457
+ *
458
+ * @param scope - The construct scope (used to find the stack)
459
+ * @param options - Certificate resolution options
460
+ * @returns The resolved certificate, or undefined if certificate is false
461
+ *
462
+ * @example
463
+ * // Create or get cached certificate at stack level
464
+ * const cert = resolveCertificate(this, {
465
+ * certificate: true,
466
+ * domainName: "api.example.com",
467
+ * zone: hostedZone,
468
+ * });
469
+ *
470
+ * @example
471
+ * // Use existing certificate
472
+ * const cert = resolveCertificate(this, {
473
+ * certificate: existingCert,
474
+ * domainName: "api.example.com",
475
+ * zone: hostedZone,
476
+ * });
477
+ *
478
+ * @example
479
+ * // Import certificate from ARN
480
+ * const cert = resolveCertificate(this, {
481
+ * certificate: "arn:aws:acm:us-east-1:123456789:certificate/abc-123",
482
+ * domainName: "api.example.com",
483
+ * zone: hostedZone,
484
+ * });
485
+ */
486
+ function resolveCertificate(scope, options) {
487
+ const { certificate, domainName, name = "Certificate", roleTag = CDK$2.ROLE.API, zone, } = options;
488
+ // false = no certificate
489
+ if (certificate === false) {
490
+ return undefined;
491
+ }
492
+ // ICertificate passed directly - use as-is
493
+ if (typeof certificate === "object" && certificate !== null) {
494
+ return certificate;
495
+ }
496
+ // ARN string - import from ARN
497
+ if (typeof certificate === "string") {
498
+ // Sanitize domain for construct ID
499
+ const sanitizedDomain = sanitizeDomainForId(domainName);
500
+ return acm.Certificate.fromCertificateArn(scope, `${name}-${sanitizedDomain}`, certificate);
501
+ }
502
+ // true (default) = create at STACK level with caching
503
+ const stack = Stack.of(scope);
504
+ // Get or create cache for this stack
505
+ let stackCache = certificateCache.get(stack);
506
+ if (!stackCache) {
507
+ stackCache = new Map();
508
+ certificateCache.set(stack, stackCache);
509
+ }
510
+ // Return cached certificate if one exists for this domain
511
+ const cached = stackCache.get(domainName);
512
+ if (cached) {
513
+ return cached;
514
+ }
515
+ // Create certificate at STACK level (not construct level!)
516
+ // This is the key difference - the certificate's lifecycle is tied to the stack,
517
+ // not to the individual construct that requested it
518
+ const sanitizedDomain = sanitizeDomainForId(domainName);
519
+ const cert = new acm.Certificate(stack, `${name}-${sanitizedDomain}`, {
520
+ domainName,
521
+ validation: acm.CertificateValidation.fromDns(zone),
522
+ });
523
+ Tags.of(cert).add(CDK$2.TAG.ROLE, roleTag);
524
+ // Cache for future requests
525
+ stackCache.set(domainName, cert);
526
+ return cert;
527
+ }
528
+ /**
529
+ * Sanitizes a domain name for use in CDK construct IDs.
530
+ * CDK construct IDs can only contain alphanumeric characters and hyphens.
531
+ */
532
+ function sanitizeDomainForId(domain) {
533
+ return domain.replace(/\./g, "-").replace(/[^a-zA-Z0-9-]/g, "");
534
+ }
535
+ /**
536
+ * Clears the certificate cache for a specific stack.
537
+ * Primarily useful for testing.
538
+ */
539
+ function clearCertificateCache(stack) {
540
+ certificateCache.delete(stack);
541
+ }
542
+ /**
543
+ * Clears all certificate caches.
544
+ * Primarily useful for testing.
545
+ */
546
+ function clearAllCertificateCaches() {
547
+ // WeakMap doesn't have a clear() method, so we create a new one
548
+ // This is a no-op since we can't actually clear a WeakMap,
549
+ // but stacks going out of scope will be garbage collected anyway
550
+ }
551
+
447
552
  /**
448
553
  * Check if the current environment matches the given environment
449
554
  */
@@ -773,34 +878,34 @@ const resolveParamsAndSecrets = ({ paramsAndSecrets, options, } = {}) => {
773
878
  };
774
879
 
775
880
  // It is a consumer if the environment is ephemeral
776
- function checkEnvIsConsumer(env = process.env) {
881
+ function checkEnvIsConsumer$1(env = process.env) {
777
882
  return (env.PROJECT_ENV === CDK$2.ENV.PERSONAL ||
778
883
  !!env.CDK_ENV_PERSONAL ||
779
884
  /** @deprecated */ env.PROJECT_ENV === "ephemeral" ||
780
885
  /** @deprecated */ !!env.CDK_ENV_EPHEMERAL);
781
886
  }
782
- function checkEnvIsProvider(env = process.env) {
887
+ function checkEnvIsProvider$1(env = process.env) {
783
888
  return env.PROJECT_ENV === CDK$2.ENV.SANDBOX;
784
889
  }
785
- function cleanName(name) {
890
+ function cleanName$1(name) {
786
891
  return name.replace(/[^a-zA-Z0-9:-]/g, "");
787
892
  }
788
- function exportEnvName(name, env = process.env) {
893
+ function exportEnvName$1(name, env = process.env) {
789
894
  let rawName;
790
- if (checkEnvIsProvider(env)) {
895
+ if (checkEnvIsProvider$1(env)) {
791
896
  rawName = `env-${env.PROJECT_ENV}-${env.PROJECT_KEY}-${name}`;
792
897
  // Clean the entire name to only allow alphanumeric, colons, and hyphens
793
- return cleanName(rawName);
898
+ return cleanName$1(rawName);
794
899
  }
795
900
  else {
796
- if (checkEnvIsConsumer(env)) {
901
+ if (checkEnvIsConsumer$1(env)) {
797
902
  rawName = `env-${CDK$2.ENV.SANDBOX}-${env.PROJECT_KEY}-${name}`;
798
903
  }
799
904
  else {
800
905
  rawName = `env-${env.PROJECT_ENV}-${env.PROJECT_KEY}-${name}`;
801
906
  }
802
907
  }
803
- return cleanName(rawName);
908
+ return cleanName$1(rawName);
804
909
  }
805
910
  class JaypieEnvSecret extends Construct {
806
911
  constructor(scope, idOrEnvKey, props) {
@@ -812,15 +917,15 @@ class JaypieEnvSecret extends Construct {
812
917
  process.env[idOrEnvKey] !== "";
813
918
  const id = treatAsEnvKey ? `EnvSecret_${idOrEnvKey}` : idOrEnvKey;
814
919
  super(scope, id);
815
- const { consumer = checkEnvIsConsumer(), envKey: envKeyProp, export: exportParam, generateSecretString, provider = checkEnvIsProvider(), roleTag, vendorTag, value, } = props || {};
920
+ const { consumer = checkEnvIsConsumer$1(), envKey: envKeyProp, export: exportParam, generateSecretString, provider = checkEnvIsProvider$1(), roleTag, vendorTag, value, } = props || {};
816
921
  const envKey = treatAsEnvKey ? idOrEnvKey : envKeyProp;
817
922
  this._envKey = envKey;
818
923
  let exportName;
819
924
  if (!exportParam) {
820
- exportName = exportEnvName(id);
925
+ exportName = exportEnvName$1(id);
821
926
  }
822
927
  else {
823
- exportName = cleanName(exportParam);
928
+ exportName = cleanName$1(exportParam);
824
929
  }
825
930
  if (consumer) {
826
931
  const secretName = Fn.importValue(exportName);
@@ -1033,22 +1138,18 @@ class JaypieApiGateway extends Construct {
1033
1138
  }
1034
1139
  }
1035
1140
  const apiGatewayName = name || constructEnvName("ApiGateway");
1036
- const certificateName = constructEnvName("Certificate");
1037
1141
  const apiDomainName = constructEnvName("ApiDomainName");
1038
1142
  let hostedZone;
1039
1143
  let certificateToUse;
1040
1144
  if (host && zone) {
1041
1145
  hostedZone = resolveHostedZone(this, { zone });
1042
- if (certificate === true) {
1043
- certificateToUse = new acm.Certificate(this, certificateName, {
1044
- domainName: host,
1045
- validation: acm.CertificateValidation.fromDns(hostedZone),
1046
- });
1047
- Tags.of(certificateToUse).add(CDK$2.TAG.ROLE, CDK$2.ROLE.HOSTING);
1048
- }
1049
- else if (typeof certificate === "object") {
1050
- certificateToUse = certificate;
1051
- }
1146
+ // Use resolveCertificate to create certificate at stack level (enables reuse when swapping constructs)
1147
+ certificateToUse = resolveCertificate(this, {
1148
+ certificate,
1149
+ domainName: host,
1150
+ roleTag: CDK$2.ROLE.HOSTING,
1151
+ zone: hostedZone,
1152
+ });
1052
1153
  this._certificate = certificateToUse;
1053
1154
  this._host = host;
1054
1155
  }
@@ -1808,6 +1909,238 @@ class JaypieBucketQueuedLambda extends JaypieQueuedLambda {
1808
1909
  }
1809
1910
  }
1810
1911
 
1912
+ // Check if environment is a consumer (personal/ephemeral builds import from sandbox)
1913
+ function checkEnvIsConsumer(env = process.env) {
1914
+ return (env.PROJECT_ENV === CDK$2.ENV.PERSONAL ||
1915
+ !!env.CDK_ENV_PERSONAL ||
1916
+ env.PROJECT_ENV === "ephemeral" ||
1917
+ !!env.CDK_ENV_EPHEMERAL);
1918
+ }
1919
+ // Check if environment is a provider (sandbox exports for consumers)
1920
+ function checkEnvIsProvider(env = process.env) {
1921
+ return env.PROJECT_ENV === CDK$2.ENV.SANDBOX;
1922
+ }
1923
+ // Sanitize export name to only allow alphanumeric, colons, and hyphens
1924
+ function cleanName(name) {
1925
+ return name.replace(/[^a-zA-Z0-9:-]/g, "");
1926
+ }
1927
+ // Generate export name based on environment
1928
+ function exportEnvName(name, env = process.env) {
1929
+ const projectKey = env.PROJECT_KEY || "default";
1930
+ let rawName;
1931
+ if (checkEnvIsProvider(env)) {
1932
+ rawName = `env-${env.PROJECT_ENV}-${projectKey}-cert-${name}`;
1933
+ }
1934
+ else if (checkEnvIsConsumer(env)) {
1935
+ rawName = `env-${CDK$2.ENV.SANDBOX}-${projectKey}-cert-${name}`;
1936
+ }
1937
+ else {
1938
+ rawName = `env-${env.PROJECT_ENV || "default"}-${projectKey}-cert-${name}`;
1939
+ }
1940
+ return cleanName(rawName);
1941
+ }
1942
+ // Resolve domain name from props or environment (called before super)
1943
+ function resolveDomainNameFromProps(props) {
1944
+ if (props?.domainName) {
1945
+ return props.domainName;
1946
+ }
1947
+ if (process.env.CDK_ENV_API_HOST_NAME) {
1948
+ return process.env.CDK_ENV_API_HOST_NAME;
1949
+ }
1950
+ if (process.env.CDK_ENV_API_SUBDOMAIN) {
1951
+ return mergeDomain(process.env.CDK_ENV_API_SUBDOMAIN, process.env.CDK_ENV_API_HOSTED_ZONE ||
1952
+ process.env.CDK_ENV_HOSTED_ZONE ||
1953
+ "");
1954
+ }
1955
+ return undefined;
1956
+ }
1957
+ // Sanitize domain for construct ID
1958
+ function sanitizeDomain(domain) {
1959
+ return domain.replace(/\./g, "-");
1960
+ }
1961
+ /**
1962
+ * A standalone certificate construct that can be shared across constructs.
1963
+ *
1964
+ * Key feature: Uses the same `resolveCertificate()` helper as JaypieDistribution,
1965
+ * JaypieApiGateway, etc. This means:
1966
+ * - Certificates are created at the stack level and cached by domain
1967
+ * - You can "take over" a certificate from another construct by using the same domain
1968
+ * - Swapping between JaypieDistribution and JaypieApiGateway won't recreate certs
1969
+ *
1970
+ * Supports flexible constructor signatures:
1971
+ * - `new JaypieCertificate(scope)` - uses environment defaults
1972
+ * - `new JaypieCertificate(scope, props)` - ID auto-generated from domain
1973
+ * - `new JaypieCertificate(scope, id, props)` - explicit ID
1974
+ *
1975
+ * @example
1976
+ * // Minimal - uses environment variables for domain/zone
1977
+ * const cert = new JaypieCertificate(this);
1978
+ *
1979
+ * @example
1980
+ * // With options - ID auto-generated as "JaypieCert-api-example-com"
1981
+ * const cert = new JaypieCertificate(this, {
1982
+ * domainName: "api.example.com",
1983
+ * zone: "example.com",
1984
+ * });
1985
+ *
1986
+ * @example
1987
+ * // Explicit ID - useful when you need a specific construct ID
1988
+ * const cert = new JaypieCertificate(this, "MyApiCert", {
1989
+ * domainName: "api.example.com",
1990
+ * zone: "example.com",
1991
+ * });
1992
+ *
1993
+ * @example
1994
+ * // Take over from JaypieDistribution (uses same ID format)
1995
+ * // After removing JaypieDistribution with certificate: true
1996
+ * const cert = new JaypieCertificate(this, {
1997
+ * domainName: "api.example.com",
1998
+ * zone: "example.com",
1999
+ * });
2000
+ *
2001
+ * @example
2002
+ * // Provider/consumer pattern for cross-stack sharing
2003
+ * // In sandbox stack:
2004
+ * new JaypieCertificate(this, { provider: true });
2005
+ *
2006
+ * // In personal build:
2007
+ * new JaypieCertificate(this, { consumer: true });
2008
+ */
2009
+ class JaypieCertificate extends Construct {
2010
+ constructor(scope, idOrProps, maybeProps) {
2011
+ // Resolve constructor arguments
2012
+ let id;
2013
+ let props;
2014
+ if (typeof idOrProps === "string") {
2015
+ // (scope, id, props) pattern
2016
+ id = idOrProps;
2017
+ props = maybeProps || {};
2018
+ }
2019
+ else if (typeof idOrProps === "object" && idOrProps !== null) {
2020
+ // (scope, props) pattern - auto-generate id
2021
+ props = idOrProps;
2022
+ const domainName = resolveDomainNameFromProps(props);
2023
+ if (domainName) {
2024
+ // Use "JaypieCert-" prefix to avoid collision with internal certificate
2025
+ id = props.id || `JaypieCert-${sanitizeDomain(domainName)}`;
2026
+ }
2027
+ else {
2028
+ id = props.id || "JaypieCert";
2029
+ }
2030
+ }
2031
+ else {
2032
+ // (scope) pattern - no id, no props
2033
+ props = {};
2034
+ const domainName = resolveDomainNameFromProps(props);
2035
+ if (domainName) {
2036
+ id = `JaypieCert-${sanitizeDomain(domainName)}`;
2037
+ }
2038
+ else {
2039
+ id = "JaypieCert";
2040
+ }
2041
+ }
2042
+ super(scope, id);
2043
+ const { consumer = checkEnvIsConsumer(), domainName: propsDomainName, export: exportParam, provider = checkEnvIsProvider(), roleTag = CDK$2.ROLE.API, zone: propsZone, } = props;
2044
+ // Validate environment variables
2045
+ if (process.env.CDK_ENV_API_SUBDOMAIN &&
2046
+ !isValidSubdomain(process.env.CDK_ENV_API_SUBDOMAIN)) {
2047
+ throw new Error("CDK_ENV_API_SUBDOMAIN is not a valid subdomain");
2048
+ }
2049
+ if (process.env.CDK_ENV_API_HOSTED_ZONE &&
2050
+ !isValidHostname$1(process.env.CDK_ENV_API_HOSTED_ZONE)) {
2051
+ throw new Error("CDK_ENV_API_HOSTED_ZONE is not a valid hostname");
2052
+ }
2053
+ if (process.env.CDK_ENV_HOSTED_ZONE &&
2054
+ !isValidHostname$1(process.env.CDK_ENV_HOSTED_ZONE)) {
2055
+ throw new Error("CDK_ENV_HOSTED_ZONE is not a valid hostname");
2056
+ }
2057
+ // Determine domain name from props or environment
2058
+ let domainName = propsDomainName;
2059
+ if (!domainName) {
2060
+ if (process.env.CDK_ENV_API_HOST_NAME) {
2061
+ domainName = process.env.CDK_ENV_API_HOST_NAME;
2062
+ }
2063
+ else if (process.env.CDK_ENV_API_SUBDOMAIN) {
2064
+ domainName = mergeDomain(process.env.CDK_ENV_API_SUBDOMAIN, process.env.CDK_ENV_API_HOSTED_ZONE ||
2065
+ process.env.CDK_ENV_HOSTED_ZONE ||
2066
+ "");
2067
+ }
2068
+ }
2069
+ if (!domainName) {
2070
+ throw new Error("domainName is required for JaypieCertificate (or set CDK_ENV_API_HOST_NAME / CDK_ENV_API_SUBDOMAIN)");
2071
+ }
2072
+ if (!isValidHostname$1(domainName)) {
2073
+ throw new Error("domainName is not a valid hostname");
2074
+ }
2075
+ this.domainName = domainName;
2076
+ // Determine zone from props or environment
2077
+ const zone = propsZone ||
2078
+ process.env.CDK_ENV_API_HOSTED_ZONE ||
2079
+ process.env.CDK_ENV_HOSTED_ZONE;
2080
+ // Generate export name
2081
+ const sanitizedDomain = domainName.replace(/\./g, "-");
2082
+ const exportName = exportParam
2083
+ ? cleanName(exportParam)
2084
+ : exportEnvName(sanitizedDomain);
2085
+ if (consumer) {
2086
+ // Import certificate ARN from provider stack
2087
+ const certificateArn = Fn.importValue(exportName);
2088
+ this.certificate = acm.Certificate.fromCertificateArn(this, "ImportedCertificate", certificateArn);
2089
+ this.certificateArn = certificateArn;
2090
+ new CfnOutput(this, "ConsumedCertificateArn", {
2091
+ value: this.certificateArn,
2092
+ });
2093
+ }
2094
+ else {
2095
+ // Create or get cached certificate at stack level
2096
+ if (!zone) {
2097
+ throw new Error("zone is required for JaypieCertificate when not consuming (or set CDK_ENV_API_HOSTED_ZONE / CDK_ENV_HOSTED_ZONE)");
2098
+ }
2099
+ const hostedZone = resolveHostedZone(this, { zone });
2100
+ // Use resolveCertificate to create at stack level (enables sharing)
2101
+ const cert = resolveCertificate(this, {
2102
+ certificate: true,
2103
+ domainName,
2104
+ roleTag,
2105
+ zone: hostedZone,
2106
+ });
2107
+ if (!cert) {
2108
+ throw new Error("Failed to create certificate");
2109
+ }
2110
+ this.certificate = cert;
2111
+ this.certificateArn = cert.certificateArn;
2112
+ if (provider) {
2113
+ new CfnOutput(this, "ProvidedCertificateArn", {
2114
+ value: this.certificateArn,
2115
+ exportName,
2116
+ });
2117
+ }
2118
+ else {
2119
+ new CfnOutput(this, "CertificateArn", {
2120
+ value: this.certificateArn,
2121
+ });
2122
+ }
2123
+ }
2124
+ }
2125
+ // IResource implementation
2126
+ get stack() {
2127
+ return Stack.of(this);
2128
+ }
2129
+ get env() {
2130
+ return {
2131
+ account: Stack.of(this).account,
2132
+ region: Stack.of(this).region,
2133
+ };
2134
+ }
2135
+ applyRemovalPolicy(policy) {
2136
+ this.certificate.applyRemovalPolicy(policy);
2137
+ }
2138
+ // ICertificate implementation
2139
+ metricDaysToExpiry(props) {
2140
+ return this.certificate.metricDaysToExpiry(props);
2141
+ }
2142
+ }
2143
+
1811
2144
  class JaypieDatadogBucket extends Construct {
1812
2145
  /**
1813
2146
  * Create a new S3 bucket for Datadog log archiving with automatic IAM permissions
@@ -2085,16 +2418,13 @@ class JaypieDistribution extends Construct {
2085
2418
  let certificateToUse;
2086
2419
  if (host && zone && certificateProp !== false) {
2087
2420
  hostedZone = resolveHostedZone(this, { zone });
2088
- if (certificateProp === true) {
2089
- certificateToUse = new acm.Certificate(this, constructEnvName("Certificate"), {
2090
- domainName: host,
2091
- validation: acm.CertificateValidation.fromDns(hostedZone),
2092
- });
2093
- Tags.of(certificateToUse).add(CDK$2.TAG.ROLE, roleTag);
2094
- }
2095
- else if (typeof certificateProp === "object") {
2096
- certificateToUse = certificateProp;
2097
- }
2421
+ // Use resolveCertificate to create certificate at stack level (enables reuse when swapping constructs)
2422
+ certificateToUse = resolveCertificate(this, {
2423
+ certificate: certificateProp,
2424
+ domainName: host,
2425
+ roleTag,
2426
+ zone: hostedZone,
2427
+ });
2098
2428
  this.certificate = certificateToUse;
2099
2429
  }
2100
2430
  // Create log bucket if logging is enabled
@@ -3581,19 +3911,17 @@ class JaypieWebDeploymentBucket extends Construct {
3581
3911
  else {
3582
3912
  hostedZone = zone;
3583
3913
  }
3584
- // Create certificate if not provided
3585
- if (props.certificate !== false) {
3586
- this.certificate =
3587
- typeof props.certificate === "object"
3588
- ? props.certificate
3589
- : new acm.Certificate(this, "Certificate", {
3590
- domainName: host,
3591
- validation: acm.CertificateValidation.fromDns(hostedZone),
3592
- });
3914
+ // Use resolveCertificate to create certificate at stack level (enables reuse when swapping constructs)
3915
+ this.certificate = resolveCertificate(this, {
3916
+ certificate: props.certificate,
3917
+ domainName: host,
3918
+ roleTag,
3919
+ zone: hostedZone,
3920
+ });
3921
+ if (this.certificate) {
3593
3922
  new CfnOutput(this, "CertificateArn", {
3594
3923
  value: this.certificate.certificateArn,
3595
3924
  });
3596
- Tags.of(this.certificate).add(CDK$2.TAG.ROLE, roleTag);
3597
3925
  }
3598
3926
  // Create CloudFront distribution
3599
3927
  this.distribution = new cloudfront.Distribution(this, "Distribution", {
@@ -3836,5 +4164,5 @@ class JaypieTraceSigningKeySecret extends JaypieEnvSecret {
3836
4164
  }
3837
4165
  }
3838
4166
 
3839
- export { CDK$2 as CDK, JaypieAccountLoggingBucket, JaypieApiGateway, JaypieAppStack, JaypieBucketQueuedLambda, JaypieDatadogBucket, JaypieDatadogForwarder, JaypieDatadogSecret, JaypieDistribution, JaypieDnsRecord, JaypieDynamoDb, JaypieEnvSecret, JaypieEventsRule, JaypieExpressLambda, JaypieGitHubDeployRole, JaypieHostedZone, JaypieInfrastructureStack, JaypieLambda, JaypieMongoDbSecret, JaypieNextJs, JaypieOpenAiSecret, JaypieOrganizationTrail, JaypieQueuedLambda, JaypieSsoPermissions, JaypieSsoSyncApplication, JaypieStack, JaypieStaticWebBucket, JaypieStreamingLambda, JaypieTraceSigningKeySecret, JaypieWebDeploymentBucket, LAMBDA_WEB_ADAPTER, addDatadogLayers, clearAllSecretsCaches, clearSecretsCache, constructEnvName, constructStackName, constructTagger, envHostname, extendDatadogRole, isEnv, isProductionEnv, isSandboxEnv, isValidHostname$1 as isValidHostname, isValidSubdomain, jaypieLambdaEnv, mergeDomain, resolveDatadogForwarderFunction, resolveDatadogLayers, resolveDatadogLoggingDestination, resolveEnvironment, resolveHostedZone, resolveParamsAndSecrets, resolveSecrets };
4167
+ export { CDK$2 as CDK, JaypieAccountLoggingBucket, JaypieApiGateway, JaypieAppStack, JaypieBucketQueuedLambda, JaypieCertificate, JaypieDatadogBucket, JaypieDatadogForwarder, JaypieDatadogSecret, JaypieDistribution, JaypieDnsRecord, JaypieDynamoDb, JaypieEnvSecret, JaypieEventsRule, JaypieExpressLambda, JaypieGitHubDeployRole, JaypieHostedZone, JaypieInfrastructureStack, JaypieLambda, JaypieMongoDbSecret, JaypieNextJs, JaypieOpenAiSecret, JaypieOrganizationTrail, JaypieQueuedLambda, JaypieSsoPermissions, JaypieSsoSyncApplication, JaypieStack, JaypieStaticWebBucket, JaypieStreamingLambda, JaypieTraceSigningKeySecret, JaypieWebDeploymentBucket, LAMBDA_WEB_ADAPTER, addDatadogLayers, clearAllCertificateCaches, clearAllSecretsCaches, clearCertificateCache, clearSecretsCache, constructEnvName, constructStackName, constructTagger, envHostname, extendDatadogRole, isEnv, isProductionEnv, isSandboxEnv, isValidHostname$1 as isValidHostname, isValidSubdomain, jaypieLambdaEnv, mergeDomain, resolveCertificate, resolveDatadogForwarderFunction, resolveDatadogLayers, resolveDatadogLoggingDestination, resolveEnvironment, resolveHostedZone, resolveParamsAndSecrets, resolveSecrets };
3840
4168
  //# sourceMappingURL=index.js.map