@jaypie/constructs 1.1.65 → 1.1.66

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.
Files changed (139) hide show
  1. package/dist/cjs/JaypieAccountLoggingBucket.d.ts +60 -0
  2. package/dist/cjs/JaypieApiGateway.d.ts +47 -0
  3. package/dist/cjs/JaypieAppStack.d.ts +5 -0
  4. package/dist/cjs/JaypieBucketQueuedLambda.d.ts +48 -0
  5. package/dist/cjs/JaypieDatadogBucket.d.ts +55 -0
  6. package/dist/cjs/JaypieDatadogForwarder.d.ts +76 -0
  7. package/dist/cjs/JaypieDatadogSecret.d.ts +5 -0
  8. package/dist/cjs/JaypieDistribution.d.ts +76 -0
  9. package/dist/cjs/JaypieDnsRecord.d.ts +45 -0
  10. package/dist/cjs/JaypieEnvSecret.d.ts +41 -0
  11. package/dist/cjs/JaypieEventsRule.d.ts +45 -0
  12. package/dist/cjs/JaypieExpressLambda.d.ts +5 -0
  13. package/dist/cjs/JaypieGitHubDeployRole.d.ts +14 -0
  14. package/dist/cjs/JaypieHostedZone.d.ts +59 -0
  15. package/dist/cjs/JaypieInfrastructureStack.d.ts +5 -0
  16. package/dist/cjs/JaypieLambda.d.ts +115 -0
  17. package/dist/cjs/JaypieMongoDbSecret.d.ts +5 -0
  18. package/dist/cjs/JaypieNextJs.d.ts +36 -0
  19. package/dist/cjs/JaypieNextJs.test.d.ts +1 -0
  20. package/dist/cjs/JaypieOpenAiSecret.d.ts +5 -0
  21. package/dist/cjs/JaypieOrganizationTrail.d.ts +62 -0
  22. package/dist/cjs/JaypieQueuedLambda.d.ts +77 -0
  23. package/dist/cjs/JaypieSsoPermissions.d.ts +96 -0
  24. package/dist/cjs/JaypieSsoSyncApplication.d.ts +27 -0
  25. package/dist/cjs/JaypieStack.d.ts +8 -0
  26. package/dist/cjs/JaypieStaticWebBucket.d.ts +22 -0
  27. package/dist/cjs/JaypieTraceSigningKeySecret.d.ts +5 -0
  28. package/dist/cjs/JaypieWebDeploymentBucket.d.ts +84 -0
  29. package/dist/cjs/__tests__/JaypieBucketQueuedLambda.spec.d.ts +1 -0
  30. package/dist/cjs/__tests__/JaypieDistribution.spec.d.ts +1 -0
  31. package/dist/cjs/__tests__/JaypieDnsRecord.spec.d.ts +1 -0
  32. package/dist/cjs/__tests__/JaypieEnvSecret.spec.d.ts +1 -0
  33. package/dist/cjs/__tests__/JaypieExpressLambda.spec.d.ts +1 -0
  34. package/dist/cjs/__tests__/JaypieHostedZone.spec.d.ts +1 -0
  35. package/dist/cjs/__tests__/JaypieLambda.spec.d.ts +1 -0
  36. package/dist/cjs/__tests__/JaypieQueuedLambda.spec.d.ts +1 -0
  37. package/dist/cjs/__tests__/JaypieSsoPermissions.spec.d.ts +1 -0
  38. package/dist/cjs/__tests__/JaypieSsoSyncApplication.spec.d.ts +1 -0
  39. package/dist/cjs/__tests__/JaypieStaticWebBucket.spec.d.ts +1 -0
  40. package/dist/cjs/__tests__/index.spec.d.ts +1 -0
  41. package/dist/cjs/constants.d.ts +151 -0
  42. package/dist/cjs/helpers/__tests__/envHostname.spec.d.ts +1 -0
  43. package/dist/cjs/helpers/__tests__/jaypieLambdaEnv.spec.d.ts +1 -0
  44. package/dist/cjs/helpers/__tests__/resolveDatadogForwarderFunction.spec.d.ts +1 -0
  45. package/dist/cjs/helpers/__tests__/resolveDatadogLoggingDestination.spec.d.ts +1 -0
  46. package/dist/cjs/helpers/__tests__/resolveEnvironment.spec.d.ts +1 -0
  47. package/dist/cjs/helpers/__tests__/resolveSecrets.spec.d.ts +1 -0
  48. package/dist/cjs/helpers/addDatadogLayers.d.ts +5 -0
  49. package/dist/cjs/helpers/constructEnvName.d.ts +5 -0
  50. package/dist/cjs/helpers/constructStackName.d.ts +1 -0
  51. package/dist/cjs/helpers/constructTagger.d.ts +4 -0
  52. package/dist/cjs/helpers/envHostname.d.ts +6 -0
  53. package/dist/cjs/helpers/extendDatadogRole.d.ts +31 -0
  54. package/dist/cjs/helpers/index.d.ts +18 -0
  55. package/dist/cjs/helpers/isEnv.d.ts +12 -0
  56. package/dist/cjs/helpers/isValidHostname.d.ts +1 -0
  57. package/dist/cjs/helpers/isValidSubdomain.d.ts +1 -0
  58. package/dist/cjs/helpers/jaypieLambdaEnv.d.ts +8 -0
  59. package/dist/cjs/helpers/mergeDomain.d.ts +1 -0
  60. package/dist/cjs/helpers/resolveDatadogForwarderFunction.d.ts +7 -0
  61. package/dist/cjs/helpers/resolveDatadogLayers.d.ts +7 -0
  62. package/dist/cjs/helpers/resolveDatadogLoggingDestination.d.ts +4 -0
  63. package/dist/cjs/helpers/resolveEnvironment.d.ts +47 -0
  64. package/dist/cjs/helpers/resolveHostedZone.d.ts +6 -0
  65. package/dist/cjs/helpers/resolveParamsAndSecrets.d.ts +13 -0
  66. package/dist/cjs/helpers/resolveSecrets.d.ts +52 -0
  67. package/dist/cjs/index.cjs +3503 -0
  68. package/dist/cjs/index.cjs.map +1 -0
  69. package/dist/cjs/index.d.ts +29 -0
  70. package/dist/esm/JaypieAccountLoggingBucket.d.ts +60 -0
  71. package/dist/esm/JaypieApiGateway.d.ts +47 -0
  72. package/dist/esm/JaypieAppStack.d.ts +5 -0
  73. package/dist/esm/JaypieBucketQueuedLambda.d.ts +48 -0
  74. package/dist/esm/JaypieDatadogBucket.d.ts +55 -0
  75. package/dist/esm/JaypieDatadogForwarder.d.ts +76 -0
  76. package/dist/esm/JaypieDatadogSecret.d.ts +5 -0
  77. package/dist/esm/JaypieDistribution.d.ts +76 -0
  78. package/dist/esm/JaypieDnsRecord.d.ts +45 -0
  79. package/dist/esm/JaypieEnvSecret.d.ts +41 -0
  80. package/dist/esm/JaypieEventsRule.d.ts +45 -0
  81. package/dist/esm/JaypieExpressLambda.d.ts +5 -0
  82. package/dist/esm/JaypieGitHubDeployRole.d.ts +14 -0
  83. package/dist/esm/JaypieHostedZone.d.ts +59 -0
  84. package/dist/esm/JaypieInfrastructureStack.d.ts +5 -0
  85. package/dist/esm/JaypieLambda.d.ts +115 -0
  86. package/dist/esm/JaypieMongoDbSecret.d.ts +5 -0
  87. package/dist/esm/JaypieNextJs.d.ts +36 -0
  88. package/dist/esm/JaypieNextJs.test.d.ts +1 -0
  89. package/dist/esm/JaypieOpenAiSecret.d.ts +5 -0
  90. package/dist/esm/JaypieOrganizationTrail.d.ts +62 -0
  91. package/dist/esm/JaypieQueuedLambda.d.ts +77 -0
  92. package/dist/esm/JaypieSsoPermissions.d.ts +96 -0
  93. package/dist/esm/JaypieSsoSyncApplication.d.ts +27 -0
  94. package/dist/esm/JaypieStack.d.ts +8 -0
  95. package/dist/esm/JaypieStaticWebBucket.d.ts +22 -0
  96. package/dist/esm/JaypieTraceSigningKeySecret.d.ts +5 -0
  97. package/dist/esm/JaypieWebDeploymentBucket.d.ts +84 -0
  98. package/dist/esm/__tests__/JaypieBucketQueuedLambda.spec.d.ts +1 -0
  99. package/dist/esm/__tests__/JaypieDistribution.spec.d.ts +1 -0
  100. package/dist/esm/__tests__/JaypieDnsRecord.spec.d.ts +1 -0
  101. package/dist/esm/__tests__/JaypieEnvSecret.spec.d.ts +1 -0
  102. package/dist/esm/__tests__/JaypieExpressLambda.spec.d.ts +1 -0
  103. package/dist/esm/__tests__/JaypieHostedZone.spec.d.ts +1 -0
  104. package/dist/esm/__tests__/JaypieLambda.spec.d.ts +1 -0
  105. package/dist/esm/__tests__/JaypieQueuedLambda.spec.d.ts +1 -0
  106. package/dist/esm/__tests__/JaypieSsoPermissions.spec.d.ts +1 -0
  107. package/dist/esm/__tests__/JaypieSsoSyncApplication.spec.d.ts +1 -0
  108. package/dist/esm/__tests__/JaypieStaticWebBucket.spec.d.ts +1 -0
  109. package/dist/esm/__tests__/index.spec.d.ts +1 -0
  110. package/dist/esm/constants.d.ts +151 -0
  111. package/dist/esm/helpers/__tests__/envHostname.spec.d.ts +1 -0
  112. package/dist/esm/helpers/__tests__/jaypieLambdaEnv.spec.d.ts +1 -0
  113. package/dist/esm/helpers/__tests__/resolveDatadogForwarderFunction.spec.d.ts +1 -0
  114. package/dist/esm/helpers/__tests__/resolveDatadogLoggingDestination.spec.d.ts +1 -0
  115. package/dist/esm/helpers/__tests__/resolveEnvironment.spec.d.ts +1 -0
  116. package/dist/esm/helpers/__tests__/resolveSecrets.spec.d.ts +1 -0
  117. package/dist/esm/helpers/addDatadogLayers.d.ts +5 -0
  118. package/dist/esm/helpers/constructEnvName.d.ts +5 -0
  119. package/dist/esm/helpers/constructStackName.d.ts +1 -0
  120. package/dist/esm/helpers/constructTagger.d.ts +4 -0
  121. package/dist/esm/helpers/envHostname.d.ts +6 -0
  122. package/dist/esm/helpers/extendDatadogRole.d.ts +31 -0
  123. package/dist/esm/helpers/index.d.ts +18 -0
  124. package/dist/esm/helpers/isEnv.d.ts +12 -0
  125. package/dist/esm/helpers/isValidHostname.d.ts +1 -0
  126. package/dist/esm/helpers/isValidSubdomain.d.ts +1 -0
  127. package/dist/esm/helpers/jaypieLambdaEnv.d.ts +8 -0
  128. package/dist/esm/helpers/mergeDomain.d.ts +1 -0
  129. package/dist/esm/helpers/resolveDatadogForwarderFunction.d.ts +7 -0
  130. package/dist/esm/helpers/resolveDatadogLayers.d.ts +7 -0
  131. package/dist/esm/helpers/resolveDatadogLoggingDestination.d.ts +4 -0
  132. package/dist/esm/helpers/resolveEnvironment.d.ts +47 -0
  133. package/dist/esm/helpers/resolveHostedZone.d.ts +6 -0
  134. package/dist/esm/helpers/resolveParamsAndSecrets.d.ts +13 -0
  135. package/dist/esm/helpers/resolveSecrets.d.ts +52 -0
  136. package/dist/esm/index.d.ts +29 -0
  137. package/dist/esm/index.js +3423 -0
  138. package/dist/esm/index.js.map +1 -0
  139. package/package.json +2 -2
@@ -0,0 +1,3423 @@
1
+ import * as cdk from 'aws-cdk-lib';
2
+ import { Tags, Stack, Fn, CfnOutput, SecretValue, Duration, RemovalPolicy, CfnStack } from 'aws-cdk-lib';
3
+ import * as s3 from 'aws-cdk-lib/aws-s3';
4
+ import { Bucket, StorageClass, BucketAccessControl, EventType } from 'aws-cdk-lib/aws-s3';
5
+ import { Construct } from 'constructs';
6
+ import * as acm from 'aws-cdk-lib/aws-certificatemanager';
7
+ import * as apiGateway from 'aws-cdk-lib/aws-apigateway';
8
+ import * as route53 from 'aws-cdk-lib/aws-route53';
9
+ import { TxtRecord, NsRecord, MxRecord, CnameRecord, ARecord, RecordTarget, HostedZone } from 'aws-cdk-lib/aws-route53';
10
+ import * as route53Targets from 'aws-cdk-lib/aws-route53-targets';
11
+ import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
12
+ import { DatadogLambda } from 'datadog-cdk-constructs-v2';
13
+ import { ConfigurationError } from '@jaypie/errors';
14
+ import { Role, PolicyStatement, Policy, FederatedPrincipal, Effect, ServicePrincipal, ManagedPolicy } from 'aws-cdk-lib/aws-iam';
15
+ import * as lambda from 'aws-cdk-lib/aws-lambda';
16
+ import * as logDestinations from 'aws-cdk-lib/aws-logs-destinations';
17
+ import * as s3n from 'aws-cdk-lib/aws-s3-notifications';
18
+ import { LambdaDestination } from 'aws-cdk-lib/aws-s3-notifications';
19
+ import * as sqs from 'aws-cdk-lib/aws-sqs';
20
+ import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources';
21
+ import * as logs from 'aws-cdk-lib/aws-logs';
22
+ import { LogGroup, RetentionDays, FilterPattern } from 'aws-cdk-lib/aws-logs';
23
+ import { Rule, RuleTargetInput } from 'aws-cdk-lib/aws-events';
24
+ import { LambdaFunction } from 'aws-cdk-lib/aws-events-targets';
25
+ import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
26
+ import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
27
+ import { Nextjs } from 'cdk-nextjs-standalone';
28
+ import * as path from 'path';
29
+ import { Trail, ReadWriteType } from 'aws-cdk-lib/aws-cloudtrail';
30
+ import { CfnPermissionSet, CfnAssignment } from 'aws-cdk-lib/aws-sso';
31
+ import { CfnApplication } from 'aws-cdk-lib/aws-sam';
32
+
33
+ const CDK$2 = {
34
+ ACCOUNT: {
35
+ DEVELOPMENT: "development",
36
+ MANAGEMENT: "management",
37
+ OPERATIONS: "operations",
38
+ PRODUCTION: "production",
39
+ SANDBOX: "sandbox",
40
+ SECURITY: "security",
41
+ STAGE: "stage",
42
+ },
43
+ BUILD: {
44
+ CONFIG: {
45
+ ALL: "all",
46
+ API: "api",
47
+ INFRASTRUCTURE: "infrastructure",
48
+ NONE: "none",
49
+ WEB: "web",
50
+ },
51
+ PERSONAL: "personal",
52
+ /**
53
+ * @deprecated rename "ephemeral" to "personal" (since 2/24/2025)
54
+ */
55
+ EPHEMERAL: "ephemeral",
56
+ /**
57
+ * @deprecated as even "ephemeral" builds have static assets (since 7/6/2024)
58
+ */
59
+ STATIC: "static",
60
+ },
61
+ CREATION: {
62
+ CDK: "cdk",
63
+ CLOUDFORMATION_TEMPLATE: "template",
64
+ MANUAL: "manual",
65
+ },
66
+ DATADOG: {
67
+ SITE: "datadoghq.com",
68
+ LAYER: {
69
+ // https://docs.datadoghq.com/serverless/aws_lambda/installation/nodejs/?tab=awscdk
70
+ NODE: 127, // 127 on 9/12/2025
71
+ EXTENSION: 86, // 86 on 9/12/2025
72
+ },
73
+ },
74
+ DEFAULT: {
75
+ REGION: "us-east-1",
76
+ },
77
+ DNS: {
78
+ CONFIG: {
79
+ TTL: 300, // 5 minutes in seconds for Route53
80
+ },
81
+ RECORD: {
82
+ A: "A",
83
+ CNAME: "CNAME",
84
+ MX: "MX",
85
+ NS: "NS",
86
+ TXT: "TXT",
87
+ },
88
+ },
89
+ DURATION: {
90
+ EXPRESS_API: 30,
91
+ LAMBDA_MAXIMUM: 900,
92
+ LAMBDA_WORKER: 900,
93
+ },
94
+ ENV: {
95
+ DEMO: "demo", // Mirror of production
96
+ DEVELOPMENT: "development", // Internal most stable development space
97
+ /** @deprecated */ EPHEMERAL: "ephemeral", // Alias for "build"
98
+ LOCAL: "local",
99
+ /** @deprecated */ MAIN: "main", // Alias for development
100
+ META: "meta", // For non-environment/infrastructure stacks
101
+ PERSONAL: "personal", // Personal builds using resources provided by sandbox
102
+ PREVIEW: "preview", // External next thing to be released
103
+ PRODUCTION: "production",
104
+ RELEASE: "release", // Internal next thing to be released
105
+ REVIEW: "review", // Internal place to collaborate on issues
106
+ SANDBOX: "sandbox", // Internal build space with no guaranteed longevity
107
+ TRAINING: "training", // aka "test"; mirror of production for external audiences
108
+ },
109
+ HOST: {
110
+ APEX: "@",
111
+ },
112
+ IMPORT: {
113
+ DATADOG_LOG_FORWARDER: "account-datadog-forwarder",
114
+ DATADOG_ROLE: "account-datadog-role",
115
+ DATADOG_SECRET: "account-datadog-secret",
116
+ LOG_BUCKET: "account-log-bucket",
117
+ OIDC_PROVIDER: "github-oidc-provider",
118
+ },
119
+ LAMBDA: {
120
+ LOG_RETENTION: 90,
121
+ MEMORY_SIZE: 1024,
122
+ },
123
+ PRINCIPAL: {
124
+ ROUTE53: "route53.amazonaws.com",
125
+ },
126
+ PRINCIPAL_TYPE: {
127
+ GROUP: "GROUP",
128
+ USER: "USER",
129
+ },
130
+ PROJECT: {
131
+ INFRASTRUCTURE: "infrastructure",
132
+ },
133
+ ROLE: {
134
+ API: "api",
135
+ DEPLOY: "deploy",
136
+ HOSTING: "hosting",
137
+ MONITORING: "monitoring",
138
+ NETWORKING: "networking",
139
+ PROCESSING: "processing",
140
+ SECURITY: "security",
141
+ STACK: "stack",
142
+ STORAGE: "storage",
143
+ TOY: "toy",
144
+ },
145
+ SERVICE: {
146
+ DATADOG: "datadog",
147
+ INFRASTRUCTURE: "infrastructure",
148
+ LIBRARIES: "libraries",
149
+ NONE: "none",
150
+ SSO: "sso",
151
+ TRACE: "trace",
152
+ },
153
+ TAG: {
154
+ BUILD_DATE: "buildDate",
155
+ BUILD_HEX: "buildHex",
156
+ BUILD_NUMBER: "buildNumber",
157
+ BUILD_TIME: "buildTime",
158
+ BUILD_TYPE: "buildType",
159
+ COMMIT: "commit",
160
+ CREATION: "creation",
161
+ ENV: "env",
162
+ NONCE: "nonce",
163
+ PROJECT: "project",
164
+ ROLE: "role",
165
+ SERVICE: "service",
166
+ SPONSOR: "sponsor",
167
+ STACK: "stack",
168
+ STACK_SHA: "stackSha",
169
+ VENDOR: "vendor",
170
+ VERSION: "version",
171
+ },
172
+ TARGET_TYPE: {
173
+ AWS_ACCOUNT: "AWS_ACCOUNT",
174
+ },
175
+ VENDOR: {
176
+ ANTHROPIC: "anthropic",
177
+ AUTH0: "auth0",
178
+ DATADOG: "datadog",
179
+ KNOWTRACE: "knowtrace",
180
+ MONGODB: "mongodb",
181
+ OPENAI: "openai",
182
+ SPLINTERLANDS: "splinterlands",
183
+ },
184
+ };
185
+
186
+ class JaypieAccountLoggingBucket extends Construct {
187
+ /**
188
+ * Create a new account-wide logging S3 bucket with lifecycle policies and export
189
+ */
190
+ constructor(scope, idOrProps, propsOrUndefined) {
191
+ // Handle overloaded constructor signatures
192
+ let props;
193
+ let id;
194
+ if (typeof idOrProps === "string") {
195
+ // First param is ID, second is props
196
+ props = propsOrUndefined || {};
197
+ id = idOrProps;
198
+ }
199
+ else {
200
+ // First param is props
201
+ props = idOrProps || {};
202
+ id = props.id || "AccountLoggingBucket";
203
+ }
204
+ super(scope, id);
205
+ // Generate default bucket name with PROJECT_NONCE
206
+ const defaultBucketName = process.env.PROJECT_NONCE
207
+ ? `account-logging-stack-${process.env.PROJECT_NONCE.toLowerCase()}`
208
+ : "account-logging-stack";
209
+ // Extract Jaypie-specific options
210
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
211
+ const { bucketName = defaultBucketName, createOutput = true, expirationDays = 365, exportName = CDK$2.IMPORT.LOG_BUCKET, glacierTransitionDays = 180, id: _id, infrequentAccessTransitionDays = 30, outputDescription = "Account-wide logging bucket", project, service = CDK$2.SERVICE.INFRASTRUCTURE, ...bucketProps } = props;
212
+ // Create the bucket with lifecycle rules
213
+ this.bucket = new Bucket(this, "Bucket", {
214
+ accessControl: BucketAccessControl.LOG_DELIVERY_WRITE,
215
+ bucketName,
216
+ lifecycleRules: [
217
+ {
218
+ expiration: cdk.Duration.days(expirationDays),
219
+ transitions: [
220
+ {
221
+ storageClass: StorageClass.INFREQUENT_ACCESS,
222
+ transitionAfter: cdk.Duration.days(infrequentAccessTransitionDays),
223
+ },
224
+ {
225
+ storageClass: StorageClass.GLACIER,
226
+ transitionAfter: cdk.Duration.days(glacierTransitionDays),
227
+ },
228
+ ],
229
+ },
230
+ ],
231
+ ...bucketProps,
232
+ });
233
+ // Add tags
234
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
235
+ if (service) {
236
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.SERVICE, service);
237
+ }
238
+ if (project) {
239
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.PROJECT, project);
240
+ }
241
+ // Create CloudFormation output if enabled
242
+ if (createOutput) {
243
+ new cdk.CfnOutput(this, "BucketNameOutput", {
244
+ description: outputDescription,
245
+ exportName,
246
+ value: this.bucket.bucketName,
247
+ });
248
+ }
249
+ }
250
+ }
251
+
252
+ function addDatadogLayers(lambdaFunction, options = {}) {
253
+ const datadogApiKeyArn = options?.datadogApiKeyArn;
254
+ const resolvedDatadogApiKeyArn = datadogApiKeyArn ||
255
+ process.env.DATADOG_API_KEY_ARN ||
256
+ process.env.CDK_ENV_DATADOG_API_KEY_ARN;
257
+ if (!resolvedDatadogApiKeyArn) {
258
+ return false;
259
+ }
260
+ // Define Datadog environment variables
261
+ const datadogEnvVars = {
262
+ DD_API_KEY_SECRET_ARN: resolvedDatadogApiKeyArn,
263
+ DD_ENHANCED_METRICS: "true",
264
+ DD_ENV: process.env.PROJECT_ENV || "",
265
+ DD_PROFILING_ENABLED: "false",
266
+ DD_SERVERLESS_APPSEC_ENABLED: "false",
267
+ DD_SERVICE: process.env.PROJECT_SERVICE || "",
268
+ DD_SITE: CDK$2.DATADOG.SITE,
269
+ DD_TAGS: `${CDK$2.TAG.SPONSOR}:${process.env.PROJECT_SPONSOR || ""}`,
270
+ DD_TRACE_OTEL_ENABLED: "false",
271
+ };
272
+ // Add environment variables only if they don't already exist
273
+ Object.entries(datadogEnvVars).forEach(([key, value]) => {
274
+ lambdaFunction.addEnvironment(key, value);
275
+ });
276
+ const datadogApiKeySecret = secretsmanager.Secret.fromSecretCompleteArn(lambdaFunction, "DatadogApiKey", resolvedDatadogApiKeyArn);
277
+ const datadogLambda = new DatadogLambda(lambdaFunction, "DatadogLambda", {
278
+ apiKeySecret: datadogApiKeySecret, // apiKeySecret auto-grants secret access to the added lambdas
279
+ nodeLayerVersion: CDK$2.DATADOG.LAYER.NODE,
280
+ extensionLayerVersion: CDK$2.DATADOG.LAYER.EXTENSION,
281
+ env: process.env.PROJECT_ENV,
282
+ service: process.env.PROJECT_SERVICE,
283
+ version: process.env.PROJECT_VERSION,
284
+ });
285
+ datadogLambda.addLambdaFunctions([lambdaFunction]);
286
+ return true;
287
+ }
288
+
289
+ function constructEnvName(name, opts) {
290
+ const env = opts?.env ?? process.env.PROJECT_ENV ?? "build";
291
+ const key = opts?.key ?? process.env.PROJECT_KEY ?? "project";
292
+ const nonce = opts?.nonce ?? process.env.PROJECT_NONCE ?? "cfe2"; // This default is intentionally short. It is not a special value but should not be changed.
293
+ return `${env}-${key}-${name}-${nonce}`;
294
+ }
295
+
296
+ function constructStackName(key) {
297
+ if (!key) {
298
+ return `cdk-${process.env.PROJECT_SPONSOR}-${process.env.PROJECT_KEY}-${process.env.PROJECT_ENV}-${process.env.PROJECT_NONCE}`;
299
+ }
300
+ else {
301
+ return `cdk-${process.env.PROJECT_SPONSOR}-${process.env.PROJECT_KEY}-${process.env.PROJECT_ENV}-${process.env.PROJECT_NONCE}-${key}`;
302
+ }
303
+ }
304
+
305
+ const CDK$1 = {
306
+ CREATION: {
307
+ CDK: "cdk",
308
+ },
309
+ ROLE: {
310
+ STACK: "stack",
311
+ },
312
+ TAG: {
313
+ BUILD_DATE: "buildDate",
314
+ BUILD_HEX: "buildHex",
315
+ BUILD_TIME: "buildTime",
316
+ COMMIT: "commit",
317
+ CREATION: "creation",
318
+ ENV: "env",
319
+ NONCE: "nonce",
320
+ PROJECT: "project",
321
+ ROLE: "role",
322
+ SERVICE: "service",
323
+ SPONSOR: "sponsor",
324
+ STACK: "stack",
325
+ VERSION: "version",
326
+ },
327
+ };
328
+ function constructTagger(construct, { name } = {}) {
329
+ const stackName = name || constructStackName();
330
+ const version = process.env.npm_package_version || process.env.PROJECT_VERSION || null;
331
+ if (process.env.PROJECT_COMMIT && process.env.PROJECT_COMMIT.length > 8) {
332
+ Tags.of(construct).add(CDK$1.TAG.BUILD_HEX, process.env.PROJECT_COMMIT.slice(0, 8));
333
+ }
334
+ Tags.of(construct).add(CDK$1.TAG.BUILD_DATE, new Date().toISOString());
335
+ Tags.of(construct).add(CDK$1.TAG.BUILD_TIME, Date.now().toString());
336
+ if (process.env.PROJECT_COMMIT)
337
+ Tags.of(construct).add(CDK$1.TAG.COMMIT, process.env.PROJECT_COMMIT);
338
+ Tags.of(construct).add(CDK$1.TAG.CREATION, CDK$1.CREATION.CDK);
339
+ if (process.env.PROJECT_ENV)
340
+ Tags.of(construct).add(CDK$1.TAG.ENV, process.env.PROJECT_ENV);
341
+ if (process.env.PROJECT_NONCE)
342
+ Tags.of(construct).add(CDK$1.TAG.NONCE, process.env.PROJECT_NONCE);
343
+ if (process.env.PROJECT_KEY)
344
+ Tags.of(construct).add(CDK$1.TAG.PROJECT, process.env.PROJECT_KEY);
345
+ Tags.of(construct).add(CDK$1.TAG.ROLE, CDK$1.ROLE.STACK);
346
+ if (process.env.PROJECT_SERVICE)
347
+ Tags.of(construct).add(CDK$1.TAG.SERVICE, process.env.PROJECT_SERVICE);
348
+ if (process.env.PROJECT_SPONSOR)
349
+ Tags.of(construct).add(CDK$1.TAG.SPONSOR, process.env.PROJECT_SPONSOR);
350
+ if (stackName)
351
+ Tags.of(construct).add(CDK$1.TAG.STACK, stackName);
352
+ if (version)
353
+ Tags.of(construct).add(CDK$1.TAG.VERSION, version);
354
+ return true;
355
+ }
356
+
357
+ function envHostname({ component, domain, env, subdomain, } = {}) {
358
+ const resolvedDomain = domain || process.env.CDK_ENV_DOMAIN || process.env.CDK_ENV_HOSTED_ZONE;
359
+ if (!resolvedDomain) {
360
+ throw new ConfigurationError("No hostname `domain` provided. Set CDK_ENV_DOMAIN or CDK_ENV_HOSTED_ZONE to use environment domain");
361
+ }
362
+ const resolvedComponent = component === "@" || component === "" ? undefined : component;
363
+ const resolvedSubdomain = subdomain || process.env.CDK_ENV_SUBDOMAIN;
364
+ const resolvedEnv = env || process.env.PROJECT_ENV;
365
+ const filteredEnv = resolvedEnv === CDK$2.ENV.PRODUCTION ? undefined : resolvedEnv;
366
+ const parts = [
367
+ resolvedComponent,
368
+ resolvedSubdomain,
369
+ filteredEnv,
370
+ resolvedDomain,
371
+ ].filter((part) => part);
372
+ return parts.join(".");
373
+ }
374
+
375
+ /**
376
+ * Extends the Datadog IAM role with additional permissions
377
+ *
378
+ * Checks for CDK_ENV_DATADOG_ROLE_ARN environment variable.
379
+ * If found, creates a custom policy with:
380
+ * - budgets:ViewBudget
381
+ * - logs:DescribeLogGroups
382
+ *
383
+ * @param scope - The construct scope
384
+ * @param options - Configuration options
385
+ * @returns The created Policy, or undefined if CDK_ENV_DATADOG_ROLE_ARN is not set
386
+ */
387
+ function extendDatadogRole(scope, options) {
388
+ const datadogRoleArn = process.env.CDK_ENV_DATADOG_ROLE_ARN;
389
+ // Early return if no Datadog role ARN is configured
390
+ if (!datadogRoleArn) {
391
+ return undefined;
392
+ }
393
+ const { id = "DatadogCustomPolicy", project, service = CDK$2.SERVICE.DATADOG, } = options || {};
394
+ // Lookup the Datadog role
395
+ const datadogRole = Role.fromRoleArn(scope, "DatadogRole", datadogRoleArn);
396
+ // Build policy statements
397
+ const statements = [
398
+ // Allow view budget
399
+ new PolicyStatement({
400
+ actions: ["budgets:ViewBudget"],
401
+ resources: ["*"],
402
+ }),
403
+ // Allow describe log groups
404
+ new PolicyStatement({
405
+ actions: ["logs:DescribeLogGroups"],
406
+ resources: ["*"],
407
+ }),
408
+ ];
409
+ // Create the custom policy
410
+ const datadogCustomPolicy = new Policy(scope, id, {
411
+ roles: [datadogRole],
412
+ statements,
413
+ });
414
+ // Add tags
415
+ cdk.Tags.of(datadogCustomPolicy).add(CDK$2.TAG.SERVICE, service);
416
+ cdk.Tags.of(datadogCustomPolicy).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
417
+ cdk.Tags.of(datadogCustomPolicy).add(CDK$2.TAG.VENDOR, CDK$2.VENDOR.DATADOG);
418
+ if (project) {
419
+ cdk.Tags.of(datadogCustomPolicy).add(CDK$2.TAG.PROJECT, project);
420
+ }
421
+ return datadogCustomPolicy;
422
+ }
423
+
424
+ /**
425
+ * Check if the current environment matches the given environment
426
+ */
427
+ function isEnv(env) {
428
+ return process.env.PROJECT_ENV === env;
429
+ }
430
+ /**
431
+ * Check if the current environment is production
432
+ */
433
+ function isProductionEnv() {
434
+ return isEnv(CDK$2.ENV.PRODUCTION);
435
+ }
436
+ /**
437
+ * Check if the current environment is sandbox
438
+ */
439
+ function isSandboxEnv() {
440
+ return isEnv(CDK$2.ENV.SANDBOX);
441
+ }
442
+
443
+ // In short the RFC 1035 standard for valid hostnames is:
444
+ // 1. Must be less than 253 characters
445
+ // 2. Must start with a letter
446
+ // 3. Must end with a letter or number
447
+ // 4. Can only contain letters, numbers, and hyphens
448
+ // 5. Last part of the domain must be at least 2 characters
449
+ function validPart$1(part) {
450
+ if (!part.match(/^[a-z]/))
451
+ return false;
452
+ if (!part.match(/[a-z0-9]$/))
453
+ return false;
454
+ return /^[a-zA-Z0-9-]+$/.test(part);
455
+ }
456
+ function isValidHostname$1(hostname) {
457
+ // Check hostname is a string
458
+ if (typeof hostname !== "string")
459
+ return false;
460
+ // Convert hostname to lowercase
461
+ const check = hostname.toString().toLowerCase();
462
+ // Check hostname is less than 253 characters
463
+ if (check.length > 253)
464
+ return false;
465
+ // Split on dots
466
+ const parts = check.split(".");
467
+ // Check each part is validPart
468
+ const validParts = parts.map(validPart$1);
469
+ // Confirm all parts are valid
470
+ if (!validParts.every((part) => part))
471
+ return false;
472
+ // Confirm last part is at least 2 characters
473
+ const lastPart = parts[parts.length - 1];
474
+ if (lastPart.length < 2)
475
+ return false;
476
+ // Confirm last part is all letters
477
+ if (!lastPart.match(/^[a-z]+$/))
478
+ return false;
479
+ // This is a valid hostname
480
+ return true;
481
+ }
482
+
483
+ function validPart(part) {
484
+ if (!part.match(/^[a-z]/))
485
+ return false;
486
+ if (!part.match(/[a-z0-9]$/))
487
+ return false;
488
+ return /^[a-zA-Z0-9-]+$/.test(part);
489
+ }
490
+ function isValidSubdomain(subdomain) {
491
+ // Check subdomain is a string
492
+ if (typeof subdomain !== "string")
493
+ return false;
494
+ // Special case for apex
495
+ if (subdomain === CDK$2.HOST.APEX)
496
+ return true;
497
+ // Convert subdomain to lowercase
498
+ const check = subdomain.toString().toLowerCase();
499
+ // Check subdomain is less than 250 characters
500
+ // We use 250 instead of 253 because we need to leave room for the dot top-level domain
501
+ if (check.length > 250)
502
+ return false;
503
+ // Split on dots
504
+ const parts = check.split(".");
505
+ // Check each part is validPart
506
+ const validParts = parts.map(validPart);
507
+ // Confirm all parts are valid
508
+ if (!validParts.every((part) => part))
509
+ return false;
510
+ // Do not care if last part is at least 2 characters
511
+ // Do not care if last part is all letters
512
+ // This is a valid subdomain
513
+ return true;
514
+ }
515
+
516
+ function jaypieLambdaEnv(options = {}) {
517
+ const { initialEnvironment = {} } = options;
518
+ // Start with empty environment - we'll only add valid values
519
+ let environment = {};
520
+ // First, add all valid string values from initialEnvironment
521
+ Object.entries(initialEnvironment).forEach(([key, value]) => {
522
+ if (typeof value === "string") {
523
+ environment[key] = value;
524
+ }
525
+ });
526
+ // Default environment values
527
+ const defaultEnvValues = {
528
+ AWS_LAMBDA_NODEJS_DISABLE_CALLBACK_WARNING: "true",
529
+ };
530
+ // Apply default environment values with user overrides
531
+ Object.entries(defaultEnvValues).forEach(([key, defaultValue]) => {
532
+ if (key in initialEnvironment) {
533
+ const userValue = initialEnvironment[key];
534
+ // If user passes a string, it's already added above
535
+ // If user passes non-string falsy value, omit the key
536
+ if (!userValue) {
537
+ delete environment[key];
538
+ }
539
+ // Ignore non-string truthy values (key not added)
540
+ }
541
+ else {
542
+ // No user override, use default value
543
+ environment[key] = defaultValue;
544
+ }
545
+ });
546
+ // Default environment variables from process.env if present
547
+ const defaultEnvVars = [
548
+ "DATADOG_API_KEY_ARN",
549
+ "LOG_LEVEL",
550
+ "MODULE_LOGGER",
551
+ "MODULE_LOG_LEVEL",
552
+ "PROJECT_CHAOS",
553
+ "PROJECT_COMMIT",
554
+ "PROJECT_ENV",
555
+ "PROJECT_KEY",
556
+ "PROJECT_SECRET",
557
+ "PROJECT_SERVICE",
558
+ "PROJECT_SPONSOR",
559
+ "PROJECT_VERSION",
560
+ ];
561
+ // Add default environment variables if they exist in process.env
562
+ defaultEnvVars.forEach((envVar) => {
563
+ if (process.env[envVar] && !environment[envVar]) {
564
+ environment[envVar] = process.env[envVar];
565
+ }
566
+ });
567
+ return environment;
568
+ }
569
+
570
+ function mergeDomain(subDomain, hostedZone) {
571
+ if (!hostedZone) {
572
+ throw new ConfigurationError("hostedZone is required");
573
+ }
574
+ if (!subDomain) {
575
+ // Return hostedZone if subDomain is not passed
576
+ // Pass CDK.HOST.APEX to explicitly indicate apex domain
577
+ return hostedZone;
578
+ }
579
+ if (subDomain === CDK$2.HOST.APEX) {
580
+ return hostedZone;
581
+ }
582
+ return `${subDomain}.${hostedZone}`;
583
+ }
584
+
585
+ const DEFAULT_FUNCTION_NAME$1 = "DatadogForwarderFunction";
586
+ // Cache to store resolved functions
587
+ // Using nested structure to support multiple functions per scope with automatic GC
588
+ const functionCache = new WeakMap();
589
+ function resolveDatadogForwarderFunction(scope, options) {
590
+ const { import: importValue, name } = options || {};
591
+ const functionName = name || DEFAULT_FUNCTION_NAME$1;
592
+ const importKey = importValue || CDK$2.IMPORT.DATADOG_LOG_FORWARDER;
593
+ // Create a cache key based on name and import
594
+ const cacheKey = `${functionName}:${importKey}`;
595
+ // Get or create scope cache
596
+ let scopeCache = functionCache.get(scope);
597
+ if (!scopeCache) {
598
+ scopeCache = new Map();
599
+ functionCache.set(scope, scopeCache);
600
+ }
601
+ // Return cached function if it exists
602
+ const cachedFunction = scopeCache.get(cacheKey);
603
+ if (cachedFunction) {
604
+ return cachedFunction;
605
+ }
606
+ // Create and cache the function
607
+ const func = lambda.Function.fromFunctionArn(scope, functionName, cdk.Fn.importValue(importKey));
608
+ scopeCache.set(cacheKey, func);
609
+ return func;
610
+ }
611
+
612
+ function resolveDatadogLayers(scope, options = {}) {
613
+ const { datadogApiKeyArn, uniqueId } = options;
614
+ let resolvedRegion = Stack.of(scope).region || "us-east-1";
615
+ // Resolve the Datadog API key ARN from multiple sources
616
+ const resolvedDatadogApiKeyArn = datadogApiKeyArn ||
617
+ process.env.DATADOG_API_KEY_ARN ||
618
+ process.env.CDK_ENV_DATADOG_API_KEY_ARN;
619
+ // Return null if no API key is found
620
+ if (!resolvedDatadogApiKeyArn) {
621
+ return undefined;
622
+ }
623
+ const layerIdSuffix = uniqueId || process.env.PROJECT_NONCE || Date.now().toString();
624
+ // Create Datadog Node.js layer
625
+ const datadogNodeLayer = lambda.LayerVersion.fromLayerVersionArn(scope, `DatadogNodeLayer-${layerIdSuffix}`, `arn:aws:lambda:${resolvedRegion}:464622532012:layer:Datadog-Node20-x:${CDK$2.DATADOG.LAYER.NODE}`);
626
+ // Create Datadog Extension layer
627
+ const datadogExtensionLayer = lambda.LayerVersion.fromLayerVersionArn(scope, `DatadogExtensionLayer-${layerIdSuffix}`, `arn:aws:lambda:${resolvedRegion}:464622532012:layer:Datadog-Extension:${CDK$2.DATADOG.LAYER.EXTENSION}`);
628
+ return [datadogNodeLayer, datadogExtensionLayer];
629
+ }
630
+
631
+ const DEFAULT_FUNCTION_NAME = "DatadogForwarderFunction";
632
+ // Cache to store resolved logging destinations
633
+ // Using nested structure to support multiple destinations per scope with automatic GC
634
+ const destinationCache = new WeakMap();
635
+ function resolveDatadogLoggingDestination(scope, options) {
636
+ const { import: importValue, name } = options || {};
637
+ // Create a cache key based on name and import (same as forwarder function)
638
+ const functionName = name || DEFAULT_FUNCTION_NAME;
639
+ const importKey = importValue || CDK$2.IMPORT.DATADOG_LOG_FORWARDER;
640
+ const cacheKey = `${functionName}:${importKey}`;
641
+ // Get or create scope cache
642
+ let scopeCache = destinationCache.get(scope);
643
+ if (!scopeCache) {
644
+ scopeCache = new Map();
645
+ destinationCache.set(scope, scopeCache);
646
+ }
647
+ // Return cached destination if it exists
648
+ const cachedDestination = scopeCache.get(cacheKey);
649
+ if (cachedDestination) {
650
+ return cachedDestination;
651
+ }
652
+ // Resolve the Datadog forwarder function
653
+ const datadogForwarderFunction = resolveDatadogForwarderFunction(scope, options);
654
+ // Create and cache the logging destination
655
+ const datadogLoggingDestination = new logDestinations.LambdaDestination(datadogForwarderFunction);
656
+ scopeCache.set(cacheKey, datadogLoggingDestination);
657
+ return datadogLoggingDestination;
658
+ }
659
+
660
+ /**
661
+ * Resolves environment input to a plain object.
662
+ *
663
+ * When environment is an object (legacy syntax), returns it as-is.
664
+ * When environment is an array:
665
+ * - Strings are treated as keys to lookup in process.env
666
+ * - Objects have their key-value pairs merged in
667
+ *
668
+ * @example
669
+ * // Legacy object syntax
670
+ * resolveEnvironment({ FOO: "bar" })
671
+ * // => { FOO: "bar" }
672
+ *
673
+ * @example
674
+ * // Array syntax with process.env lookup
675
+ * // Given process.env.MY_VAR = "hello"
676
+ * resolveEnvironment(["MY_VAR"])
677
+ * // => { MY_VAR: "hello" }
678
+ *
679
+ * @example
680
+ * // Array syntax with objects
681
+ * resolveEnvironment([{ FOO: "bar", BAZ: "qux" }])
682
+ * // => { FOO: "bar", BAZ: "qux" }
683
+ *
684
+ * @example
685
+ * // Mixed array syntax
686
+ * // Given process.env.MY_VAR = "hello"
687
+ * resolveEnvironment(["MY_VAR", { FOO: "bar" }])
688
+ * // => { MY_VAR: "hello", FOO: "bar" }
689
+ */
690
+ function resolveEnvironment(environment, env = process.env) {
691
+ if (!environment) {
692
+ return {};
693
+ }
694
+ // Legacy object syntax - return as-is
695
+ if (!Array.isArray(environment)) {
696
+ return environment;
697
+ }
698
+ // Array syntax - process each item
699
+ return environment.reduce((acc, item) => {
700
+ if (typeof item === "string") {
701
+ // String: lookup in process.env
702
+ const value = env[item];
703
+ if (value !== undefined) {
704
+ return {
705
+ ...acc,
706
+ [item]: value,
707
+ };
708
+ }
709
+ // Skip if not found in process.env
710
+ return acc;
711
+ }
712
+ // Object: merge key-value pairs
713
+ return {
714
+ ...acc,
715
+ ...item,
716
+ };
717
+ }, {});
718
+ }
719
+
720
+ function resolveHostedZone(scope, { name = "HostedZone", zone = process.env.CDK_ENV_HOSTED_ZONE, } = {}) {
721
+ if (!zone) {
722
+ throw new ConfigurationError("No `zone` provided. Set CDK_ENV_HOSTED_ZONE to use environment zone");
723
+ }
724
+ if (typeof zone === "string") {
725
+ return route53.HostedZone.fromLookup(scope, name, {
726
+ domainName: zone,
727
+ });
728
+ }
729
+ return zone;
730
+ }
731
+
732
+ const resolveParamsAndSecrets = ({ paramsAndSecrets, options, } = {}) => {
733
+ if (paramsAndSecrets === false) {
734
+ return;
735
+ }
736
+ let resolvedParamsAndSecrets;
737
+ if (paramsAndSecrets instanceof lambda.ParamsAndSecretsLayerVersion) {
738
+ resolvedParamsAndSecrets = paramsAndSecrets;
739
+ }
740
+ else {
741
+ const resolvedOptions = options || {};
742
+ resolvedParamsAndSecrets = lambda.ParamsAndSecretsLayerVersion.fromVersion(lambda.ParamsAndSecretsVersions.V1_0_103, {
743
+ cacheSize: resolvedOptions.cacheSize,
744
+ logLevel: resolvedOptions.logLevel || lambda.ParamsAndSecretsLogLevel.WARN,
745
+ parameterStoreTtl: resolvedOptions.parameterStoreTtl,
746
+ secretsManagerTtl: resolvedOptions.secretsManagerTtl,
747
+ });
748
+ }
749
+ return resolvedParamsAndSecrets;
750
+ };
751
+
752
+ // It is a consumer if the environment is ephemeral
753
+ function checkEnvIsConsumer(env = process.env) {
754
+ return (env.PROJECT_ENV === CDK$2.ENV.PERSONAL ||
755
+ !!env.CDK_ENV_PERSONAL ||
756
+ /** @deprecated */ env.PROJECT_ENV === "ephemeral" ||
757
+ /** @deprecated */ !!env.CDK_ENV_EPHEMERAL);
758
+ }
759
+ function checkEnvIsProvider(env = process.env) {
760
+ return env.PROJECT_ENV === CDK$2.ENV.SANDBOX;
761
+ }
762
+ function cleanName(name) {
763
+ return name.replace(/[^a-zA-Z0-9:-]/g, "");
764
+ }
765
+ function exportEnvName(name, env = process.env) {
766
+ let rawName;
767
+ if (checkEnvIsProvider(env)) {
768
+ rawName = `env-${env.PROJECT_ENV}-${env.PROJECT_KEY}-${name}`;
769
+ // Clean the entire name to only allow alphanumeric, colons, and hyphens
770
+ return cleanName(rawName);
771
+ }
772
+ else {
773
+ if (checkEnvIsConsumer(env)) {
774
+ rawName = `env-${CDK$2.ENV.SANDBOX}-${env.PROJECT_KEY}-${name}`;
775
+ }
776
+ else {
777
+ rawName = `env-${env.PROJECT_ENV}-${env.PROJECT_KEY}-${name}`;
778
+ }
779
+ }
780
+ return cleanName(rawName);
781
+ }
782
+ class JaypieEnvSecret extends Construct {
783
+ constructor(scope, idOrEnvKey, props) {
784
+ // Check if idOrEnvKey should be treated as envKey:
785
+ // - No props provided OR props.envKey is not set
786
+ // - AND idOrEnvKey exists as a non-empty string in process.env
787
+ const treatAsEnvKey = (!props || props.envKey === undefined) &&
788
+ typeof process.env[idOrEnvKey] === "string" &&
789
+ process.env[idOrEnvKey] !== "";
790
+ const id = treatAsEnvKey ? `EnvSecret_${idOrEnvKey}` : idOrEnvKey;
791
+ super(scope, id);
792
+ const { consumer = checkEnvIsConsumer(), envKey: envKeyProp, export: exportParam, generateSecretString, provider = checkEnvIsProvider(), roleTag, vendorTag, value, } = props || {};
793
+ const envKey = treatAsEnvKey ? idOrEnvKey : envKeyProp;
794
+ this._envKey = envKey;
795
+ let exportName;
796
+ if (!exportParam) {
797
+ exportName = exportEnvName(id);
798
+ }
799
+ else {
800
+ exportName = cleanName(exportParam);
801
+ }
802
+ if (consumer) {
803
+ const secretName = Fn.importValue(exportName);
804
+ this._secret = secretsmanager.Secret.fromSecretNameV2(this, id, secretName);
805
+ // Add CfnOutput for consumer secrets
806
+ new CfnOutput(this, `ConsumedName`, {
807
+ value: this._secret.secretName,
808
+ });
809
+ }
810
+ else {
811
+ const secretValue = envKey && process.env[envKey] ? process.env[envKey] : value;
812
+ const secretProps = {
813
+ generateSecretString,
814
+ secretStringValue: !generateSecretString && secretValue
815
+ ? SecretValue.unsafePlainText(secretValue)
816
+ : undefined,
817
+ };
818
+ this._secret = new secretsmanager.Secret(this, id, secretProps);
819
+ if (roleTag) {
820
+ Tags.of(this._secret).add(CDK$2.TAG.ROLE, roleTag);
821
+ }
822
+ if (vendorTag) {
823
+ Tags.of(this._secret).add(CDK$2.TAG.VENDOR, vendorTag);
824
+ }
825
+ if (provider) {
826
+ new CfnOutput(this, `ProvidedName`, {
827
+ value: this._secret.secretName,
828
+ exportName,
829
+ });
830
+ }
831
+ else {
832
+ new CfnOutput(this, `CreatedName`, {
833
+ value: this._secret.secretName,
834
+ });
835
+ }
836
+ }
837
+ }
838
+ // IResource implementation
839
+ get stack() {
840
+ return Stack.of(this);
841
+ }
842
+ get env() {
843
+ return {
844
+ account: Stack.of(this).account,
845
+ region: Stack.of(this).region,
846
+ };
847
+ }
848
+ applyRemovalPolicy(policy) {
849
+ this._secret.applyRemovalPolicy(policy);
850
+ }
851
+ // ISecret implementation
852
+ get secretArn() {
853
+ return this._secret.secretArn;
854
+ }
855
+ get secretName() {
856
+ return this._secret.secretName;
857
+ }
858
+ get secretFullArn() {
859
+ return this._secret.secretFullArn;
860
+ }
861
+ get encryptionKey() {
862
+ return this._secret.encryptionKey;
863
+ }
864
+ get secretValue() {
865
+ return this._secret.secretValue;
866
+ }
867
+ secretValueFromJson(key) {
868
+ return this._secret.secretValueFromJson(key);
869
+ }
870
+ grantRead(grantee, versionStages) {
871
+ return this._secret.grantRead(grantee, versionStages);
872
+ }
873
+ grantWrite(grantee) {
874
+ return this._secret.grantWrite(grantee);
875
+ }
876
+ addRotationSchedule(id, options) {
877
+ return this._secret.addRotationSchedule(id, options);
878
+ }
879
+ addToResourcePolicy(statement) {
880
+ return this._secret.addToResourcePolicy(statement);
881
+ }
882
+ denyAccountRootDelete() {
883
+ this._secret.denyAccountRootDelete();
884
+ }
885
+ attach(target) {
886
+ return this._secret.attach(target);
887
+ }
888
+ cfnDynamicReferenceKey(options) {
889
+ return this._secret.cfnDynamicReferenceKey(options);
890
+ }
891
+ get envKey() {
892
+ return this._envKey;
893
+ }
894
+ }
895
+
896
+ /**
897
+ * Cache for secrets by scope to avoid creating duplicates.
898
+ * Uses WeakMap to allow garbage collection when scopes are no longer referenced.
899
+ */
900
+ const secretsByScope = new WeakMap();
901
+ /**
902
+ * Gets or creates the secrets cache for a given scope.
903
+ */
904
+ function getSecretsCache(scope) {
905
+ let cache = secretsByScope.get(scope);
906
+ if (!cache) {
907
+ cache = new Map();
908
+ secretsByScope.set(scope, cache);
909
+ }
910
+ return cache;
911
+ }
912
+ /**
913
+ * Gets an existing secret from the cache or creates a new one.
914
+ * This ensures that multiple constructs within the same scope share secrets.
915
+ */
916
+ function getOrCreateSecret(scope, envKey, props) {
917
+ const cache = getSecretsCache(scope);
918
+ const existingSecret = cache.get(envKey);
919
+ if (existingSecret) {
920
+ return existingSecret;
921
+ }
922
+ // Create new secret - JaypieEnvSecret's smart constructor handles envKey detection
923
+ const secret = new JaypieEnvSecret(scope, envKey, {
924
+ ...props,
925
+ envKey,
926
+ });
927
+ cache.set(envKey, secret);
928
+ return secret;
929
+ }
930
+ /**
931
+ * Resolves secrets input to an array of JaypieEnvSecret instances.
932
+ *
933
+ * When an item is already a JaypieEnvSecret, it's passed through as-is.
934
+ * When an item is a string, a JaypieEnvSecret is created (or reused from cache)
935
+ * with the string as the envKey.
936
+ *
937
+ * Secrets are cached per scope to avoid creating duplicate secrets when
938
+ * multiple constructs in the same scope reference the same secret.
939
+ *
940
+ * @example
941
+ * // JaypieEnvSecret instances pass through
942
+ * const secret = new JaypieEnvSecret(scope, "MySecret", { envKey: "MY_KEY" });
943
+ * resolveSecrets(scope, [secret])
944
+ * // => [secret]
945
+ *
946
+ * @example
947
+ * // Strings create JaypieEnvSecret instances
948
+ * resolveSecrets(scope, ["AUTH0_SECRET", "MONGODB_URI"])
949
+ * // => [JaypieEnvSecret(envKey: "AUTH0_SECRET"), JaypieEnvSecret(envKey: "MONGODB_URI")]
950
+ *
951
+ * @example
952
+ * // Mixed input
953
+ * const existingSecret = new JaypieEnvSecret(scope, "Existing", { envKey: "EXISTING" });
954
+ * resolveSecrets(scope, [existingSecret, "NEW_SECRET"])
955
+ * // => [existingSecret, JaypieEnvSecret(envKey: "NEW_SECRET")]
956
+ *
957
+ * @example
958
+ * // Secrets are shared across calls with the same scope
959
+ * const secrets1 = resolveSecrets(scope, ["SHARED_SECRET"]);
960
+ * const secrets2 = resolveSecrets(scope, ["SHARED_SECRET"]);
961
+ * // secrets1[0] === secrets2[0] (same instance)
962
+ */
963
+ function resolveSecrets(scope, secrets) {
964
+ if (!secrets || secrets.length === 0) {
965
+ return [];
966
+ }
967
+ return secrets.map((item) => {
968
+ if (typeof item === "string") {
969
+ return getOrCreateSecret(scope, item);
970
+ }
971
+ // Already a JaypieEnvSecret instance
972
+ return item;
973
+ });
974
+ }
975
+ /**
976
+ * Clears the secrets cache for a given scope.
977
+ * Primarily useful for testing.
978
+ */
979
+ function clearSecretsCache(scope) {
980
+ secretsByScope.delete(scope);
981
+ }
982
+ /**
983
+ * Clears all secrets caches.
984
+ * Primarily useful for testing.
985
+ */
986
+ function clearAllSecretsCaches() {
987
+ // WeakMap doesn't have a clear() method, so we create a new one
988
+ // This relies on the module being reloaded or the function being called
989
+ // between test runs. For testing, use clearSecretsCache(scope) instead.
990
+ }
991
+
992
+ class JaypieApiGateway extends Construct {
993
+ constructor(scope, id, props) {
994
+ super(scope, id);
995
+ const { certificate = true, handler, host: propsHost, name, roleTag = CDK$2.ROLE.API, zone: propsZone, } = props;
996
+ // Determine zone from props or environment
997
+ let zone = propsZone;
998
+ if (!zone && process.env.CDK_ENV_API_HOSTED_ZONE) {
999
+ zone = process.env.CDK_ENV_API_HOSTED_ZONE;
1000
+ }
1001
+ // Determine host from props or environment
1002
+ let host = propsHost;
1003
+ if (!host) {
1004
+ if (process.env.CDK_ENV_API_HOST_NAME) {
1005
+ host = process.env.CDK_ENV_API_HOST_NAME;
1006
+ }
1007
+ else if (process.env.CDK_ENV_API_SUBDOMAIN &&
1008
+ process.env.CDK_ENV_API_HOSTED_ZONE) {
1009
+ host = mergeDomain(process.env.CDK_ENV_API_SUBDOMAIN, process.env.CDK_ENV_API_HOSTED_ZONE);
1010
+ }
1011
+ }
1012
+ const apiGatewayName = name || constructEnvName("ApiGateway");
1013
+ const certificateName = constructEnvName("Certificate");
1014
+ const apiDomainName = constructEnvName("ApiDomainName");
1015
+ let hostedZone;
1016
+ let certificateToUse;
1017
+ if (host && zone) {
1018
+ hostedZone = resolveHostedZone(this, { zone });
1019
+ if (certificate === true) {
1020
+ certificateToUse = new acm.Certificate(this, certificateName, {
1021
+ domainName: host,
1022
+ validation: acm.CertificateValidation.fromDns(hostedZone),
1023
+ });
1024
+ Tags.of(certificateToUse).add(CDK$2.TAG.ROLE, CDK$2.ROLE.HOSTING);
1025
+ }
1026
+ else if (typeof certificate === "object") {
1027
+ certificateToUse = certificate;
1028
+ }
1029
+ this._certificate = certificateToUse;
1030
+ this._host = host;
1031
+ }
1032
+ const {
1033
+ // * `...lambdaRestApiProps` cannot be moved to the first const destructuring because it needs to exclude the custom properties first.
1034
+ // Ignore the variables we already assigned to other properties
1035
+ /* eslint-disable @typescript-eslint/no-unused-vars */
1036
+ certificate: _certificate, host: _host, name: _name, roleTag: _roleTag, zone: _zone, handler: _handler,
1037
+ /* eslint-enable @typescript-eslint/no-unused-vars */
1038
+ ...lambdaRestApiProps } = props;
1039
+ this._api = new apiGateway.LambdaRestApi(this, apiGatewayName, {
1040
+ handler,
1041
+ ...lambdaRestApiProps,
1042
+ });
1043
+ Tags.of(this._api).add(CDK$2.TAG.ROLE, roleTag);
1044
+ if (host && certificateToUse && hostedZone) {
1045
+ this._domainName = this._api.addDomainName(apiDomainName, {
1046
+ domainName: host,
1047
+ certificate: certificateToUse,
1048
+ });
1049
+ Tags.of(this._domainName).add(CDK$2.TAG.ROLE, roleTag);
1050
+ const record = new route53.ARecord(this, "AliasRecord", {
1051
+ recordName: host,
1052
+ target: route53.RecordTarget.fromAlias(new route53Targets.ApiGatewayDomain(this._domainName)),
1053
+ zone: hostedZone,
1054
+ });
1055
+ Tags.of(record).add(CDK$2.TAG.ROLE, CDK$2.ROLE.NETWORKING);
1056
+ }
1057
+ }
1058
+ get api() {
1059
+ return this._api;
1060
+ }
1061
+ get url() {
1062
+ return this._api.url;
1063
+ }
1064
+ get certificateArn() {
1065
+ return this._certificate?.certificateArn;
1066
+ }
1067
+ get domainName() {
1068
+ return this._domainName?.domainName;
1069
+ }
1070
+ get host() {
1071
+ return this._host;
1072
+ }
1073
+ get restApiId() {
1074
+ return this._api.restApiId;
1075
+ }
1076
+ get restApiName() {
1077
+ return this._api.restApiName;
1078
+ }
1079
+ get restApiRootResourceId() {
1080
+ return this._api.restApiRootResourceId;
1081
+ }
1082
+ get deploymentStage() {
1083
+ return this._api.deploymentStage;
1084
+ }
1085
+ get domainNameAliasDomainName() {
1086
+ return this._domainName?.domainNameAliasDomainName;
1087
+ }
1088
+ get domainNameAliasHostedZoneId() {
1089
+ return this._domainName?.domainNameAliasHostedZoneId;
1090
+ }
1091
+ get root() {
1092
+ return this._api.root;
1093
+ }
1094
+ get env() {
1095
+ return {
1096
+ account: Stack.of(this).account,
1097
+ region: Stack.of(this).region,
1098
+ };
1099
+ }
1100
+ get stack() {
1101
+ return this._api.stack;
1102
+ }
1103
+ arnForExecuteApi(method, path, stage) {
1104
+ return this._api.arnForExecuteApi(method, path, stage);
1105
+ }
1106
+ metric(metricName, props) {
1107
+ return this._api.metric(metricName, props);
1108
+ }
1109
+ metricCacheHitCount(props) {
1110
+ return this._api.metricCacheHitCount(props);
1111
+ }
1112
+ metricCacheMissCount(props) {
1113
+ return this._api.metricCacheMissCount(props);
1114
+ }
1115
+ metricClientError(props) {
1116
+ return this._api.metricClientError(props);
1117
+ }
1118
+ metricCount(props) {
1119
+ return this._api.metricCount(props);
1120
+ }
1121
+ metricIntegrationLatency(props) {
1122
+ return this._api.metricIntegrationLatency(props);
1123
+ }
1124
+ metricLatency(props) {
1125
+ return this._api.metricLatency(props);
1126
+ }
1127
+ metricServerError(props) {
1128
+ return this._api.metricServerError(props);
1129
+ }
1130
+ applyRemovalPolicy(policy) {
1131
+ this._api.applyRemovalPolicy(policy);
1132
+ }
1133
+ get restApiRef() {
1134
+ return {
1135
+ restApiId: this._api.restApiId,
1136
+ };
1137
+ }
1138
+ }
1139
+
1140
+ class JaypieStack extends Stack {
1141
+ constructor(scope, id, props = {}) {
1142
+ const { key, ...stackProps } = props;
1143
+ // Handle stackName
1144
+ if (!stackProps.stackName) {
1145
+ stackProps.stackName = constructStackName(key);
1146
+ }
1147
+ // Handle env
1148
+ stackProps.env = {
1149
+ account: process.env.CDK_DEFAULT_ACCOUNT,
1150
+ region: process.env.CDK_DEFAULT_REGION,
1151
+ ...stackProps.env,
1152
+ };
1153
+ super(scope, id, stackProps);
1154
+ // Apply tags
1155
+ constructTagger(this, { name: stackProps.stackName });
1156
+ }
1157
+ }
1158
+
1159
+ class JaypieAppStack extends JaypieStack {
1160
+ constructor(scope, id, props = {}) {
1161
+ const { key = "app", ...stackProps } = props;
1162
+ // Handle stackName
1163
+ if (!stackProps.stackName) {
1164
+ stackProps.stackName = constructStackName(key);
1165
+ }
1166
+ super(scope, id, { key, ...stackProps });
1167
+ }
1168
+ }
1169
+
1170
+ class JaypieLambda extends Construct {
1171
+ constructor(scope, id, props) {
1172
+ super(scope, id);
1173
+ const { allowAllOutbound, allowPublicSubnet, architecture = lambda.Architecture.X86_64, code, datadogApiKeyArn, deadLetterQueue, deadLetterQueueEnabled, deadLetterTopic, description, environment: environmentInput, envSecrets = {}, ephemeralStorageSize, filesystem, handler = "index.handler", initialPolicy, layers = [], logGroup, logRetention = CDK$2.LAMBDA.LOG_RETENTION, maxEventAge, memorySize = CDK$2.LAMBDA.MEMORY_SIZE, paramsAndSecrets, paramsAndSecretsOptions, profiling, profilingGroup, provisionedConcurrentExecutions, reservedConcurrentExecutions, retryAttempts, roleTag = CDK$2.ROLE.PROCESSING, runtime = new lambda.Runtime("nodejs24.x", lambda.RuntimeFamily.NODEJS, {
1174
+ supportsInlineCode: true,
1175
+ }), runtimeManagementMode, secrets: secretsInput = [], securityGroups, timeout = Duration.seconds(CDK$2.DURATION.LAMBDA_WORKER), tracing, vendorTag, vpc, vpcSubnets, } = props;
1176
+ // Resolve environment from array or object syntax
1177
+ const initialEnvironment = resolveEnvironment(environmentInput);
1178
+ // Get base environment with defaults
1179
+ const environment = jaypieLambdaEnv({ initialEnvironment });
1180
+ // Resolve secrets from mixed array (strings and JaypieEnvSecret instances)
1181
+ const secrets = resolveSecrets(scope, secretsInput);
1182
+ const codeAsset = typeof code === "string" ? lambda.Code.fromAsset(code) : code;
1183
+ // Create a working copy of layers
1184
+ const resolvedLayers = [...layers];
1185
+ // Process secrets environment variables
1186
+ const secretsEnvironment = Object.entries(envSecrets).reduce((acc, [key, secret]) => ({
1187
+ ...acc,
1188
+ [`SECRET_${key}`]: secret.secretName,
1189
+ }), {});
1190
+ // Process JaypieEnvSecret array
1191
+ const jaypieSecretsEnvironment = secrets.reduce((acc, secret) => {
1192
+ if (secret.envKey) {
1193
+ return {
1194
+ ...acc,
1195
+ [`SECRET_${secret.envKey}`]: secret.secretName,
1196
+ };
1197
+ }
1198
+ return acc;
1199
+ }, {});
1200
+ // Add ParamsAndSecrets layer if configured
1201
+ const resolvedParamsAndSecrets = resolveParamsAndSecrets({
1202
+ paramsAndSecrets,
1203
+ options: paramsAndSecretsOptions,
1204
+ });
1205
+ // Create LogGroup if not provided
1206
+ const resolvedLogGroup = logGroup ??
1207
+ new logs.LogGroup(this, "LogGroup", {
1208
+ retention: logRetention,
1209
+ removalPolicy: RemovalPolicy.DESTROY,
1210
+ });
1211
+ // Create Lambda Function
1212
+ this._lambda = new lambda.Function(this, "Function", {
1213
+ allowAllOutbound,
1214
+ allowPublicSubnet,
1215
+ architecture,
1216
+ code: codeAsset,
1217
+ deadLetterQueue,
1218
+ deadLetterQueueEnabled,
1219
+ deadLetterTopic,
1220
+ description,
1221
+ environment: {
1222
+ ...environment,
1223
+ ...secretsEnvironment,
1224
+ ...jaypieSecretsEnvironment,
1225
+ },
1226
+ ephemeralStorageSize,
1227
+ filesystem,
1228
+ handler,
1229
+ initialPolicy,
1230
+ layers: resolvedLayers,
1231
+ logGroup: resolvedLogGroup,
1232
+ maxEventAge,
1233
+ memorySize,
1234
+ paramsAndSecrets: resolvedParamsAndSecrets,
1235
+ profiling,
1236
+ profilingGroup,
1237
+ reservedConcurrentExecutions,
1238
+ retryAttempts,
1239
+ runtime,
1240
+ runtimeManagementMode,
1241
+ securityGroups,
1242
+ timeout: typeof timeout === "number" ? Duration.seconds(timeout) : timeout,
1243
+ tracing,
1244
+ vpc,
1245
+ vpcSubnets,
1246
+ // Enable auto-publishing of versions when using provisioned concurrency
1247
+ currentVersionOptions: provisionedConcurrentExecutions !== undefined
1248
+ ? {
1249
+ removalPolicy: RemovalPolicy.RETAIN,
1250
+ description: "Auto-published version for provisioned concurrency",
1251
+ // Don't set provisioned concurrency here - it will be set on the alias
1252
+ }
1253
+ : undefined,
1254
+ });
1255
+ addDatadogLayers(this._lambda, { datadogApiKeyArn });
1256
+ // Grant secret read permissions
1257
+ Object.values(envSecrets).forEach((secret) => {
1258
+ secret.grantRead(this._lambda);
1259
+ });
1260
+ // Grant read permissions for JaypieEnvSecrets
1261
+ secrets.forEach((secret) => {
1262
+ secret.grantRead(this._lambda);
1263
+ });
1264
+ // Configure provisioned concurrency if specified
1265
+ if (provisionedConcurrentExecutions !== undefined) {
1266
+ // Use currentVersion which is auto-published with proper configuration
1267
+ const version = this._lambda.currentVersion;
1268
+ // Create alias for provisioned concurrency
1269
+ this._provisioned = new lambda.Alias(this, "ProvisionedAlias", {
1270
+ aliasName: "provisioned",
1271
+ version,
1272
+ provisionedConcurrentExecutions,
1273
+ });
1274
+ // Add explicit dependencies to ensure proper creation order
1275
+ this._provisioned.node.addDependency(version);
1276
+ }
1277
+ if (roleTag) {
1278
+ Tags.of(this._lambda).add(CDK$2.TAG.ROLE, roleTag);
1279
+ }
1280
+ if (vendorTag) {
1281
+ Tags.of(this._lambda).add(CDK$2.TAG.VENDOR, vendorTag);
1282
+ }
1283
+ // Assign _reference based on provisioned state
1284
+ this._reference =
1285
+ this._provisioned !== undefined ? this._provisioned : this._lambda;
1286
+ }
1287
+ // Public accessors
1288
+ get lambda() {
1289
+ return this._lambda;
1290
+ }
1291
+ get provisioned() {
1292
+ return this._provisioned;
1293
+ }
1294
+ get reference() {
1295
+ return this._reference;
1296
+ }
1297
+ // IFunction implementation
1298
+ get functionArn() {
1299
+ return this._reference.functionArn;
1300
+ }
1301
+ get functionName() {
1302
+ return this._reference.functionName;
1303
+ }
1304
+ get grantPrincipal() {
1305
+ return this._reference.grantPrincipal;
1306
+ }
1307
+ get role() {
1308
+ return this._reference.role;
1309
+ }
1310
+ get architecture() {
1311
+ return this._reference.architecture;
1312
+ }
1313
+ get connections() {
1314
+ return this._reference.connections;
1315
+ }
1316
+ get isBoundToVpc() {
1317
+ return this._reference.isBoundToVpc;
1318
+ }
1319
+ get latestVersion() {
1320
+ return this._reference.latestVersion;
1321
+ }
1322
+ get permissionsNode() {
1323
+ return this._reference.permissionsNode;
1324
+ }
1325
+ get resourceArnsForGrantInvoke() {
1326
+ return this._reference.resourceArnsForGrantInvoke;
1327
+ }
1328
+ get functionRef() {
1329
+ return {
1330
+ functionArn: this._reference.functionArn,
1331
+ functionName: this._reference.functionName,
1332
+ };
1333
+ }
1334
+ addEventSource(source) {
1335
+ this._reference.addEventSource(source);
1336
+ }
1337
+ addEventSourceMapping(id, options) {
1338
+ return this._reference.addEventSourceMapping(id, options);
1339
+ }
1340
+ addFunctionUrl(options) {
1341
+ return this._reference.addFunctionUrl(options);
1342
+ }
1343
+ addPermission(id, permission) {
1344
+ this._reference.addPermission(id, permission);
1345
+ }
1346
+ addToRolePolicy(statement) {
1347
+ this._reference.addToRolePolicy(statement);
1348
+ }
1349
+ configureAsyncInvoke(options) {
1350
+ this._reference.configureAsyncInvoke(options);
1351
+ }
1352
+ grantInvoke(grantee) {
1353
+ return this._reference.grantInvoke(grantee);
1354
+ }
1355
+ grantInvokeCompositePrincipal(compositePrincipal) {
1356
+ return this._reference.grantInvokeCompositePrincipal(compositePrincipal);
1357
+ }
1358
+ grantInvokeUrl(grantee) {
1359
+ return this._reference.grantInvokeUrl(grantee);
1360
+ }
1361
+ grantInvokeLatestVersion(grantee) {
1362
+ return this._reference.grantInvokeLatestVersion(grantee);
1363
+ }
1364
+ grantInvokeVersion(grantee, version) {
1365
+ return this._reference.grantInvokeVersion(grantee, version);
1366
+ }
1367
+ metric(metricName, props) {
1368
+ return this._reference.metric(metricName, props);
1369
+ }
1370
+ metricDuration(props) {
1371
+ return this._reference.metricDuration(props);
1372
+ }
1373
+ metricErrors(props) {
1374
+ return this._reference.metricErrors(props);
1375
+ }
1376
+ metricInvocations(props) {
1377
+ return this._reference.metricInvocations(props);
1378
+ }
1379
+ metricThrottles(props) {
1380
+ return this._reference.metricThrottles(props);
1381
+ }
1382
+ get env() {
1383
+ return {
1384
+ account: Stack.of(this).account,
1385
+ region: Stack.of(this).region,
1386
+ };
1387
+ }
1388
+ get stack() {
1389
+ return this._reference.stack;
1390
+ }
1391
+ applyRemovalPolicy(policy) {
1392
+ this._reference.applyRemovalPolicy(policy);
1393
+ }
1394
+ addEnvironment(key, value) {
1395
+ this._lambda.addEnvironment(key, value);
1396
+ }
1397
+ }
1398
+
1399
+ class JaypieQueuedLambda extends Construct {
1400
+ constructor(scope, id, props) {
1401
+ super(scope, id);
1402
+ const { allowAllOutbound, allowPublicSubnet, architecture, batchSize = 1, code, datadogApiKeyArn, deadLetterQueue, deadLetterQueueEnabled, deadLetterTopic, description, environment = {}, envSecrets = {}, ephemeralStorageSize, fifo = true, filesystem, handler = "index.handler", initialPolicy, layers = [], logGroup, logRetention = CDK$2.LAMBDA.LOG_RETENTION, maxEventAge, memorySize = CDK$2.LAMBDA.MEMORY_SIZE, paramsAndSecrets, paramsAndSecretsOptions, profiling, profilingGroup, provisionedConcurrentExecutions, reservedConcurrentExecutions, retryAttempts, roleTag, runtime = new lambda.Runtime("nodejs24.x", lambda.RuntimeFamily.NODEJS, {
1403
+ supportsInlineCode: true,
1404
+ }), runtimeManagementMode, secrets = [], securityGroups, timeout = Duration.seconds(CDK$2.DURATION.LAMBDA_WORKER), tracing, vendorTag, visibilityTimeout = Duration.seconds(CDK$2.DURATION.LAMBDA_WORKER), vpc, vpcSubnets, } = props;
1405
+ // Create SQS Queue
1406
+ this._queue = new sqs.Queue(this, "Queue", {
1407
+ fifo,
1408
+ visibilityTimeout: typeof visibilityTimeout === "number"
1409
+ ? Duration.seconds(visibilityTimeout)
1410
+ : visibilityTimeout,
1411
+ });
1412
+ if (roleTag) {
1413
+ Tags.of(this._queue).add(CDK$2.TAG.ROLE, roleTag);
1414
+ }
1415
+ if (vendorTag) {
1416
+ Tags.of(this._queue).add(CDK$2.TAG.VENDOR, vendorTag);
1417
+ }
1418
+ // Create Lambda with JaypieLambda
1419
+ this._lambdaConstruct = new JaypieLambda(this, "Function", {
1420
+ allowAllOutbound,
1421
+ allowPublicSubnet,
1422
+ architecture,
1423
+ code,
1424
+ datadogApiKeyArn,
1425
+ deadLetterQueue,
1426
+ deadLetterQueueEnabled,
1427
+ deadLetterTopic,
1428
+ description,
1429
+ environment: {
1430
+ ...environment,
1431
+ CDK_ENV_QUEUE_URL: this._queue.queueUrl,
1432
+ },
1433
+ envSecrets,
1434
+ ephemeralStorageSize,
1435
+ filesystem,
1436
+ handler,
1437
+ initialPolicy,
1438
+ layers,
1439
+ logGroup,
1440
+ logRetention,
1441
+ maxEventAge,
1442
+ memorySize,
1443
+ paramsAndSecrets,
1444
+ paramsAndSecretsOptions,
1445
+ profiling,
1446
+ profilingGroup,
1447
+ provisionedConcurrentExecutions,
1448
+ reservedConcurrentExecutions,
1449
+ retryAttempts,
1450
+ roleTag,
1451
+ runtime,
1452
+ runtimeManagementMode,
1453
+ secrets,
1454
+ securityGroups,
1455
+ timeout,
1456
+ tracing,
1457
+ vendorTag,
1458
+ vpc,
1459
+ vpcSubnets,
1460
+ });
1461
+ // Set up queue and lambda integration
1462
+ this._queue.grantConsumeMessages(this._lambdaConstruct);
1463
+ this._queue.grantSendMessages(this._lambdaConstruct);
1464
+ this._lambdaConstruct.addEventSource(new lambdaEventSources.SqsEventSource(this._queue, {
1465
+ batchSize,
1466
+ }));
1467
+ }
1468
+ // Public accessors
1469
+ get queue() {
1470
+ return this._queue;
1471
+ }
1472
+ get lambda() {
1473
+ return this._lambdaConstruct.lambda;
1474
+ }
1475
+ // IFunction implementation
1476
+ get functionArn() {
1477
+ return this._lambdaConstruct.functionArn;
1478
+ }
1479
+ get functionName() {
1480
+ return this._lambdaConstruct.functionName;
1481
+ }
1482
+ get grantPrincipal() {
1483
+ return this._lambdaConstruct.grantPrincipal;
1484
+ }
1485
+ get role() {
1486
+ return this._lambdaConstruct.role;
1487
+ }
1488
+ get architecture() {
1489
+ return this._lambdaConstruct.architecture;
1490
+ }
1491
+ get connections() {
1492
+ return this._lambdaConstruct.connections;
1493
+ }
1494
+ get isBoundToVpc() {
1495
+ return this._lambdaConstruct.isBoundToVpc;
1496
+ }
1497
+ get latestVersion() {
1498
+ return this._lambdaConstruct.latestVersion;
1499
+ }
1500
+ get permissionsNode() {
1501
+ return this._lambdaConstruct.permissionsNode;
1502
+ }
1503
+ get resourceArnsForGrantInvoke() {
1504
+ return this._lambdaConstruct.resourceArnsForGrantInvoke;
1505
+ }
1506
+ get functionRef() {
1507
+ return this._lambdaConstruct.functionRef;
1508
+ }
1509
+ addEventSource(source) {
1510
+ this._lambdaConstruct.addEventSource(source);
1511
+ }
1512
+ addEventSourceMapping(id, options) {
1513
+ return this._lambdaConstruct.addEventSourceMapping(id, options);
1514
+ }
1515
+ addFunctionUrl(options) {
1516
+ return this._lambdaConstruct.addFunctionUrl(options);
1517
+ }
1518
+ addPermission(id, permission) {
1519
+ this._lambdaConstruct.addPermission(id, permission);
1520
+ }
1521
+ addToRolePolicy(statement) {
1522
+ this._lambdaConstruct.addToRolePolicy(statement);
1523
+ }
1524
+ configureAsyncInvoke(options) {
1525
+ this._lambdaConstruct.configureAsyncInvoke(options);
1526
+ }
1527
+ grantInvoke(grantee) {
1528
+ return this._lambdaConstruct.grantInvoke(grantee);
1529
+ }
1530
+ grantInvokeCompositePrincipal(compositePrincipal) {
1531
+ return this._lambdaConstruct.grantInvokeCompositePrincipal(compositePrincipal);
1532
+ }
1533
+ grantInvokeUrl(grantee) {
1534
+ return this._lambdaConstruct.grantInvokeUrl(grantee);
1535
+ }
1536
+ metric(metricName, props) {
1537
+ return this._lambdaConstruct.metric(metricName, props);
1538
+ }
1539
+ metricDuration(props) {
1540
+ return this._lambdaConstruct.metricDuration(props);
1541
+ }
1542
+ metricErrors(props) {
1543
+ return this._lambdaConstruct.metricErrors(props);
1544
+ }
1545
+ metricInvocations(props) {
1546
+ return this._lambdaConstruct.metricInvocations(props);
1547
+ }
1548
+ metricThrottles(props) {
1549
+ return this._lambdaConstruct.metricThrottles(props);
1550
+ }
1551
+ // Additional IFunction implementation
1552
+ grantInvokeLatestVersion(grantee) {
1553
+ return this._lambdaConstruct.grantInvokeLatestVersion(grantee);
1554
+ }
1555
+ grantInvokeVersion(grantee, version) {
1556
+ return this._lambdaConstruct.grantInvokeVersion(grantee, version);
1557
+ }
1558
+ get env() {
1559
+ return {
1560
+ account: Stack.of(this).account,
1561
+ region: Stack.of(this).region,
1562
+ };
1563
+ }
1564
+ get stack() {
1565
+ return Stack.of(this);
1566
+ }
1567
+ applyRemovalPolicy(policy) {
1568
+ this._lambdaConstruct.applyRemovalPolicy(policy);
1569
+ this._queue.applyRemovalPolicy(policy);
1570
+ }
1571
+ // IQueue implementation
1572
+ get queueRef() {
1573
+ return {
1574
+ queueUrl: this._queue.queueUrl,
1575
+ queueArn: this._queue.queueArn,
1576
+ };
1577
+ }
1578
+ get fifo() {
1579
+ return this._queue.fifo;
1580
+ }
1581
+ get queueArn() {
1582
+ return this._queue.queueArn;
1583
+ }
1584
+ get queueName() {
1585
+ return this._queue.queueName;
1586
+ }
1587
+ get queueUrl() {
1588
+ return this._queue.queueUrl;
1589
+ }
1590
+ get encryptionMasterKey() {
1591
+ return this._queue.encryptionMasterKey;
1592
+ }
1593
+ addToResourcePolicy(statement) {
1594
+ return this._queue.addToResourcePolicy(statement);
1595
+ }
1596
+ grant(grantee, ...actions) {
1597
+ return this._queue.grant(grantee, ...actions);
1598
+ }
1599
+ grantConsumeMessages(grantee) {
1600
+ return this._queue.grantConsumeMessages(grantee);
1601
+ }
1602
+ grantPurge(grantee) {
1603
+ return this._queue.grantPurge(grantee);
1604
+ }
1605
+ grantSendMessages(grantee) {
1606
+ return this._queue.grantSendMessages(grantee);
1607
+ }
1608
+ // Queue metrics
1609
+ metricApproximateAgeOfOldestMessage(props) {
1610
+ return this._queue.metricApproximateAgeOfOldestMessage(props);
1611
+ }
1612
+ metricApproximateNumberOfMessagesDelayed(props) {
1613
+ return this._queue.metricApproximateNumberOfMessagesDelayed(props);
1614
+ }
1615
+ metricApproximateNumberOfMessagesNotVisible(props) {
1616
+ return this._queue.metricApproximateNumberOfMessagesNotVisible(props);
1617
+ }
1618
+ metricApproximateNumberOfMessagesVisible(props) {
1619
+ return this._queue.metricApproximateNumberOfMessagesVisible(props);
1620
+ }
1621
+ metricNumberOfEmptyReceives(props) {
1622
+ return this._queue.metricNumberOfEmptyReceives(props);
1623
+ }
1624
+ metricNumberOfMessagesDeleted(props) {
1625
+ return this._queue.metricNumberOfMessagesDeleted(props);
1626
+ }
1627
+ metricNumberOfMessagesReceived(props) {
1628
+ return this._queue.metricNumberOfMessagesReceived(props);
1629
+ }
1630
+ metricNumberOfMessagesSent(props) {
1631
+ return this._queue.metricNumberOfMessagesSent(props);
1632
+ }
1633
+ metricSentMessageSize(props) {
1634
+ return this._queue.metricSentMessageSize(props);
1635
+ }
1636
+ addEnvironment(key, value) {
1637
+ this._lambdaConstruct.addEnvironment(key, value);
1638
+ }
1639
+ }
1640
+
1641
+ class JaypieBucketQueuedLambda extends JaypieQueuedLambda {
1642
+ constructor(scope, id, props) {
1643
+ props.fifo = false; // S3 event notifications are not supported for FIFO queues
1644
+ super(scope, id, props);
1645
+ const { bucketName, roleTag, vendorTag, bucketOptions = {} } = props;
1646
+ // Create S3 Bucket
1647
+ this._bucket = new s3.Bucket(this, "Bucket", {
1648
+ bucketName: bucketOptions.bucketName || bucketName,
1649
+ removalPolicy: bucketOptions.removalPolicy || RemovalPolicy.RETAIN,
1650
+ ...bucketOptions,
1651
+ });
1652
+ // Add tags to bucket
1653
+ if (roleTag) {
1654
+ Tags.of(this._bucket).add(CDK$2.TAG.ROLE, roleTag);
1655
+ }
1656
+ if (vendorTag) {
1657
+ Tags.of(this._bucket).add(CDK$2.TAG.VENDOR, vendorTag);
1658
+ }
1659
+ // Add an event notification from the bucket to the queue
1660
+ this._bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.SqsDestination(this.queue));
1661
+ // Grant the lambda access to the bucket
1662
+ this._bucket.grantReadWrite(this);
1663
+ // Add environment variable for bucket name
1664
+ this.lambda.addEnvironment("CDK_ENV_BUCKET_NAME", this._bucket.bucketName);
1665
+ }
1666
+ // Public accessors
1667
+ get bucket() {
1668
+ return this._bucket;
1669
+ }
1670
+ // IBucket implementation
1671
+ get bucketArn() {
1672
+ return this._bucket.bucketArn;
1673
+ }
1674
+ get bucketDomainName() {
1675
+ return this._bucket.bucketDomainName;
1676
+ }
1677
+ get bucketDualStackDomainName() {
1678
+ return this._bucket.bucketDualStackDomainName;
1679
+ }
1680
+ get bucketName() {
1681
+ return this._bucket.bucketName;
1682
+ }
1683
+ get bucketRegionalDomainName() {
1684
+ return this._bucket.bucketRegionalDomainName;
1685
+ }
1686
+ get bucketWebsiteDomainName() {
1687
+ return this._bucket.bucketWebsiteDomainName;
1688
+ }
1689
+ get bucketWebsiteUrl() {
1690
+ return this._bucket.bucketWebsiteUrl;
1691
+ }
1692
+ get encryptionKey() {
1693
+ return this._bucket.encryptionKey;
1694
+ }
1695
+ get isWebsite() {
1696
+ return this._bucket.isWebsite || false;
1697
+ }
1698
+ get policy() {
1699
+ return this._bucket.policy;
1700
+ }
1701
+ addEventNotification(event, dest, ...filters) {
1702
+ this._bucket.addEventNotification(event, dest, ...filters);
1703
+ }
1704
+ addObjectCreatedNotification(dest, ...filters) {
1705
+ this._bucket.addObjectCreatedNotification(dest, ...filters);
1706
+ }
1707
+ addObjectRemovedNotification(dest, ...filters) {
1708
+ this._bucket.addObjectRemovedNotification(dest, ...filters);
1709
+ }
1710
+ addToResourcePolicy(permission) {
1711
+ return this._bucket.addToResourcePolicy(permission);
1712
+ }
1713
+ arnForObjects(objectKeyPattern) {
1714
+ return this._bucket.arnForObjects(objectKeyPattern);
1715
+ }
1716
+ enableEventBridgeNotification() {
1717
+ this._bucket.enableEventBridgeNotification();
1718
+ }
1719
+ grantDelete(grantee, objectsKeyPattern) {
1720
+ return this._bucket.grantDelete(grantee, objectsKeyPattern);
1721
+ }
1722
+ grantPublicAccess(keyPrefix, ...allowedActions) {
1723
+ return this._bucket.grantPublicAccess(keyPrefix, ...allowedActions);
1724
+ }
1725
+ grantPut(grantee, objectsKeyPattern) {
1726
+ return this._bucket.grantPut(grantee, objectsKeyPattern);
1727
+ }
1728
+ grantPutAcl(grantee, objectsKeyPattern) {
1729
+ return this._bucket.grantPutAcl(grantee, objectsKeyPattern);
1730
+ }
1731
+ grantRead(grantee, objectsKeyPattern) {
1732
+ return this._bucket.grantRead(grantee, objectsKeyPattern);
1733
+ }
1734
+ grantReadWrite(grantee, objectsKeyPattern) {
1735
+ return this._bucket.grantReadWrite(grantee, objectsKeyPattern);
1736
+ }
1737
+ grantWrite(grantee, objectsKeyPattern) {
1738
+ return this._bucket.grantWrite(grantee, objectsKeyPattern);
1739
+ }
1740
+ onCloudTrailEvent(id, options) {
1741
+ return this._bucket.onCloudTrailEvent(id, options);
1742
+ }
1743
+ onCloudTrailPutObject(id, options) {
1744
+ return this._bucket.onCloudTrailPutObject(id, options);
1745
+ }
1746
+ onCloudTrailWriteObject(id, options) {
1747
+ return this._bucket.onCloudTrailWriteObject(id, options);
1748
+ }
1749
+ s3UrlForObject(key) {
1750
+ return this._bucket.s3UrlForObject(key);
1751
+ }
1752
+ transferAccelerationUrlForObject(key, options) {
1753
+ return this._bucket.transferAccelerationUrlForObject(key, options);
1754
+ }
1755
+ urlForObject(key) {
1756
+ return this._bucket.urlForObject(key);
1757
+ }
1758
+ virtualHostedUrlForObject(key, options) {
1759
+ return this._bucket.virtualHostedUrlForObject(key, options);
1760
+ }
1761
+ grantReplicationPermission(identity, props) {
1762
+ return this._bucket.grantReplicationPermission(identity, props);
1763
+ }
1764
+ addReplicationPolicy(policy) {
1765
+ this._bucket.addReplicationPolicy(policy);
1766
+ }
1767
+ get bucketRef() {
1768
+ return {
1769
+ bucketArn: this._bucket.bucketArn,
1770
+ bucketName: this._bucket.bucketName,
1771
+ };
1772
+ }
1773
+ // Override applyRemovalPolicy to apply to all resources
1774
+ applyRemovalPolicy(policy) {
1775
+ super.applyRemovalPolicy(policy);
1776
+ this._bucket.applyRemovalPolicy(policy);
1777
+ }
1778
+ }
1779
+
1780
+ class JaypieDatadogBucket extends Construct {
1781
+ /**
1782
+ * Create a new S3 bucket for Datadog log archiving with automatic IAM permissions
1783
+ */
1784
+ constructor(scope, idOrProps, propsOrUndefined) {
1785
+ // Handle overloaded constructor signatures
1786
+ let props;
1787
+ let id;
1788
+ if (typeof idOrProps === "string") {
1789
+ // First param is ID, second is props
1790
+ props = propsOrUndefined || {};
1791
+ id = idOrProps;
1792
+ }
1793
+ else {
1794
+ // First param is props
1795
+ props = idOrProps || {};
1796
+ id = props.id || "JaypieDatadogBucket";
1797
+ }
1798
+ super(scope, id);
1799
+ // Extract Jaypie-specific options
1800
+ const { bucketId = "DatadogArchiveBucket", bucketScope, grantDatadogAccess = true, project, service = CDK$2.SERVICE.DATADOG, ...bucketProps } = props;
1801
+ // Create the bucket using bucketScope (defaults to this) and bucketId
1802
+ const effectiveBucketScope = bucketScope || this;
1803
+ this.bucket = new Bucket(effectiveBucketScope, bucketId, bucketProps);
1804
+ // Add tags to bucket
1805
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.SERVICE, service);
1806
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
1807
+ if (project) {
1808
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.PROJECT, project);
1809
+ }
1810
+ // Grant Datadog role access to bucket if enabled
1811
+ if (grantDatadogAccess) {
1812
+ this.policy = this.grantDatadogRoleBucketAccess({ project, service });
1813
+ }
1814
+ }
1815
+ /**
1816
+ * Grants the Datadog IAM role access to this bucket
1817
+ *
1818
+ * Checks for CDK_ENV_DATADOG_ROLE_ARN environment variable.
1819
+ * If found, creates a custom policy with:
1820
+ * - s3:ListBucket on bucket
1821
+ * - s3:GetObject and s3:PutObject on bucket/*
1822
+ *
1823
+ * @param options - Configuration options
1824
+ * @returns The created Policy, or undefined if CDK_ENV_DATADOG_ROLE_ARN is not set
1825
+ */
1826
+ grantDatadogRoleBucketAccess(options) {
1827
+ const datadogRoleArn = process.env.CDK_ENV_DATADOG_ROLE_ARN;
1828
+ // Early return if no Datadog role ARN is configured
1829
+ if (!datadogRoleArn) {
1830
+ return undefined;
1831
+ }
1832
+ const { project, service = CDK$2.SERVICE.DATADOG } = options || {};
1833
+ // Lookup the Datadog role
1834
+ const datadogRole = Role.fromRoleArn(this, "DatadogRole", datadogRoleArn);
1835
+ // Build policy statements for bucket access
1836
+ const statements = [
1837
+ // Allow list bucket
1838
+ new PolicyStatement({
1839
+ actions: ["s3:ListBucket"],
1840
+ resources: [this.bucket.bucketArn],
1841
+ }),
1842
+ // Allow read and write to the bucket
1843
+ new PolicyStatement({
1844
+ actions: ["s3:GetObject", "s3:PutObject"],
1845
+ resources: [`${this.bucket.bucketArn}/*`],
1846
+ }),
1847
+ ];
1848
+ // Create the custom policy
1849
+ const datadogBucketPolicy = new Policy(this, "DatadogBucketPolicy", {
1850
+ roles: [datadogRole],
1851
+ statements,
1852
+ });
1853
+ // Add tags
1854
+ cdk.Tags.of(datadogBucketPolicy).add(CDK$2.TAG.SERVICE, service);
1855
+ cdk.Tags.of(datadogBucketPolicy).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
1856
+ cdk.Tags.of(datadogBucketPolicy).add(CDK$2.TAG.VENDOR, CDK$2.VENDOR.DATADOG);
1857
+ if (project) {
1858
+ cdk.Tags.of(datadogBucketPolicy).add(CDK$2.TAG.PROJECT, project);
1859
+ }
1860
+ return datadogBucketPolicy;
1861
+ }
1862
+ }
1863
+
1864
+ const DATADOG_FORWARDER_TEMPLATE_URL = "https://datadog-cloudformation-template.s3.amazonaws.com/aws/forwarder/latest.yaml";
1865
+ const DEFAULT_RESERVED_CONCURRENCY = "10";
1866
+ class JaypieDatadogForwarder extends Construct {
1867
+ /**
1868
+ * Create a new Datadog forwarder with CloudFormation nested stack
1869
+ */
1870
+ constructor(scope, idOrProps, propsOrUndefined) {
1871
+ // Handle overloaded constructor signatures
1872
+ let props;
1873
+ let id;
1874
+ if (typeof idOrProps === "string") {
1875
+ // First param is ID, second is props
1876
+ props = propsOrUndefined || {};
1877
+ id = idOrProps;
1878
+ }
1879
+ else {
1880
+ // First param is props
1881
+ props = idOrProps || {};
1882
+ id = props.id || "DatadogForwarder";
1883
+ }
1884
+ super(scope, id);
1885
+ // Resolve options with defaults
1886
+ const { account = process.env.CDK_ENV_ACCOUNT, additionalTags, createOutput = true, datadogApiKey = process.env.CDK_ENV_DATADOG_API_KEY, enableCloudFormationEvents = true, enableRoleExtension = true, exportName = CDK$2.IMPORT.DATADOG_LOG_FORWARDER, project, reservedConcurrency = DEFAULT_RESERVED_CONCURRENCY, service = CDK$2.VENDOR.DATADOG, templateUrl = DATADOG_FORWARDER_TEMPLATE_URL, } = props;
1887
+ // Validate required parameters
1888
+ if (!datadogApiKey) {
1889
+ throw new Error("Datadog API key is required. Provide via datadogApiKey prop or CDK_ENV_DATADOG_API_KEY environment variable.");
1890
+ }
1891
+ // Build Datadog tags
1892
+ let ddTags = account ? `account:${account}` : "";
1893
+ if (additionalTags) {
1894
+ ddTags = ddTags ? `${ddTags},${additionalTags}` : additionalTags;
1895
+ }
1896
+ // Deploy Datadog CloudFormation stack
1897
+ this.cfnStack = new CfnStack(this, "Stack", {
1898
+ parameters: {
1899
+ DdApiKey: datadogApiKey,
1900
+ DdTags: ddTags,
1901
+ ReservedConcurrency: reservedConcurrency,
1902
+ },
1903
+ templateUrl,
1904
+ });
1905
+ // Add tags to stack
1906
+ cdk.Tags.of(this.cfnStack).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
1907
+ cdk.Tags.of(this.cfnStack).add(CDK$2.TAG.SERVICE, service);
1908
+ cdk.Tags.of(this.cfnStack).add(CDK$2.TAG.VENDOR, CDK$2.VENDOR.DATADOG);
1909
+ if (project) {
1910
+ cdk.Tags.of(this.cfnStack).add(CDK$2.TAG.PROJECT, project);
1911
+ }
1912
+ // Extract forwarder function from stack outputs
1913
+ this.forwarderFunction = lambda.Function.fromFunctionArn(this, "Function", this.cfnStack.getAtt("Outputs.DatadogForwarderArn").toString());
1914
+ // Extend Datadog role with custom permissions if enabled
1915
+ if (enableRoleExtension) {
1916
+ extendDatadogRole(this, { project, service });
1917
+ }
1918
+ // Create CloudFormation events rule if enabled
1919
+ if (enableCloudFormationEvents) {
1920
+ this.eventsRule = new Rule(this, "CloudFormationEventsRule", {
1921
+ eventPattern: {
1922
+ source: ["aws.cloudformation"],
1923
+ },
1924
+ targets: [
1925
+ new LambdaFunction(this.forwarderFunction, {
1926
+ event: RuleTargetInput.fromEventPath("$"),
1927
+ }),
1928
+ ],
1929
+ });
1930
+ // Add tags to events rule
1931
+ cdk.Tags.of(this.eventsRule).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
1932
+ cdk.Tags.of(this.eventsRule).add(CDK$2.TAG.SERVICE, service);
1933
+ cdk.Tags.of(this.eventsRule).add(CDK$2.TAG.VENDOR, CDK$2.VENDOR.DATADOG);
1934
+ if (project) {
1935
+ cdk.Tags.of(this.eventsRule).add(CDK$2.TAG.PROJECT, project);
1936
+ }
1937
+ }
1938
+ // Create CloudFormation output if enabled
1939
+ if (createOutput) {
1940
+ new cdk.CfnOutput(this, "ForwarderArnOutput", {
1941
+ description: "Datadog Log Forwarder Lambda ARN",
1942
+ exportName,
1943
+ value: this.cfnStack.getAtt("Outputs.DatadogForwarderArn").toString(),
1944
+ });
1945
+ }
1946
+ }
1947
+ }
1948
+
1949
+ class JaypieDistribution extends Construct {
1950
+ constructor(scope, id, props) {
1951
+ super(scope, id);
1952
+ const { certificate: certificateProp = true, defaultBehavior: propsDefaultBehavior, destination: destinationProp = true, handler, host: propsHost, invokeMode = lambda.InvokeMode.BUFFERED, roleTag = CDK$2.ROLE.API, zone: propsZone, ...distributionProps } = props;
1953
+ // Validate environment variables
1954
+ if (process.env.CDK_ENV_API_SUBDOMAIN &&
1955
+ !isValidSubdomain(process.env.CDK_ENV_API_SUBDOMAIN)) {
1956
+ throw new Error("CDK_ENV_API_SUBDOMAIN is not a valid subdomain");
1957
+ }
1958
+ if (process.env.CDK_ENV_API_HOSTED_ZONE &&
1959
+ !isValidHostname$1(process.env.CDK_ENV_API_HOSTED_ZONE)) {
1960
+ throw new Error("CDK_ENV_API_HOSTED_ZONE is not a valid hostname");
1961
+ }
1962
+ if (process.env.CDK_ENV_HOSTED_ZONE &&
1963
+ !isValidHostname$1(process.env.CDK_ENV_HOSTED_ZONE)) {
1964
+ throw new Error("CDK_ENV_HOSTED_ZONE is not a valid hostname");
1965
+ }
1966
+ // Determine host from props or environment
1967
+ let host = propsHost;
1968
+ if (!host) {
1969
+ try {
1970
+ if (process.env.CDK_ENV_API_HOST_NAME) {
1971
+ host = process.env.CDK_ENV_API_HOST_NAME;
1972
+ }
1973
+ else if (process.env.CDK_ENV_API_SUBDOMAIN) {
1974
+ host = mergeDomain(process.env.CDK_ENV_API_SUBDOMAIN, process.env.CDK_ENV_API_HOSTED_ZONE ||
1975
+ process.env.CDK_ENV_HOSTED_ZONE ||
1976
+ "");
1977
+ }
1978
+ }
1979
+ catch {
1980
+ host = undefined;
1981
+ }
1982
+ }
1983
+ if (host && !isValidHostname$1(host)) {
1984
+ throw new Error("Host is not a valid hostname");
1985
+ }
1986
+ this.host = host;
1987
+ // Determine zone from props or environment
1988
+ const zone = propsZone || process.env.CDK_ENV_HOSTED_ZONE;
1989
+ // Resolve the origin from handler
1990
+ // Check order matters: IFunctionUrl before IOrigin (FunctionUrl also has bind method)
1991
+ // IFunction before IFunctionUrl (IFunction doesn't have functionUrlId)
1992
+ let origin;
1993
+ if (handler) {
1994
+ if (this.isIFunction(handler)) {
1995
+ // Create FunctionUrl for the Lambda function
1996
+ const functionUrl = new lambda.FunctionUrl(this, "FunctionUrl", {
1997
+ function: handler,
1998
+ authType: lambda.FunctionUrlAuthType.NONE,
1999
+ invokeMode,
2000
+ });
2001
+ this.functionUrl = functionUrl;
2002
+ origin = new origins.FunctionUrlOrigin(functionUrl);
2003
+ }
2004
+ else if (this.isIFunctionUrl(handler)) {
2005
+ origin = new origins.FunctionUrlOrigin(handler);
2006
+ }
2007
+ else if (this.isIOrigin(handler)) {
2008
+ origin = handler;
2009
+ }
2010
+ }
2011
+ // Build default behavior
2012
+ let defaultBehavior;
2013
+ if (propsDefaultBehavior) {
2014
+ defaultBehavior = propsDefaultBehavior;
2015
+ }
2016
+ else if (origin) {
2017
+ defaultBehavior = {
2018
+ allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
2019
+ cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
2020
+ origin,
2021
+ originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
2022
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
2023
+ };
2024
+ }
2025
+ else {
2026
+ throw new Error("Either handler or defaultBehavior must be provided to JaypieDistribution");
2027
+ }
2028
+ // Resolve hosted zone and certificate
2029
+ // Only resolve zone when we need it (for certificate or DNS)
2030
+ let hostedZone;
2031
+ let certificateToUse;
2032
+ if (host && zone && certificateProp !== false) {
2033
+ hostedZone = resolveHostedZone(this, { zone });
2034
+ if (certificateProp === true) {
2035
+ certificateToUse = new acm.Certificate(this, constructEnvName("Certificate"), {
2036
+ domainName: host,
2037
+ validation: acm.CertificateValidation.fromDns(hostedZone),
2038
+ });
2039
+ Tags.of(certificateToUse).add(CDK$2.TAG.ROLE, roleTag);
2040
+ }
2041
+ else if (typeof certificateProp === "object") {
2042
+ certificateToUse = certificateProp;
2043
+ }
2044
+ this.certificate = certificateToUse;
2045
+ }
2046
+ // Create log bucket if logging is enabled
2047
+ let logBucket;
2048
+ if (destinationProp !== false) {
2049
+ logBucket = new s3.Bucket(this, constructEnvName("LogBucket"), {
2050
+ objectOwnership: s3.ObjectOwnership.OBJECT_WRITER,
2051
+ removalPolicy: RemovalPolicy.DESTROY,
2052
+ autoDeleteObjects: true,
2053
+ lifecycleRules: [
2054
+ {
2055
+ expiration: Duration.days(90),
2056
+ transitions: [
2057
+ {
2058
+ storageClass: s3.StorageClass.INFREQUENT_ACCESS,
2059
+ transitionAfter: Duration.days(30),
2060
+ },
2061
+ ],
2062
+ },
2063
+ ],
2064
+ });
2065
+ Tags.of(logBucket).add(CDK$2.TAG.ROLE, CDK$2.ROLE.STORAGE);
2066
+ // Add S3 notification to Datadog forwarder
2067
+ const lambdaDestination = destinationProp === true
2068
+ ? new LambdaDestination(resolveDatadogForwarderFunction(this))
2069
+ : destinationProp;
2070
+ logBucket.addEventNotification(s3.EventType.OBJECT_CREATED, lambdaDestination);
2071
+ this.logBucket = logBucket;
2072
+ }
2073
+ // Create the CloudFront distribution
2074
+ this.distribution = new cloudfront.Distribution(this, constructEnvName("Distribution"), {
2075
+ defaultBehavior,
2076
+ ...(host && certificateToUse
2077
+ ? {
2078
+ certificate: certificateToUse,
2079
+ domainNames: [host],
2080
+ }
2081
+ : {}),
2082
+ ...(logBucket
2083
+ ? {
2084
+ enableLogging: true,
2085
+ logBucket,
2086
+ logFilePrefix: "cloudfront-logs/",
2087
+ }
2088
+ : {}),
2089
+ ...distributionProps,
2090
+ });
2091
+ Tags.of(this.distribution).add(CDK$2.TAG.ROLE, roleTag);
2092
+ this.distributionArn = `arn:aws:cloudfront::${Stack.of(this).account}:distribution/${this.distribution.distributionId}`;
2093
+ this.distributionDomainName = this.distribution.distributionDomainName;
2094
+ this.distributionId = this.distribution.distributionId;
2095
+ this.domainName = this.distribution.domainName;
2096
+ // Create DNS records if we have host and zone
2097
+ if (host && hostedZone) {
2098
+ const aRecord = new route53.ARecord(this, "AliasRecord", {
2099
+ recordName: host,
2100
+ target: route53.RecordTarget.fromAlias(new route53Targets.CloudFrontTarget(this.distribution)),
2101
+ zone: hostedZone,
2102
+ });
2103
+ Tags.of(aRecord).add(CDK$2.TAG.ROLE, CDK$2.ROLE.NETWORKING);
2104
+ const aaaaRecord = new route53.AaaaRecord(this, "AaaaAliasRecord", {
2105
+ recordName: host,
2106
+ target: route53.RecordTarget.fromAlias(new route53Targets.CloudFrontTarget(this.distribution)),
2107
+ zone: hostedZone,
2108
+ });
2109
+ Tags.of(aaaaRecord).add(CDK$2.TAG.ROLE, CDK$2.ROLE.NETWORKING);
2110
+ }
2111
+ }
2112
+ // Type guards for handler types
2113
+ isIOrigin(handler) {
2114
+ return (typeof handler === "object" &&
2115
+ handler !== null &&
2116
+ "bind" in handler &&
2117
+ typeof handler.bind === "function");
2118
+ }
2119
+ isIFunctionUrl(handler) {
2120
+ // FunctionUrl has 'url' property which is the function URL string
2121
+ // IFunction does not have 'url' property
2122
+ return (typeof handler === "object" &&
2123
+ handler !== null &&
2124
+ "url" in handler &&
2125
+ "functionArn" in handler);
2126
+ }
2127
+ isIFunction(handler) {
2128
+ // IFunction has functionArn and functionName but NOT 'url'
2129
+ // (FunctionUrl also has functionArn but also has 'url')
2130
+ return (typeof handler === "object" &&
2131
+ handler !== null &&
2132
+ "functionArn" in handler &&
2133
+ "functionName" in handler &&
2134
+ !("url" in handler));
2135
+ }
2136
+ // Implement IDistribution interface
2137
+ get env() {
2138
+ return {
2139
+ account: Stack.of(this).account,
2140
+ region: Stack.of(this).region,
2141
+ };
2142
+ }
2143
+ get stack() {
2144
+ return this.distribution.stack;
2145
+ }
2146
+ applyRemovalPolicy(policy) {
2147
+ this.distribution.applyRemovalPolicy(policy);
2148
+ }
2149
+ grant(identity, ...actions) {
2150
+ return this.distribution.grant(identity, ...actions);
2151
+ }
2152
+ grantCreateInvalidation(identity) {
2153
+ return this.distribution.grantCreateInvalidation(identity);
2154
+ }
2155
+ get distributionRef() {
2156
+ return {
2157
+ distributionId: this.distribution.distributionId,
2158
+ };
2159
+ }
2160
+ }
2161
+
2162
+ class JaypieDatadogSecret extends JaypieEnvSecret {
2163
+ constructor(scope, id = "MongoConnectionString", props) {
2164
+ const defaultProps = {
2165
+ envKey: "DATADOG_API_KEY",
2166
+ roleTag: CDK$2.ROLE.MONITORING,
2167
+ vendorTag: CDK$2.VENDOR.DATADOG,
2168
+ ...props,
2169
+ };
2170
+ super(scope, id, defaultProps);
2171
+ }
2172
+ }
2173
+
2174
+ class JaypieDnsRecord extends Construct {
2175
+ constructor(scope, id, props) {
2176
+ super(scope, id);
2177
+ const { comment, recordName, type, values } = props;
2178
+ const ttl = props.ttl || cdk.Duration.seconds(CDK$2.DNS.CONFIG.TTL);
2179
+ // Resolve the hosted zone (supports both string and IHostedZone)
2180
+ const zone = resolveHostedZone(scope, {
2181
+ name: `${id}HostedZone`,
2182
+ zone: props.zone,
2183
+ });
2184
+ // Common properties for all record types
2185
+ const baseProps = {
2186
+ comment,
2187
+ recordName,
2188
+ ttl,
2189
+ zone,
2190
+ };
2191
+ // Create the appropriate record based on type
2192
+ switch (type) {
2193
+ case CDK$2.DNS.RECORD.A: {
2194
+ if (!Array.isArray(values) || values.length === 0) {
2195
+ throw new ConfigurationError("A record requires at least one IP address");
2196
+ }
2197
+ this.record = new ARecord(this, "Record", {
2198
+ ...baseProps,
2199
+ target: RecordTarget.fromIpAddresses(...values),
2200
+ });
2201
+ break;
2202
+ }
2203
+ case CDK$2.DNS.RECORD.CNAME: {
2204
+ if (!Array.isArray(values) || values.length === 0) {
2205
+ throw new ConfigurationError("CNAME record requires a domain name");
2206
+ }
2207
+ this.record = new CnameRecord(this, "Record", {
2208
+ ...baseProps,
2209
+ domainName: values[0],
2210
+ });
2211
+ break;
2212
+ }
2213
+ case CDK$2.DNS.RECORD.MX: {
2214
+ if (!Array.isArray(values) || values.length === 0) {
2215
+ throw new ConfigurationError("MX record requires at least one mail server");
2216
+ }
2217
+ this.record = new MxRecord(this, "Record", {
2218
+ ...baseProps,
2219
+ values: values,
2220
+ });
2221
+ break;
2222
+ }
2223
+ case CDK$2.DNS.RECORD.NS: {
2224
+ if (!Array.isArray(values) || values.length === 0) {
2225
+ throw new ConfigurationError("NS record requires at least one name server");
2226
+ }
2227
+ this.record = new NsRecord(this, "Record", {
2228
+ ...baseProps,
2229
+ values: values,
2230
+ });
2231
+ break;
2232
+ }
2233
+ case CDK$2.DNS.RECORD.TXT: {
2234
+ if (!Array.isArray(values) || values.length === 0) {
2235
+ throw new ConfigurationError("TXT record requires at least one value");
2236
+ }
2237
+ this.record = new TxtRecord(this, "Record", {
2238
+ ...baseProps,
2239
+ values: values,
2240
+ });
2241
+ break;
2242
+ }
2243
+ default:
2244
+ throw new ConfigurationError(`Unsupported DNS record type: ${type}. Supported types: A, CNAME, MX, NS, TXT`);
2245
+ }
2246
+ // Add standard tags to the DNS record
2247
+ cdk.Tags.of(this.record).add(CDK$2.TAG.SERVICE, CDK$2.SERVICE.INFRASTRUCTURE);
2248
+ cdk.Tags.of(this.record).add(CDK$2.TAG.ROLE, CDK$2.ROLE.NETWORKING);
2249
+ }
2250
+ }
2251
+
2252
+ class JaypieEventsRule extends Construct {
2253
+ /**
2254
+ * Create a new EventBridge rule that targets a Lambda function
2255
+ */
2256
+ constructor(scope, idOrSourceOrProps, propsOrUndefined) {
2257
+ // Handle overloaded constructor signatures
2258
+ let props;
2259
+ let id;
2260
+ if (typeof idOrSourceOrProps === "string") {
2261
+ // Check if it looks like an AWS source (starts with "aws.")
2262
+ if (idOrSourceOrProps.startsWith("aws.")) {
2263
+ // First param is source, second is props
2264
+ props = propsOrUndefined || {};
2265
+ props.source = idOrSourceOrProps;
2266
+ // Generate ID from source
2267
+ const sourceName = idOrSourceOrProps
2268
+ .replace("aws.", "")
2269
+ .split(".")
2270
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
2271
+ .join("");
2272
+ id = props.id || `${sourceName}EventsRule`;
2273
+ }
2274
+ else {
2275
+ // First param is ID, second is props
2276
+ props = propsOrUndefined || {};
2277
+ id = idOrSourceOrProps;
2278
+ }
2279
+ }
2280
+ else {
2281
+ // First param is props
2282
+ props = idOrSourceOrProps || {};
2283
+ if (props.source) {
2284
+ const sourceName = typeof props.source === "string"
2285
+ ? props.source
2286
+ .replace("aws.", "")
2287
+ .split(".")
2288
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
2289
+ .join("")
2290
+ : "Events";
2291
+ id = props.id || `${sourceName}EventsRule`;
2292
+ }
2293
+ else {
2294
+ id = props.id || "EventsRule";
2295
+ }
2296
+ }
2297
+ super(scope, id);
2298
+ // Extract Jaypie-specific options
2299
+ const { id: _id, project, service = CDK$2.SERVICE.DATADOG, source, targetFunction, vendor = CDK$2.VENDOR.DATADOG, ...ruleProps } = props;
2300
+ // Resolve target function
2301
+ this.targetFunction =
2302
+ targetFunction || resolveDatadogForwarderFunction(scope);
2303
+ // Build event pattern if source is specified
2304
+ const eventPattern = source
2305
+ ? {
2306
+ ...ruleProps.eventPattern,
2307
+ source: Array.isArray(source) ? source : [source],
2308
+ }
2309
+ : ruleProps.eventPattern;
2310
+ // Build rule props
2311
+ const finalRuleProps = {
2312
+ ...ruleProps,
2313
+ eventPattern,
2314
+ targets: [
2315
+ new LambdaFunction(this.targetFunction, {
2316
+ event: RuleTargetInput.fromEventPath("$"),
2317
+ }),
2318
+ ],
2319
+ };
2320
+ // Create the rule
2321
+ this.rule = new Rule(this, "Rule", finalRuleProps);
2322
+ // Add tags
2323
+ cdk.Tags.of(this.rule).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
2324
+ cdk.Tags.of(this.rule).add(CDK$2.TAG.SERVICE, service);
2325
+ cdk.Tags.of(this.rule).add(CDK$2.TAG.VENDOR, vendor);
2326
+ if (project) {
2327
+ cdk.Tags.of(this.rule).add(CDK$2.TAG.PROJECT, project);
2328
+ }
2329
+ }
2330
+ }
2331
+
2332
+ class JaypieGitHubDeployRole extends Construct {
2333
+ constructor(scope, id = "GitHubDeployRole", props = {}) {
2334
+ super(scope, id);
2335
+ const { oidcProviderArn = Fn.importValue(CDK$2.IMPORT.OIDC_PROVIDER), output = true, repoRestriction: propsRepoRestriction, } = props;
2336
+ // Extract account ID from the scope
2337
+ const accountId = Stack.of(this).account;
2338
+ // Resolve repoRestriction from props or environment variables
2339
+ let repoRestriction = propsRepoRestriction;
2340
+ if (!repoRestriction) {
2341
+ const envRepo = process.env.CDK_ENV_REPO || process.env.PROJECT_REPO;
2342
+ if (!envRepo) {
2343
+ throw new ConfigurationError("No repoRestriction provided. Set repoRestriction prop, CDK_ENV_REPO, or PROJECT_REPO environment variable");
2344
+ }
2345
+ // Extract organization from owner/repo format and create org-wide restriction
2346
+ const organization = envRepo.split("/")[0];
2347
+ repoRestriction = `repo:${organization}/*:*`;
2348
+ }
2349
+ // Create the IAM role
2350
+ this._role = new Role(this, "GitHubActionsRole", {
2351
+ assumedBy: new FederatedPrincipal(oidcProviderArn, {
2352
+ StringLike: {
2353
+ "token.actions.githubusercontent.com:sub": repoRestriction,
2354
+ },
2355
+ }, "sts:AssumeRoleWithWebIdentity"),
2356
+ maxSessionDuration: Duration.hours(1),
2357
+ path: "/",
2358
+ });
2359
+ Tags.of(this._role).add(CDK$2.TAG.ROLE, CDK$2.ROLE.DEPLOY);
2360
+ // Allow the role to access the GitHub OIDC provider
2361
+ this._role.addToPolicy(new PolicyStatement({
2362
+ actions: ["sts:AssumeRoleWithWebIdentity"],
2363
+ resources: [`arn:aws:iam::${accountId}:oidc-provider/*`],
2364
+ }));
2365
+ // Allow the role to deploy CDK apps
2366
+ this._role.addToPolicy(new PolicyStatement({
2367
+ actions: [
2368
+ "cloudformation:CreateStack",
2369
+ "cloudformation:DeleteStack",
2370
+ "cloudformation:DescribeStackEvents",
2371
+ "cloudformation:DescribeStackResource",
2372
+ "cloudformation:DescribeStackResources",
2373
+ "cloudformation:DescribeStacks",
2374
+ "cloudformation:GetTemplate",
2375
+ "cloudformation:SetStackPolicy",
2376
+ "cloudformation:UpdateStack",
2377
+ "cloudformation:ValidateTemplate",
2378
+ "iam:PassRole",
2379
+ "route53:ListHostedZones*",
2380
+ "s3:GetObject",
2381
+ "s3:ListBucket",
2382
+ ],
2383
+ effect: Effect.ALLOW,
2384
+ resources: ["*"],
2385
+ }));
2386
+ this._role.addToPolicy(new PolicyStatement({
2387
+ actions: ["iam:PassRole", "sts:AssumeRole"],
2388
+ effect: Effect.ALLOW,
2389
+ resources: [
2390
+ "arn:aws:iam::*:role/cdk-hnb659fds-deploy-role-*",
2391
+ "arn:aws:iam::*:role/cdk-hnb659fds-file-publishing-*",
2392
+ "arn:aws:iam::*:role/cdk-readOnlyRole",
2393
+ ],
2394
+ }));
2395
+ // Export the ARN of the role
2396
+ if (output !== false) {
2397
+ const outputId = typeof output === "string" ? output : "GitHubActionsRoleArn";
2398
+ new CfnOutput(this, outputId, {
2399
+ value: this._role.roleArn,
2400
+ });
2401
+ }
2402
+ }
2403
+ get role() {
2404
+ return this._role;
2405
+ }
2406
+ get roleArn() {
2407
+ return this._role.roleArn;
2408
+ }
2409
+ get roleName() {
2410
+ return this._role.roleName;
2411
+ }
2412
+ }
2413
+
2414
+ class JaypieExpressLambda extends JaypieLambda {
2415
+ constructor(scope, id, props) {
2416
+ super(scope, id, {
2417
+ timeout: Duration.seconds(CDK$2.DURATION.EXPRESS_API),
2418
+ roleTag: CDK$2.ROLE.API,
2419
+ ...props,
2420
+ });
2421
+ }
2422
+ }
2423
+
2424
+ const SERVICE = {
2425
+ ROUTE53: "route53.amazonaws.com",
2426
+ };
2427
+ /**
2428
+ * Check if a string is a valid hostname
2429
+ */
2430
+ function isValidHostname(str) {
2431
+ // Check if it contains a dot and matches hostname pattern
2432
+ if (!str.includes("."))
2433
+ return false;
2434
+ // Basic hostname validation: alphanumeric, hyphens, dots
2435
+ // Each label must start and end with alphanumeric
2436
+ const hostnameRegex = /^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i;
2437
+ return hostnameRegex.test(str);
2438
+ }
2439
+ class JaypieHostedZone extends Construct {
2440
+ /**
2441
+ * Create a new hosted zone with query logging and optional DNS records
2442
+ */
2443
+ constructor(scope, idOrProps, propsOrRecords) {
2444
+ // Handle overloaded constructor signatures
2445
+ let props;
2446
+ let id;
2447
+ if (typeof idOrProps === "string") {
2448
+ // If it's a valid hostname, treat it as zoneName
2449
+ if (isValidHostname(idOrProps)) {
2450
+ // Third param can be props object or records array
2451
+ if (Array.isArray(propsOrRecords)) {
2452
+ props = { zoneName: idOrProps, records: propsOrRecords };
2453
+ }
2454
+ else {
2455
+ props = propsOrRecords || { zoneName: idOrProps };
2456
+ // Set zoneName if not already set
2457
+ if (!props.zoneName) {
2458
+ props = { ...props, zoneName: idOrProps };
2459
+ }
2460
+ }
2461
+ // Use id from props if provided, otherwise derive from zoneName
2462
+ id = props.id || `${idOrProps}-HostedZone`;
2463
+ }
2464
+ else {
2465
+ // Otherwise treat it as an explicit id
2466
+ props = propsOrRecords;
2467
+ id = idOrProps;
2468
+ }
2469
+ }
2470
+ else {
2471
+ // idOrProps is props
2472
+ props = idOrProps;
2473
+ id = props.id || `${props.zoneName}-HostedZone`;
2474
+ }
2475
+ super(scope, id);
2476
+ const { zoneName, project } = props;
2477
+ const destination = props.destination ?? true;
2478
+ const service = props.service || CDK$2.SERVICE.INFRASTRUCTURE;
2479
+ // Create the log group
2480
+ this.logGroup = new LogGroup(this, "LogGroup", {
2481
+ logGroupName: process.env.PROJECT_NONCE
2482
+ ? `/aws/route53/${zoneName}-${process.env.PROJECT_NONCE}`
2483
+ : `/aws/route53/${zoneName}`,
2484
+ retention: RetentionDays.ONE_WEEK,
2485
+ });
2486
+ // Add tags
2487
+ cdk.Tags.of(this.logGroup).add(CDK$2.TAG.SERVICE, service);
2488
+ cdk.Tags.of(this.logGroup).add(CDK$2.TAG.ROLE, CDK$2.ROLE.NETWORKING);
2489
+ if (project) {
2490
+ cdk.Tags.of(this.logGroup).add(CDK$2.TAG.PROJECT, project);
2491
+ }
2492
+ // Grant Route 53 permissions to write to the log group
2493
+ this.logGroup.grantWrite(new ServicePrincipal(SERVICE.ROUTE53));
2494
+ // Add destination based on configuration
2495
+ if (destination !== false) {
2496
+ const lambdaDestination = destination === true
2497
+ ? resolveDatadogLoggingDestination(scope)
2498
+ : destination;
2499
+ this.logGroup.addSubscriptionFilter("DatadogLambdaDestination", {
2500
+ destination: lambdaDestination,
2501
+ filterPattern: FilterPattern.allEvents(),
2502
+ });
2503
+ }
2504
+ // Create the hosted zone
2505
+ this.hostedZone = new HostedZone(this, "HostedZone", {
2506
+ queryLogsLogGroupArn: this.logGroup.logGroupArn,
2507
+ zoneName,
2508
+ });
2509
+ // Add tags
2510
+ cdk.Tags.of(this.hostedZone).add(CDK$2.TAG.SERVICE, service);
2511
+ cdk.Tags.of(this.hostedZone).add(CDK$2.TAG.ROLE, CDK$2.ROLE.NETWORKING);
2512
+ if (project) {
2513
+ cdk.Tags.of(this.hostedZone).add(CDK$2.TAG.PROJECT, project);
2514
+ }
2515
+ // Create DNS records if provided
2516
+ this.dnsRecords = [];
2517
+ if (props.records) {
2518
+ props.records.forEach((recordConfig, index) => {
2519
+ const { id, ...recordProps } = recordConfig;
2520
+ // Generate a default ID if not provided
2521
+ const recordId = id ||
2522
+ `${recordProps.type}${recordProps.recordName ? `-${recordProps.recordName}` : ""}-${index}`;
2523
+ const dnsRecord = new JaypieDnsRecord(this, recordId, {
2524
+ ...recordProps,
2525
+ zone: this.hostedZone,
2526
+ });
2527
+ this.dnsRecords.push(dnsRecord);
2528
+ });
2529
+ }
2530
+ }
2531
+ }
2532
+
2533
+ const CDK = {
2534
+ TAG: {
2535
+ STACK_SHA: "stackSha",
2536
+ },
2537
+ };
2538
+ class JaypieInfrastructureStack extends JaypieStack {
2539
+ constructor(scope, id, props = {}) {
2540
+ const { key = "infra", ...stackProps } = props;
2541
+ // Handle stackName
2542
+ if (!stackProps.stackName) {
2543
+ stackProps.stackName = constructStackName(key);
2544
+ }
2545
+ super(scope, id, { key, ...stackProps });
2546
+ // Add infrastructure-specific tag
2547
+ if (process.env.CDK_ENV_INFRASTRUCTURE_STACK_SHA) {
2548
+ Tags.of(this).add(CDK.TAG.STACK_SHA, process.env.CDK_ENV_INFRASTRUCTURE_STACK_SHA);
2549
+ }
2550
+ }
2551
+ }
2552
+
2553
+ class JaypieMongoDbSecret extends JaypieEnvSecret {
2554
+ constructor(scope, id = "MongoConnectionString", props) {
2555
+ const defaultProps = {
2556
+ envKey: "MONGODB_URI",
2557
+ roleTag: CDK$2.ROLE.STORAGE,
2558
+ vendorTag: CDK$2.VENDOR.MONGODB,
2559
+ ...props,
2560
+ };
2561
+ super(scope, id, defaultProps);
2562
+ }
2563
+ }
2564
+
2565
+ class JaypieNextJs extends Construct {
2566
+ constructor(scope, id, props) {
2567
+ super(scope, id);
2568
+ const domainName = props?.domainName || envHostname();
2569
+ this.domainName = domainName;
2570
+ const domainNameSanitized = domainName
2571
+ .replace(/\./g, "-")
2572
+ .replace(/[^a-zA-Z0-9]/g, "_");
2573
+ // Resolve environment from array or object syntax
2574
+ const environment = resolveEnvironment(props?.environment);
2575
+ const envSecrets = props?.envSecrets || {};
2576
+ const nextjsPath = props?.nextjsPath?.startsWith("..")
2577
+ ? path.join(process.cwd(), props.nextjsPath)
2578
+ : props?.nextjsPath || path.join(process.cwd(), "..", "nextjs");
2579
+ const paramsAndSecrets = resolveParamsAndSecrets();
2580
+ // Resolve secrets from mixed array (strings and JaypieEnvSecret instances)
2581
+ const secrets = resolveSecrets(scope, props?.secrets);
2582
+ // Process secrets environment variables
2583
+ const secretsEnvironment = Object.entries(envSecrets).reduce((acc, [key, secret]) => ({
2584
+ ...acc,
2585
+ [`SECRET_${key}`]: secret.secretName,
2586
+ }), {});
2587
+ // Process JaypieEnvSecret array
2588
+ const jaypieSecretsEnvironment = secrets.reduce((acc, secret) => {
2589
+ if (secret.envKey) {
2590
+ return {
2591
+ ...acc,
2592
+ [`SECRET_${secret.envKey}`]: secret.secretName,
2593
+ };
2594
+ }
2595
+ return acc;
2596
+ }, {});
2597
+ // Process NEXT_PUBLIC_ environment variables
2598
+ const nextPublicEnv = Object.entries(process.env).reduce((acc, [key, value]) => {
2599
+ if (key.startsWith("NEXT_PUBLIC_") && value) {
2600
+ return {
2601
+ ...acc,
2602
+ [key]: value,
2603
+ };
2604
+ }
2605
+ return acc;
2606
+ }, {});
2607
+ const nextjs = new Nextjs(this, "NextJsApp", {
2608
+ nextjsPath,
2609
+ domainProps: {
2610
+ domainName,
2611
+ hostedZone: resolveHostedZone(this, {
2612
+ zone: props?.hostedZone,
2613
+ }),
2614
+ },
2615
+ environment: {
2616
+ ...jaypieLambdaEnv(),
2617
+ ...environment,
2618
+ ...secretsEnvironment,
2619
+ ...jaypieSecretsEnvironment,
2620
+ ...nextPublicEnv,
2621
+ NEXT_PUBLIC_SITE_URL: `https://${domainName}`,
2622
+ },
2623
+ overrides: {
2624
+ nextjsDistribution: {
2625
+ imageCachePolicyProps: {
2626
+ cachePolicyName: `NextJsImageCachePolicy-${domainNameSanitized}`,
2627
+ },
2628
+ serverCachePolicyProps: {
2629
+ cachePolicyName: `NextJsServerCachePolicy-${domainNameSanitized}`,
2630
+ },
2631
+ },
2632
+ nextjsImage: {
2633
+ functionProps: {
2634
+ paramsAndSecrets,
2635
+ },
2636
+ },
2637
+ nextjsServer: {
2638
+ functionProps: {
2639
+ paramsAndSecrets,
2640
+ },
2641
+ },
2642
+ },
2643
+ });
2644
+ addDatadogLayers(nextjs.imageOptimizationFunction);
2645
+ addDatadogLayers(nextjs.serverFunction.lambdaFunction);
2646
+ // Grant secret read permissions
2647
+ Object.values(envSecrets).forEach((secret) => {
2648
+ secret.grantRead(nextjs.serverFunction.lambdaFunction);
2649
+ });
2650
+ // Grant read permissions for JaypieEnvSecrets
2651
+ secrets.forEach((secret) => {
2652
+ secret.grantRead(nextjs.serverFunction.lambdaFunction);
2653
+ });
2654
+ }
2655
+ }
2656
+
2657
+ class JaypieOpenAiSecret extends JaypieEnvSecret {
2658
+ constructor(scope, id = "OpenAiApiKey", props) {
2659
+ const defaultProps = {
2660
+ envKey: "OPENAI_API_KEY",
2661
+ roleTag: CDK$2.ROLE.PROCESSING,
2662
+ vendorTag: CDK$2.VENDOR.OPENAI,
2663
+ ...props,
2664
+ };
2665
+ super(scope, id, defaultProps);
2666
+ }
2667
+ }
2668
+
2669
+ class JaypieOrganizationTrail extends Construct {
2670
+ /**
2671
+ * Create a new organization CloudTrail with S3 bucket and lifecycle policies
2672
+ */
2673
+ constructor(scope, idOrProps, propsOrUndefined) {
2674
+ // Handle overloaded constructor signatures
2675
+ let props;
2676
+ let id;
2677
+ if (typeof idOrProps === "string") {
2678
+ // First param is ID, second is props
2679
+ props = propsOrUndefined || {};
2680
+ id = idOrProps;
2681
+ }
2682
+ else {
2683
+ // First param is props
2684
+ props = idOrProps || {};
2685
+ const defaultName = process.env.PROJECT_NONCE
2686
+ ? `organization-cloudtrail-${process.env.PROJECT_NONCE}`
2687
+ : "organization-cloudtrail";
2688
+ id = props.id || `${props.trailName || defaultName}-Trail`;
2689
+ }
2690
+ super(scope, id);
2691
+ // Resolve options with defaults
2692
+ const { bucketName = process.env.PROJECT_NONCE
2693
+ ? `organization-cloudtrail-${process.env.PROJECT_NONCE}`
2694
+ : "organization-cloudtrail", enableDatadogNotifications = true, enableFileValidation = false, expirationDays = 365, glacierTransitionDays = 180, infrequentAccessTransitionDays = 30, project, service = CDK$2.SERVICE.INFRASTRUCTURE, trailName = process.env.PROJECT_NONCE
2695
+ ? `organization-cloudtrail-${process.env.PROJECT_NONCE}`
2696
+ : "organization-cloudtrail", } = props;
2697
+ // Create the S3 bucket for CloudTrail logs
2698
+ this.bucket = new Bucket(this, "Bucket", {
2699
+ accessControl: BucketAccessControl.LOG_DELIVERY_WRITE,
2700
+ bucketName,
2701
+ lifecycleRules: [
2702
+ {
2703
+ expiration: cdk.Duration.days(expirationDays),
2704
+ transitions: [
2705
+ {
2706
+ storageClass: StorageClass.INFREQUENT_ACCESS,
2707
+ transitionAfter: cdk.Duration.days(infrequentAccessTransitionDays),
2708
+ },
2709
+ {
2710
+ storageClass: StorageClass.GLACIER,
2711
+ transitionAfter: cdk.Duration.days(glacierTransitionDays),
2712
+ },
2713
+ ],
2714
+ },
2715
+ ],
2716
+ });
2717
+ // Add CloudTrail bucket policies
2718
+ this.bucket.addToResourcePolicy(new PolicyStatement({
2719
+ actions: ["s3:GetBucketAcl"],
2720
+ effect: Effect.ALLOW,
2721
+ principals: [new ServicePrincipal("cloudtrail.amazonaws.com")],
2722
+ resources: [this.bucket.bucketArn],
2723
+ }));
2724
+ this.bucket.addToResourcePolicy(new PolicyStatement({
2725
+ actions: ["s3:PutObject"],
2726
+ conditions: {
2727
+ StringEquals: {
2728
+ "s3:x-amz-acl": "bucket-owner-full-control",
2729
+ },
2730
+ },
2731
+ effect: Effect.ALLOW,
2732
+ principals: [new ServicePrincipal("cloudtrail.amazonaws.com")],
2733
+ resources: [`${this.bucket.bucketArn}/*`],
2734
+ }));
2735
+ // Add tags to bucket
2736
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.SERVICE, service);
2737
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
2738
+ if (project) {
2739
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.PROJECT, project);
2740
+ }
2741
+ // Add Datadog notifications if enabled
2742
+ if (enableDatadogNotifications) {
2743
+ const datadogForwarderFunction = resolveDatadogForwarderFunction(scope);
2744
+ this.bucket.addEventNotification(EventType.OBJECT_CREATED, new LambdaDestination(datadogForwarderFunction));
2745
+ }
2746
+ // Create the organization trail
2747
+ this.trail = new Trail(this, "Trail", {
2748
+ bucket: this.bucket,
2749
+ enableFileValidation,
2750
+ isOrganizationTrail: true,
2751
+ managementEvents: ReadWriteType.ALL,
2752
+ trailName,
2753
+ });
2754
+ // Add tags to trail
2755
+ cdk.Tags.of(this.trail).add(CDK$2.TAG.SERVICE, service);
2756
+ cdk.Tags.of(this.trail).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
2757
+ if (project) {
2758
+ cdk.Tags.of(this.trail).add(CDK$2.TAG.PROJECT, project);
2759
+ }
2760
+ }
2761
+ }
2762
+
2763
+ /**
2764
+ * JaypieSsoPermissions Construct
2765
+ *
2766
+ * Creates and manages AWS IAM Identity Center (SSO) permission sets and assignments
2767
+ *
2768
+ * @example
2769
+ * const permissionSets = new JaypieSsoPermissions(this, "PermissionSets", {
2770
+ * iamIdentityCenterArn: "arn:aws:sso:::instance/...",
2771
+ * administratorGroupId: "b4c8b438-4031-7000-782d-5046945fb956",
2772
+ * analystGroupId: "2488f4e8-d061-708e-abe1-c315f0e30005",
2773
+ * developerGroupId: "b438a4f8-e0e1-707c-c6e8-21841daf9ad1",
2774
+ * administratorAccountAssignments: {
2775
+ * "211125635435": ["Administrator", "Analyst", "Developer"],
2776
+ * "381492033431": ["Administrator", "Analyst"],
2777
+ * },
2778
+ * analystAccountAssignments: {
2779
+ * "211125635435": ["Analyst", "Developer"],
2780
+ * "381492033431": [],
2781
+ * },
2782
+ * developerAccountAssignments: {
2783
+ * "211125635435": ["Analyst", "Developer"],
2784
+ * "381492033431": [],
2785
+ * },
2786
+ * });
2787
+ */
2788
+ class JaypieSsoPermissions extends Construct {
2789
+ constructor(scope, id, props) {
2790
+ super(scope, id);
2791
+ const { iamIdentityCenterArn: iamIdentityCenterArnProp, administratorGroupId, analystGroupId, developerGroupId, administratorAccountAssignments, analystAccountAssignments, developerAccountAssignments, } = props;
2792
+ const iamIdentityCenterArn = iamIdentityCenterArnProp || process.env.CDK_ENV_IAM_IDENTITY_CENTER_ARN;
2793
+ if (!iamIdentityCenterArn) {
2794
+ // If no IAM Identity Center ARN provided, skip SSO setup
2795
+ return;
2796
+ }
2797
+ //
2798
+ // Permission Sets
2799
+ //
2800
+ this.administratorPermissionSet = new CfnPermissionSet(this, "AdministratorPermissionSet", {
2801
+ // Required
2802
+ instanceArn: iamIdentityCenterArn,
2803
+ name: "Administrator",
2804
+ // Optional
2805
+ description: "Unrestricted access",
2806
+ inlinePolicy: {
2807
+ Version: "2012-10-17",
2808
+ Statement: [
2809
+ {
2810
+ Effect: "Allow",
2811
+ Action: [
2812
+ "aws-portal:ViewUsage",
2813
+ "aws-portal:ViewBilling",
2814
+ "budgets:*",
2815
+ "cur:DescribeReportDefinitions",
2816
+ "cur:PutReportDefinition",
2817
+ "cur:DeleteReportDefinition",
2818
+ "cur:ModifyReportDefinition",
2819
+ ],
2820
+ Resource: "*",
2821
+ },
2822
+ ],
2823
+ },
2824
+ managedPolicies: [
2825
+ ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess")
2826
+ .managedPolicyArn,
2827
+ ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
2828
+ ],
2829
+ sessionDuration: Duration.hours(1).toIsoString(),
2830
+ tags: [
2831
+ {
2832
+ key: CDK$2.TAG.SERVICE,
2833
+ value: CDK$2.SERVICE.SSO,
2834
+ },
2835
+ {
2836
+ key: CDK$2.TAG.ROLE,
2837
+ value: CDK$2.ROLE.SECURITY,
2838
+ },
2839
+ ],
2840
+ });
2841
+ this.analystPermissionSet = new CfnPermissionSet(this, "AnalystPermissionSet", {
2842
+ // Required
2843
+ instanceArn: iamIdentityCenterArn,
2844
+ name: "Analyst",
2845
+ // Optional
2846
+ description: "Read-only access; may expand to limited write access",
2847
+ inlinePolicy: {
2848
+ Version: "2012-10-17",
2849
+ Statement: [
2850
+ {
2851
+ Effect: "Allow",
2852
+ Action: [
2853
+ "aws-portal:ViewUsage",
2854
+ "aws-portal:ViewBilling",
2855
+ "budgets:Describe*",
2856
+ "budgets:View*",
2857
+ "ce:Get*",
2858
+ "ce:List*",
2859
+ "cloudformation:Describe*",
2860
+ "cloudformation:Get*",
2861
+ "cloudformation:List*",
2862
+ "cloudwatch:BatchGet*",
2863
+ "cloudwatch:Get*",
2864
+ "cloudwatch:List*",
2865
+ "cost-optimization-hub:Get*",
2866
+ "cost-optimization-hub:List*",
2867
+ "ec2:Describe*",
2868
+ "ec2:Get*",
2869
+ "ec2:List*",
2870
+ "ec2:Search*",
2871
+ "iam:Get*",
2872
+ "iam:List*",
2873
+ "iam:PassRole",
2874
+ "lambda:Get*",
2875
+ "lambda:List*",
2876
+ "logs:Describe*",
2877
+ "logs:Get*",
2878
+ "logs:List*",
2879
+ "pipes:Describe*",
2880
+ "pipes:List*",
2881
+ "s3:Get*",
2882
+ "s3:List*",
2883
+ "secretsmanager:GetRandomPassword",
2884
+ "secretsmanager:GetResourcePolicy",
2885
+ "secretsmanager:List*",
2886
+ "securityhub:Describe*",
2887
+ "securityhub:Get*",
2888
+ "securityhub:List*",
2889
+ "servicecatalog:Describe*",
2890
+ "sns:Get*",
2891
+ "sns:List*",
2892
+ "sqs:Get*",
2893
+ "sqs:List*",
2894
+ "states:Describe*",
2895
+ "states:Get*",
2896
+ "states:List*",
2897
+ "tag:*",
2898
+ "uxc:*",
2899
+ "xray:*",
2900
+ ],
2901
+ Resource: "*",
2902
+ },
2903
+ ],
2904
+ },
2905
+ managedPolicies: [
2906
+ ManagedPolicy.fromAwsManagedPolicyName("AmazonQDeveloperAccess")
2907
+ .managedPolicyArn,
2908
+ ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
2909
+ ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess")
2910
+ .managedPolicyArn,
2911
+ ],
2912
+ sessionDuration: Duration.hours(12).toIsoString(),
2913
+ tags: [
2914
+ {
2915
+ key: CDK$2.TAG.SERVICE,
2916
+ value: CDK$2.SERVICE.SSO,
2917
+ },
2918
+ {
2919
+ key: CDK$2.TAG.ROLE,
2920
+ value: CDK$2.ROLE.SECURITY,
2921
+ },
2922
+ ],
2923
+ });
2924
+ this.developerPermissionSet = new CfnPermissionSet(this, "DeveloperPermissionSet", {
2925
+ // Required
2926
+ instanceArn: iamIdentityCenterArn,
2927
+ name: "Developer",
2928
+ // Optional
2929
+ description: "Administrative access with limited restrictions",
2930
+ inlinePolicy: {
2931
+ Version: "2012-10-17",
2932
+ Statement: [
2933
+ {
2934
+ Effect: "Allow",
2935
+ Action: [
2936
+ "budgets:*",
2937
+ "ce:*",
2938
+ "cloudformation:*",
2939
+ "cloudwatch:*",
2940
+ "cost-optimization-hub:*",
2941
+ "ec2:*",
2942
+ "iam:Get*",
2943
+ "iam:List*",
2944
+ "iam:PassRole",
2945
+ "lambda:*",
2946
+ "logs:*",
2947
+ "pipes:*",
2948
+ "s3:*",
2949
+ "secretsmanager:*",
2950
+ "securityhub:*",
2951
+ "servicecatalog:*",
2952
+ "sns:*",
2953
+ "sqs:*",
2954
+ "states:*",
2955
+ "tag:*",
2956
+ "uxc:*",
2957
+ "xray:*",
2958
+ ],
2959
+ Resource: "*",
2960
+ },
2961
+ ],
2962
+ },
2963
+ managedPolicies: [
2964
+ ManagedPolicy.fromAwsManagedPolicyName("AmazonQDeveloperAccess")
2965
+ .managedPolicyArn,
2966
+ ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
2967
+ ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess")
2968
+ .managedPolicyArn,
2969
+ ManagedPolicy.fromAwsManagedPolicyName("job-function/SystemAdministrator").managedPolicyArn,
2970
+ ],
2971
+ sessionDuration: Duration.hours(4).toIsoString(),
2972
+ tags: [
2973
+ {
2974
+ key: CDK$2.TAG.SERVICE,
2975
+ value: CDK$2.SERVICE.SSO,
2976
+ },
2977
+ {
2978
+ key: CDK$2.TAG.ROLE,
2979
+ value: CDK$2.ROLE.SECURITY,
2980
+ },
2981
+ ],
2982
+ });
2983
+ // Map permission set names to their ARNs and labels
2984
+ const permissionSetMap = {
2985
+ Administrator: {
2986
+ arn: this.administratorPermissionSet.attrPermissionSetArn,
2987
+ label: "Administrator",
2988
+ },
2989
+ Analyst: {
2990
+ arn: this.analystPermissionSet.attrPermissionSetArn,
2991
+ label: "Analyst",
2992
+ },
2993
+ Developer: {
2994
+ arn: this.developerPermissionSet.attrPermissionSetArn,
2995
+ label: "Developer",
2996
+ },
2997
+ };
2998
+ //
2999
+ // Assignments
3000
+ //
3001
+ // Helper function to create assignments for a group
3002
+ const createAssignments = (groupId, accountAssignments) => {
3003
+ if (!groupId || !accountAssignments) {
3004
+ return; // Skip if group ID or assignments not provided
3005
+ }
3006
+ Object.keys(accountAssignments).forEach((accountId) => {
3007
+ const permissionSetNames = accountAssignments[accountId];
3008
+ permissionSetNames.forEach((permissionSetName) => {
3009
+ const permissionSet = permissionSetMap[permissionSetName];
3010
+ if (!permissionSet) {
3011
+ throw new ConfigurationError(`Unknown permission set: ${permissionSetName}. Valid options: ${Object.keys(permissionSetMap).join(", ")}`);
3012
+ }
3013
+ const accountAssignment = new CfnAssignment(this, `AccountAssignment-${accountId}-${permissionSet.label}Role-${groupId}Group`, {
3014
+ // Required
3015
+ instanceArn: iamIdentityCenterArn,
3016
+ permissionSetArn: permissionSet.arn,
3017
+ principalId: groupId,
3018
+ principalType: CDK$2.PRINCIPAL_TYPE.GROUP,
3019
+ targetId: accountId,
3020
+ targetType: CDK$2.TARGET_TYPE.AWS_ACCOUNT,
3021
+ });
3022
+ Tags.of(accountAssignment).add(CDK$2.TAG.SERVICE, CDK$2.SERVICE.SSO);
3023
+ Tags.of(accountAssignment).add(CDK$2.TAG.ROLE, CDK$2.ROLE.SECURITY);
3024
+ });
3025
+ });
3026
+ };
3027
+ // Create assignments for each group
3028
+ createAssignments(administratorGroupId, administratorAccountAssignments);
3029
+ createAssignments(analystGroupId, analystAccountAssignments);
3030
+ createAssignments(developerGroupId, developerAccountAssignments);
3031
+ }
3032
+ }
3033
+
3034
+ //
3035
+ //
3036
+ // Constants
3037
+ //
3038
+ const DEFAULT_APPLICATION_ID = "arn:aws:serverlessrepo:us-east-2:004480582608:applications/SSOSync";
3039
+ const DEFAULT_APPLICATION_VERSION = "2.3.3";
3040
+ const DEFAULT_GOOGLE_GROUP_MATCH = "name:AWS*";
3041
+ //
3042
+ //
3043
+ // Class
3044
+ //
3045
+ class JaypieSsoSyncApplication extends Construct {
3046
+ constructor(scope, id = "SsoSyncApplication", props = {}) {
3047
+ super(scope, id);
3048
+ const { googleAdminEmail, googleAdminEmailEnvKey = "CDK_ENV_SSOSYNC_GOOGLE_ADMIN_EMAIL", googleCredentials, googleCredentialsEnvKey = "CDK_ENV_SSOSYNC_GOOGLE_CREDENTIALS", googleGroupMatch, googleGroupMatchEnvKey = "CDK_ENV_SSOSYNC_GOOGLE_GROUP_MATCH", identityStoreId, identityStoreIdEnvKey = "CDK_ENV_SSOSYNC_IDENTITY_STORE_ID", scimEndpointAccessToken, scimEndpointAccessTokenEnvKey = "CDK_ENV_SCIM_ENDPOINT_ACCESS_TOKEN", scimEndpointUrl, scimEndpointUrlEnvKey = "CDK_ENV_SSOSYNC_SCIM_ENDPOINT_URL", semanticVersion, semanticVersionEnvKey = "CDK_ENV_SSOSYNC_SEMANTIC_VERSION", ssoSyncApplicationId = DEFAULT_APPLICATION_ID, tags, } = props;
3049
+ // Resolve all values from props or environment variables
3050
+ const resolvedGoogleAdminEmail = googleAdminEmail || process.env[googleAdminEmailEnvKey];
3051
+ const resolvedGoogleCredentials = googleCredentials || process.env[googleCredentialsEnvKey];
3052
+ const resolvedGoogleGroupMatch = googleGroupMatch ||
3053
+ process.env[googleGroupMatchEnvKey] ||
3054
+ DEFAULT_GOOGLE_GROUP_MATCH;
3055
+ const resolvedIdentityStoreId = identityStoreId || process.env[identityStoreIdEnvKey];
3056
+ const resolvedScimEndpointAccessToken = scimEndpointAccessToken || process.env[scimEndpointAccessTokenEnvKey];
3057
+ const resolvedScimEndpointUrl = scimEndpointUrl || process.env[scimEndpointUrlEnvKey];
3058
+ const resolvedSemanticVersion = semanticVersion ||
3059
+ process.env[semanticVersionEnvKey] ||
3060
+ DEFAULT_APPLICATION_VERSION;
3061
+ // Validate required parameters
3062
+ const missingParams = [];
3063
+ if (!resolvedGoogleAdminEmail) {
3064
+ missingParams.push(`googleAdminEmail or ${googleAdminEmailEnvKey} environment variable`);
3065
+ }
3066
+ if (!resolvedGoogleCredentials) {
3067
+ missingParams.push(`googleCredentials or ${googleCredentialsEnvKey} environment variable`);
3068
+ }
3069
+ if (!resolvedIdentityStoreId) {
3070
+ missingParams.push(`identityStoreId or ${identityStoreIdEnvKey} environment variable`);
3071
+ }
3072
+ if (!resolvedScimEndpointAccessToken) {
3073
+ missingParams.push(`scimEndpointAccessToken or ${scimEndpointAccessTokenEnvKey} environment variable`);
3074
+ }
3075
+ if (!resolvedScimEndpointUrl) {
3076
+ missingParams.push(`scimEndpointUrl or ${scimEndpointUrlEnvKey} environment variable`);
3077
+ }
3078
+ if (missingParams.length > 0) {
3079
+ throw new ConfigurationError(`JaypieSsoSyncApplication missing required configuration: ${missingParams.join(", ")}`);
3080
+ }
3081
+ // Create the SSO Sync Application
3082
+ // Type assertion is safe because we validated all required values above
3083
+ this._application = new CfnApplication(this, "Application", {
3084
+ location: {
3085
+ applicationId: ssoSyncApplicationId,
3086
+ semanticVersion: resolvedSemanticVersion,
3087
+ },
3088
+ parameters: {
3089
+ GoogleAdminEmail: resolvedGoogleAdminEmail,
3090
+ GoogleCredentials: resolvedGoogleCredentials,
3091
+ GoogleGroupMatch: resolvedGoogleGroupMatch,
3092
+ IdentityStoreID: resolvedIdentityStoreId,
3093
+ Region: Stack.of(this).region,
3094
+ SCIMEndpointAccessToken: resolvedScimEndpointAccessToken,
3095
+ SCIMEndpointUrl: resolvedScimEndpointUrl,
3096
+ },
3097
+ });
3098
+ // Add tags
3099
+ const defaultTags = {
3100
+ [CDK$2.TAG.ROLE]: CDK$2.ROLE.SECURITY,
3101
+ };
3102
+ const allTags = { ...defaultTags, ...tags };
3103
+ Object.entries(allTags).forEach(([key, value]) => {
3104
+ Tags.of(this._application).add(key, value);
3105
+ });
3106
+ }
3107
+ get application() {
3108
+ return this._application;
3109
+ }
3110
+ }
3111
+
3112
+ class JaypieWebDeploymentBucket extends Construct {
3113
+ constructor(scope, id, props = {}) {
3114
+ super(scope, id);
3115
+ const roleTag = props.roleTag || CDK$2.ROLE.HOSTING;
3116
+ // Environment variable validation
3117
+ if (process.env.CDK_ENV_WEB_SUBDOMAIN &&
3118
+ !isValidSubdomain(process.env.CDK_ENV_WEB_SUBDOMAIN)) {
3119
+ throw new ConfigurationError("CDK_ENV_WEB_SUBDOMAIN is not a valid subdomain");
3120
+ }
3121
+ if (process.env.CDK_ENV_WEB_HOSTED_ZONE &&
3122
+ !isValidHostname$1(process.env.CDK_ENV_WEB_HOSTED_ZONE)) {
3123
+ throw new ConfigurationError("CDK_ENV_WEB_HOSTED_ZONE is not a valid hostname");
3124
+ }
3125
+ if (process.env.CDK_ENV_HOSTED_ZONE &&
3126
+ !isValidHostname$1(process.env.CDK_ENV_HOSTED_ZONE)) {
3127
+ throw new ConfigurationError("CDK_ENV_HOSTED_ZONE is not a valid hostname");
3128
+ }
3129
+ // Determine host from props or environment
3130
+ let host = props.host;
3131
+ if (!host) {
3132
+ try {
3133
+ host =
3134
+ process.env.CDK_ENV_WEB_HOST ||
3135
+ mergeDomain(process.env.CDK_ENV_WEB_SUBDOMAIN || "", process.env.CDK_ENV_WEB_HOSTED_ZONE ||
3136
+ process.env.CDK_ENV_HOSTED_ZONE ||
3137
+ "");
3138
+ }
3139
+ catch {
3140
+ host = undefined;
3141
+ }
3142
+ }
3143
+ if (host && !isValidHostname$1(host)) {
3144
+ throw new ConfigurationError("Host is not a valid hostname");
3145
+ }
3146
+ // Determine zone from props or environment
3147
+ const zone = props.zone ||
3148
+ process.env.CDK_ENV_WEB_HOSTED_ZONE ||
3149
+ process.env.CDK_ENV_HOSTED_ZONE;
3150
+ // Create the S3 bucket
3151
+ this.bucket = new s3.Bucket(this, "DestinationBucket", {
3152
+ accessControl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL,
3153
+ autoDeleteObjects: true,
3154
+ blockPublicAccess: s3.BlockPublicAccess.BLOCK_ACLS_ONLY,
3155
+ bucketName: props.name || constructEnvName("web"),
3156
+ publicReadAccess: true,
3157
+ removalPolicy: RemovalPolicy.DESTROY,
3158
+ versioned: false,
3159
+ websiteErrorDocument: "index.html",
3160
+ websiteIndexDocument: "index.html",
3161
+ ...props,
3162
+ });
3163
+ // Delegate IBucket properties to the bucket
3164
+ this.bucketArn = this.bucket.bucketArn;
3165
+ this.bucketDomainName = this.bucket.bucketDomainName;
3166
+ this.bucketDualStackDomainName = this.bucket.bucketDualStackDomainName;
3167
+ this.bucketName = this.bucket.bucketName;
3168
+ this.bucketRegionalDomainName = this.bucket.bucketRegionalDomainName;
3169
+ this.bucketWebsiteDomainName = this.bucket.bucketWebsiteDomainName;
3170
+ this.bucketWebsiteUrl = this.bucket.bucketWebsiteUrl;
3171
+ this.encryptionKey = this.bucket.encryptionKey;
3172
+ this.isWebsite = this.bucket.isWebsite;
3173
+ this.notificationsHandlerRole = undefined;
3174
+ this.policy = this.bucket.policy;
3175
+ Tags.of(this.bucket).add(CDK$2.TAG.ROLE, roleTag);
3176
+ // Create deployment role if repository is configured
3177
+ let repo;
3178
+ if (process.env.CDK_ENV_REPO) {
3179
+ repo = `repo:${process.env.CDK_ENV_REPO}:*`;
3180
+ }
3181
+ if (repo) {
3182
+ const bucketDeployRole = new Role(this, "DestinationBucketDeployRole", {
3183
+ assumedBy: new FederatedPrincipal(Fn.importValue(CDK$2.IMPORT.OIDC_PROVIDER), {
3184
+ StringLike: {
3185
+ "token.actions.githubusercontent.com:sub": repo,
3186
+ },
3187
+ }, "sts:AssumeRoleWithWebIdentity"),
3188
+ maxSessionDuration: Duration.hours(1),
3189
+ });
3190
+ Tags.of(bucketDeployRole).add(CDK$2.TAG.ROLE, CDK$2.ROLE.DEPLOY);
3191
+ // Allow the role to write to the bucket
3192
+ bucketDeployRole.addToPolicy(new PolicyStatement({
3193
+ effect: Effect.ALLOW,
3194
+ actions: [
3195
+ "s3:DeleteObject",
3196
+ "s3:GetObject",
3197
+ "s3:ListObjectsV2",
3198
+ "s3:PutObject",
3199
+ ],
3200
+ resources: [`${this.bucket.bucketArn}/*`],
3201
+ }));
3202
+ bucketDeployRole.addToPolicy(new PolicyStatement({
3203
+ effect: Effect.ALLOW,
3204
+ actions: ["s3:ListBucket"],
3205
+ resources: [this.bucket.bucketArn],
3206
+ }));
3207
+ // Allow the role to describe the current stack
3208
+ const stack = Stack.of(this);
3209
+ bucketDeployRole.addToPolicy(new PolicyStatement({
3210
+ actions: ["cloudformation:DescribeStacks"],
3211
+ effect: Effect.ALLOW,
3212
+ resources: [
3213
+ `arn:aws:cloudformation:${stack.region}:${stack.account}:stack/${stack.stackName}/*`,
3214
+ ],
3215
+ }));
3216
+ this.deployRoleArn = bucketDeployRole.roleArn;
3217
+ // Output the deploy role ARN
3218
+ new CfnOutput(this, "DestinationBucketDeployRoleArn", {
3219
+ value: bucketDeployRole.roleArn,
3220
+ });
3221
+ }
3222
+ // Create CloudFront distribution and certificate if host and zone are provided
3223
+ if (host && zone) {
3224
+ let hostedZone;
3225
+ if (typeof zone === "string") {
3226
+ hostedZone = route53.HostedZone.fromLookup(this, "HostedZone", {
3227
+ domainName: zone,
3228
+ });
3229
+ }
3230
+ else if (zone instanceof JaypieHostedZone) {
3231
+ hostedZone = zone.hostedZone;
3232
+ }
3233
+ else {
3234
+ hostedZone = zone;
3235
+ }
3236
+ // Create certificate if not provided
3237
+ if (props.certificate !== false) {
3238
+ this.certificate =
3239
+ typeof props.certificate === "object"
3240
+ ? props.certificate
3241
+ : new acm.Certificate(this, "Certificate", {
3242
+ domainName: host,
3243
+ validation: acm.CertificateValidation.fromDns(hostedZone),
3244
+ });
3245
+ new CfnOutput(this, "CertificateArn", {
3246
+ value: this.certificate.certificateArn,
3247
+ });
3248
+ Tags.of(this.certificate).add(CDK$2.TAG.ROLE, roleTag);
3249
+ }
3250
+ // Create CloudFront distribution
3251
+ this.distribution = new cloudfront.Distribution(this, "Distribution", {
3252
+ defaultBehavior: {
3253
+ cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
3254
+ origin: new origins.S3StaticWebsiteOrigin(this.bucket),
3255
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
3256
+ },
3257
+ certificate: this.certificate,
3258
+ domainNames: [host],
3259
+ });
3260
+ Tags.of(this.distribution).add(CDK$2.TAG.ROLE, roleTag);
3261
+ // If this is production, enable caching on everything but index.html
3262
+ if (isProductionEnv()) {
3263
+ this.distribution.addBehavior("/*", new origins.S3StaticWebsiteOrigin(this.bucket), {
3264
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
3265
+ cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
3266
+ });
3267
+ }
3268
+ // Create DNS record
3269
+ const record = new route53.ARecord(this, "AliasRecord", {
3270
+ recordName: host,
3271
+ target: route53.RecordTarget.fromAlias(new route53Targets.CloudFrontTarget(this.distribution)),
3272
+ zone: hostedZone,
3273
+ });
3274
+ Tags.of(record).add(CDK$2.TAG.ROLE, CDK$2.ROLE.NETWORKING);
3275
+ this.distributionDomainName = this.distribution.distributionDomainName;
3276
+ }
3277
+ }
3278
+ // Implement remaining IBucket methods by delegating to the bucket
3279
+ addEventNotification(event, dest, ...filters) {
3280
+ this.bucket.addEventNotification(event, dest, ...filters);
3281
+ }
3282
+ addObjectCreatedNotification(dest, ...filters) {
3283
+ this.bucket.addObjectCreatedNotification(dest, ...filters);
3284
+ }
3285
+ addObjectRemovedNotification(dest, ...filters) {
3286
+ this.bucket.addObjectRemovedNotification(dest, ...filters);
3287
+ }
3288
+ addToResourcePolicy(permission) {
3289
+ return this.bucket.addToResourcePolicy(permission);
3290
+ }
3291
+ arnForObjects(keyPattern) {
3292
+ return this.bucket.arnForObjects(keyPattern);
3293
+ }
3294
+ grantDelete(identity, objectsKeyPattern) {
3295
+ return this.bucket.grantDelete(identity, objectsKeyPattern);
3296
+ }
3297
+ grantPublicAccess(allowedActions, keyPrefix) {
3298
+ return keyPrefix
3299
+ ? this.bucket.grantPublicAccess(allowedActions, keyPrefix)
3300
+ : this.bucket.grantPublicAccess(allowedActions);
3301
+ }
3302
+ grantPut(identity, objectsKeyPattern) {
3303
+ return this.bucket.grantPut(identity, objectsKeyPattern);
3304
+ }
3305
+ grantPutAcl(identity, objectsKeyPattern) {
3306
+ return this.bucket.grantPutAcl(identity, objectsKeyPattern);
3307
+ }
3308
+ grantRead(identity, objectsKeyPattern) {
3309
+ return this.bucket.grantRead(identity, objectsKeyPattern);
3310
+ }
3311
+ grantReadWrite(identity, objectsKeyPattern) {
3312
+ return this.bucket.grantReadWrite(identity, objectsKeyPattern);
3313
+ }
3314
+ grantWrite(identity, objectsKeyPattern) {
3315
+ return this.bucket.grantWrite(identity, objectsKeyPattern);
3316
+ }
3317
+ grantReplicationPermission(identity, props) {
3318
+ return this.bucket.grantReplicationPermission(identity, props);
3319
+ }
3320
+ s3UrlForObject(key) {
3321
+ return this.bucket.s3UrlForObject(key);
3322
+ }
3323
+ urlForObject(key) {
3324
+ return this.bucket.urlForObject(key);
3325
+ }
3326
+ virtualHostedUrlForObject(key, options) {
3327
+ return this.bucket.virtualHostedUrlForObject(key, options);
3328
+ }
3329
+ transferAccelerationUrlForObject(key) {
3330
+ return this.bucket.transferAccelerationUrlForObject(key);
3331
+ }
3332
+ onCloudTrailEvent(id, options) {
3333
+ return this.bucket.onCloudTrailEvent(id, options);
3334
+ }
3335
+ onCloudTrailPutObject(id, options) {
3336
+ return this.bucket.onCloudTrailPutObject(id, options);
3337
+ }
3338
+ onCloudTrailWriteObject(id, options) {
3339
+ return this.bucket.onCloudTrailWriteObject(id, options);
3340
+ }
3341
+ addCorsRule(rule) {
3342
+ this.bucket.addCorsRule(rule);
3343
+ }
3344
+ addInventory(inventory) {
3345
+ this.bucket.addInventory(inventory);
3346
+ }
3347
+ addLifecycleRule(rule) {
3348
+ this.bucket.addLifecycleRule(rule);
3349
+ }
3350
+ addMetric(metric) {
3351
+ this.bucket.addMetric(metric);
3352
+ }
3353
+ enableEventBridgeNotification() {
3354
+ this.bucket.enableEventBridgeNotification();
3355
+ }
3356
+ addReplicationPolicy(policy) {
3357
+ this.bucket.addReplicationPolicy(policy);
3358
+ }
3359
+ get stack() {
3360
+ return this.bucket.stack;
3361
+ }
3362
+ get env() {
3363
+ return this.bucket.env;
3364
+ }
3365
+ applyRemovalPolicy(policy) {
3366
+ this.bucket.applyRemovalPolicy(policy);
3367
+ }
3368
+ get bucketRef() {
3369
+ return {
3370
+ bucketArn: this.bucket.bucketArn,
3371
+ bucketName: this.bucket.bucketName,
3372
+ };
3373
+ }
3374
+ }
3375
+
3376
+ class JaypieStaticWebBucket extends JaypieWebDeploymentBucket {
3377
+ constructor(scope, id, props = {}) {
3378
+ // Handle overloaded signatures: (scope), (scope, props), (scope, id, props)
3379
+ let resolvedId;
3380
+ let resolvedProps;
3381
+ if (typeof id === "string") {
3382
+ resolvedId = id;
3383
+ resolvedProps = props;
3384
+ }
3385
+ else if (typeof id === "object") {
3386
+ resolvedId = "JaypieStaticWebBucket";
3387
+ resolvedProps = id;
3388
+ }
3389
+ else {
3390
+ resolvedId = "JaypieStaticWebBucket";
3391
+ resolvedProps = props;
3392
+ }
3393
+ const host = resolvedProps.host ?? envHostname({ subdomain: "static" });
3394
+ const name = resolvedProps.name ?? constructEnvName("static");
3395
+ const roleTag = resolvedProps.roleTag ?? CDK$2.ROLE.HOSTING;
3396
+ // Only use default zone if zone is not explicitly provided (including undefined)
3397
+ const zone = "zone" in resolvedProps
3398
+ ? resolvedProps.zone
3399
+ : process.env.CDK_ENV_DOMAIN || process.env.CDK_ENV_HOSTED_ZONE;
3400
+ super(scope, resolvedId, {
3401
+ ...resolvedProps,
3402
+ host,
3403
+ name,
3404
+ roleTag,
3405
+ zone,
3406
+ });
3407
+ }
3408
+ }
3409
+
3410
+ class JaypieTraceSigningKeySecret extends JaypieEnvSecret {
3411
+ constructor(scope, id = "TraceSigningKey", props) {
3412
+ const defaultProps = {
3413
+ envKey: "TRACE_SIGNING_KEY",
3414
+ roleTag: CDK$2.ROLE.API,
3415
+ vendorTag: CDK$2.VENDOR.KNOWTRACE,
3416
+ ...props,
3417
+ };
3418
+ super(scope, id, defaultProps);
3419
+ }
3420
+ }
3421
+
3422
+ export { CDK$2 as CDK, JaypieAccountLoggingBucket, JaypieApiGateway, JaypieAppStack, JaypieBucketQueuedLambda, JaypieDatadogBucket, JaypieDatadogForwarder, JaypieDatadogSecret, JaypieDistribution, JaypieDnsRecord, JaypieEnvSecret, JaypieEventsRule, JaypieExpressLambda, JaypieGitHubDeployRole, JaypieHostedZone, JaypieInfrastructureStack, JaypieLambda, JaypieMongoDbSecret, JaypieNextJs, JaypieOpenAiSecret, JaypieOrganizationTrail, JaypieQueuedLambda, JaypieSsoPermissions, JaypieSsoSyncApplication, JaypieStack, JaypieStaticWebBucket, JaypieTraceSigningKeySecret, JaypieWebDeploymentBucket, 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 };
3423
+ //# sourceMappingURL=index.js.map