@jaypie/constructs 1.2.52 → 1.2.54

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.
@@ -1,4 +1,4 @@
1
- import { RemovalPolicy } from "aws-cdk-lib";
1
+ import { CfnOutput, RemovalPolicy } from "aws-cdk-lib";
2
2
  import * as acm from "aws-cdk-lib/aws-certificatemanager";
3
3
  import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
4
4
  import { AddToResourcePolicyResult, PolicyStatement } from "aws-cdk-lib/aws-iam";
@@ -123,6 +123,23 @@ export declare class JaypieWebDeploymentBucket extends Construct implements s3.I
123
123
  readonly wafLogBucket?: s3.IBucket;
124
124
  readonly webAcl?: wafv2.CfnWebACL;
125
125
  constructor(scope: Construct, id: string, props?: JaypieWebDeploymentBucketProps);
126
+ /**
127
+ * Emit stack-level CfnOutputs with stable, hash-free logical IDs so they can
128
+ * be read directly from `cdk-outputs.json` without prefix-matching. Skips
129
+ * outputs whose underlying resource is absent.
130
+ *
131
+ * Logical IDs (with optional `prefix`):
132
+ * - `${prefix}DestinationBucketName`
133
+ * - `${prefix}DestinationBucketDeployRoleArn` (when a deploy role exists)
134
+ * - `${prefix}DistributionId` (when a distribution exists)
135
+ * - `${prefix}CertificateArn` (when a certificate exists)
136
+ *
137
+ * @returns map of created outputs keyed by their logical ID
138
+ */
139
+ exportOutputs(options?: {
140
+ prefix?: string;
141
+ scope?: Construct;
142
+ }): Record<string, CfnOutput>;
126
143
  private resolveWafConfig;
127
144
  private isExportNameObject;
128
145
  private resolveLogBucket;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Build a WAF log bucket name shaped like
3
+ * `aws-waf-logs-${env}-${key}-${name}-waf-${nonce}` (or `-waf-` only when
4
+ * `name` is empty). The `aws-waf-logs-` prefix is required by AWS WAF, and
5
+ * `-${PROJECT_NONCE}` is preserved verbatim for uniqueness; the middle is
6
+ * truncated when needed to fit S3's 63-char limit.
7
+ */
8
+ export declare function constructWafLogBucketName(name?: string): string;
@@ -2,6 +2,7 @@ export { addDatadogLayers } from "./addDatadogLayers";
2
2
  export { constructEnvName } from "./constructEnvName";
3
3
  export { constructStackName } from "./constructStackName";
4
4
  export { constructTagger } from "./constructTagger";
5
+ export { constructWafLogBucketName } from "./constructWafLogBucketName";
5
6
  export { envHostname, HostConfig } from "./envHostname";
6
7
  export { extendDatadogRole, ExtendDatadogRoleOptions, } from "./extendDatadogRole";
7
8
  export { clearAllCertificateCaches, clearCertificateCache, resolveCertificate, ResolveCertificateOptions, } from "./resolveCertificate";
@@ -9,7 +9,7 @@ var route53Targets = require('aws-cdk-lib/aws-route53-targets');
9
9
  var secretsmanager = require('aws-cdk-lib/aws-secretsmanager');
10
10
  var datadogCdkConstructsV2 = require('datadog-cdk-constructs-v2');
11
11
  var errors = require('@jaypie/errors');
12
- var awsIam = require('aws-cdk-lib/aws-iam');
12
+ var iam = require('aws-cdk-lib/aws-iam');
13
13
  var acm = require('aws-cdk-lib/aws-certificatemanager');
14
14
  var logs = require('aws-cdk-lib/aws-logs');
15
15
  var lambda = require('aws-cdk-lib/aws-lambda');
@@ -56,6 +56,7 @@ var apiGateway__namespace = /*#__PURE__*/_interopNamespaceDefault(apiGateway);
56
56
  var route53__namespace = /*#__PURE__*/_interopNamespaceDefault(route53);
57
57
  var route53Targets__namespace = /*#__PURE__*/_interopNamespaceDefault(route53Targets);
58
58
  var secretsmanager__namespace = /*#__PURE__*/_interopNamespaceDefault(secretsmanager);
59
+ var iam__namespace = /*#__PURE__*/_interopNamespaceDefault(iam);
59
60
  var acm__namespace = /*#__PURE__*/_interopNamespaceDefault(acm);
60
61
  var logs__namespace = /*#__PURE__*/_interopNamespaceDefault(logs);
61
62
  var lambda__namespace = /*#__PURE__*/_interopNamespaceDefault(lambda);
@@ -401,6 +402,29 @@ function constructTagger(construct, { name } = {}) {
401
402
  return true;
402
403
  }
403
404
 
405
+ const AWS_WAF_LOGS_PREFIX = "aws-waf-logs-";
406
+ const S3_BUCKET_NAME_MAX_LENGTH = 63;
407
+ /**
408
+ * Build a WAF log bucket name shaped like
409
+ * `aws-waf-logs-${env}-${key}-${name}-waf-${nonce}` (or `-waf-` only when
410
+ * `name` is empty). The `aws-waf-logs-` prefix is required by AWS WAF, and
411
+ * `-${PROJECT_NONCE}` is preserved verbatim for uniqueness; the middle is
412
+ * truncated when needed to fit S3's 63-char limit.
413
+ */
414
+ function constructWafLogBucketName(name) {
415
+ const nonce = (process.env.PROJECT_NONCE ?? "cfe2").toLowerCase();
416
+ const nonceSuffix = `-${nonce}`;
417
+ const innerName = name ? `${name}-waf` : "waf";
418
+ const middle = constructEnvName(innerName)
419
+ .toLowerCase()
420
+ .slice(0, -nonceSuffix.length);
421
+ const maxMiddleLength = S3_BUCKET_NAME_MAX_LENGTH - AWS_WAF_LOGS_PREFIX.length - nonceSuffix.length;
422
+ const truncated = middle.length > maxMiddleLength
423
+ ? middle.slice(0, maxMiddleLength).replace(/-+$/, "")
424
+ : middle;
425
+ return `${AWS_WAF_LOGS_PREFIX}${truncated}${nonceSuffix}`;
426
+ }
427
+
404
428
  function envHostname({ component, domain, env, subdomain, } = {}) {
405
429
  const resolvedDomain = domain || process.env.CDK_ENV_DOMAIN || process.env.CDK_ENV_HOSTED_ZONE;
406
430
  if (!resolvedDomain) {
@@ -453,22 +477,22 @@ function extendDatadogRole(scope, options) {
453
477
  }
454
478
  const { id = "DatadogCustomPolicy", project, service = CDK$2.SERVICE.DATADOG, } = options || {};
455
479
  // Lookup the Datadog role
456
- const datadogRole = awsIam.Role.fromRoleArn(scope, "DatadogRole", datadogRoleArn);
480
+ const datadogRole = iam.Role.fromRoleArn(scope, "DatadogRole", datadogRoleArn);
457
481
  // Build policy statements
458
482
  const statements = [
459
483
  // Allow view budget
460
- new awsIam.PolicyStatement({
484
+ new iam.PolicyStatement({
461
485
  actions: ["budgets:ViewBudget"],
462
486
  resources: ["*"],
463
487
  }),
464
488
  // Allow describe log groups
465
- new awsIam.PolicyStatement({
489
+ new iam.PolicyStatement({
466
490
  actions: ["logs:DescribeLogGroups"],
467
491
  resources: ["*"],
468
492
  }),
469
493
  ];
470
494
  // Create the custom policy
471
- const datadogCustomPolicy = new awsIam.Policy(scope, id, {
495
+ const datadogCustomPolicy = new iam.Policy(scope, id, {
472
496
  roles: [datadogRole],
473
497
  statements,
474
498
  });
@@ -2304,22 +2328,22 @@ class JaypieDatadogBucket extends constructs.Construct {
2304
2328
  }
2305
2329
  const { project, service = CDK$2.SERVICE.DATADOG } = options || {};
2306
2330
  // Lookup the Datadog role
2307
- const datadogRole = awsIam.Role.fromRoleArn(this, "DatadogRole", datadogRoleArn);
2331
+ const datadogRole = iam.Role.fromRoleArn(this, "DatadogRole", datadogRoleArn);
2308
2332
  // Build policy statements for bucket access
2309
2333
  const statements = [
2310
2334
  // Allow list bucket
2311
- new awsIam.PolicyStatement({
2335
+ new iam.PolicyStatement({
2312
2336
  actions: ["s3:ListBucket"],
2313
2337
  resources: [this.bucket.bucketArn],
2314
2338
  }),
2315
2339
  // Allow read and write to the bucket
2316
- new awsIam.PolicyStatement({
2340
+ new iam.PolicyStatement({
2317
2341
  actions: ["s3:GetObject", "s3:PutObject"],
2318
2342
  resources: [`${this.bucket.bucketArn}/*`],
2319
2343
  }),
2320
2344
  ];
2321
2345
  // Create the custom policy
2322
- const datadogBucketPolicy = new awsIam.Policy(this, "DatadogBucketPolicy", {
2346
+ const datadogBucketPolicy = new iam.Policy(this, "DatadogBucketPolicy", {
2323
2347
  roles: [datadogRole],
2324
2348
  statements,
2325
2349
  });
@@ -2765,9 +2789,7 @@ class JaypieDistribution extends constructs.Construct {
2765
2789
  const wafLogBucketId = wafConfig.name
2766
2790
  ? constructEnvName(`${wafConfig.name}-WafLogBucket`)
2767
2791
  : constructEnvName("WafLogBucket");
2768
- const wafLogBucketName = wafConfig.name
2769
- ? `aws-waf-logs-${constructEnvName(`${wafConfig.name}-waf`).toLowerCase()}`
2770
- : `aws-waf-logs-${constructEnvName("waf").toLowerCase()}`;
2792
+ const wafLogBucketName = constructWafLogBucketName(wafConfig.name);
2771
2793
  const createdBucket = new s3__namespace.Bucket(this, wafLogBucketId, {
2772
2794
  bucketName: wafLogBucketName,
2773
2795
  lifecycleRules: [
@@ -3327,8 +3349,8 @@ class JaypieGitHubDeployRole extends constructs.Construct {
3327
3349
  }
3328
3350
  const sponsor = propsSponsor || process.env.PROJECT_SPONSOR || envRepoOrganization;
3329
3351
  // Create the IAM role
3330
- this._role = new awsIam.Role(this, "GitHubActionsRole", {
3331
- assumedBy: new awsIam.FederatedPrincipal(oidcProviderArn, {
3352
+ this._role = new iam.Role(this, "GitHubActionsRole", {
3353
+ assumedBy: new iam.FederatedPrincipal(oidcProviderArn, {
3332
3354
  StringLike: {
3333
3355
  "token.actions.githubusercontent.com:sub": repoRestriction,
3334
3356
  },
@@ -3338,12 +3360,12 @@ class JaypieGitHubDeployRole extends constructs.Construct {
3338
3360
  });
3339
3361
  cdk.Tags.of(this._role).add(CDK$2.TAG.ROLE, CDK$2.ROLE.DEPLOY);
3340
3362
  // Allow the role to access the GitHub OIDC provider
3341
- this._role.addToPolicy(new awsIam.PolicyStatement({
3363
+ this._role.addToPolicy(new iam.PolicyStatement({
3342
3364
  actions: ["sts:AssumeRoleWithWebIdentity"],
3343
3365
  resources: [`arn:aws:iam::${accountId}:oidc-provider/*`],
3344
3366
  }));
3345
3367
  // Allow the role to deploy CDK apps
3346
- this._role.addToPolicy(new awsIam.PolicyStatement({
3368
+ this._role.addToPolicy(new iam.PolicyStatement({
3347
3369
  actions: [
3348
3370
  "cloudformation:CreateStack",
3349
3371
  "cloudformation:DeleteStack",
@@ -3360,12 +3382,12 @@ class JaypieGitHubDeployRole extends constructs.Construct {
3360
3382
  "ssm:GetParameter",
3361
3383
  "ssm:GetParameters",
3362
3384
  ],
3363
- effect: awsIam.Effect.ALLOW,
3385
+ effect: iam.Effect.ALLOW,
3364
3386
  resources: ["*"],
3365
3387
  }));
3366
- this._role.addToPolicy(new awsIam.PolicyStatement({
3388
+ this._role.addToPolicy(new iam.PolicyStatement({
3367
3389
  actions: ["iam:PassRole", "sts:AssumeRole"],
3368
- effect: awsIam.Effect.ALLOW,
3390
+ effect: iam.Effect.ALLOW,
3369
3391
  resources: [
3370
3392
  "arn:aws:iam::*:role/cdk-hnb659fds-deploy-role-*",
3371
3393
  "arn:aws:iam::*:role/cdk-hnb659fds-file-publishing-*",
@@ -3378,14 +3400,14 @@ class JaypieGitHubDeployRole extends constructs.Construct {
3378
3400
  if (!sponsor) {
3379
3401
  throw new errors.ConfigurationError("Cannot grant default ECR permissions without a sponsor. Set sponsor prop, PROJECT_SPONSOR, CDK_ENV_REPO, or PROJECT_REPO, or pass `ecr: false`");
3380
3402
  }
3381
- this._role.addToPolicy(new awsIam.PolicyStatement({
3403
+ this._role.addToPolicy(new iam.PolicyStatement({
3382
3404
  actions: ["ecr:GetAuthorizationToken"],
3383
- effect: awsIam.Effect.ALLOW,
3405
+ effect: iam.Effect.ALLOW,
3384
3406
  resources: ["*"],
3385
3407
  }));
3386
- this._role.addToPolicy(new awsIam.PolicyStatement({
3408
+ this._role.addToPolicy(new iam.PolicyStatement({
3387
3409
  actions: ECR_PUSH_ACTIONS,
3388
- effect: awsIam.Effect.ALLOW,
3410
+ effect: iam.Effect.ALLOW,
3389
3411
  resources: [`arn:aws:ecr:*:${accountId}:repository/${sponsor}-*`],
3390
3412
  }));
3391
3413
  }
@@ -3539,6 +3561,14 @@ class JaypieInfrastructureStack extends JaypieStack {
3539
3561
  }
3540
3562
  }
3541
3563
 
3564
+ const DYNAMODB_CONTROL_PLANE_ACTIONS = [
3565
+ "dynamodb:DescribeContinuousBackups",
3566
+ "dynamodb:DescribeTable",
3567
+ "dynamodb:DescribeTimeToLive",
3568
+ "dynamodb:UpdateContinuousBackups",
3569
+ "dynamodb:UpdateTable",
3570
+ "dynamodb:UpdateTimeToLive",
3571
+ ];
3542
3572
  class JaypieMigration extends constructs.Construct {
3543
3573
  constructor(scope, id, props) {
3544
3574
  super(scope, id);
@@ -3554,6 +3584,18 @@ class JaypieMigration extends constructs.Construct {
3554
3584
  tables,
3555
3585
  timeout: cdk__namespace.Duration.minutes(5),
3556
3586
  });
3587
+ // Grant control-plane perms on the passed tables so migrations that
3588
+ // alter table shape (GSIs, TTL, streams, backups) succeed. JaypieLambda
3589
+ // only grants data-plane access via grantReadWriteData. Issue #339.
3590
+ if (tables.length > 0) {
3591
+ this.lambda.addToRolePolicy(new iam__namespace.PolicyStatement({
3592
+ actions: DYNAMODB_CONTROL_PLANE_ACTIONS,
3593
+ resources: tables.flatMap((table) => [
3594
+ table.tableArn,
3595
+ `${table.tableArn}/index/*`,
3596
+ ]),
3597
+ }));
3598
+ }
3557
3599
  // Custom Resource provider wrapping the Lambda
3558
3600
  const provider = new cr__namespace.Provider(this, "MigrationProvider", {
3559
3601
  onEventHandler: this.lambda,
@@ -3816,21 +3858,21 @@ class JaypieOrganizationTrail extends constructs.Construct {
3816
3858
  ],
3817
3859
  });
3818
3860
  // Add CloudTrail bucket policies
3819
- this.bucket.addToResourcePolicy(new awsIam.PolicyStatement({
3861
+ this.bucket.addToResourcePolicy(new iam.PolicyStatement({
3820
3862
  actions: ["s3:GetBucketAcl"],
3821
- effect: awsIam.Effect.ALLOW,
3822
- principals: [new awsIam.ServicePrincipal("cloudtrail.amazonaws.com")],
3863
+ effect: iam.Effect.ALLOW,
3864
+ principals: [new iam.ServicePrincipal("cloudtrail.amazonaws.com")],
3823
3865
  resources: [this.bucket.bucketArn],
3824
3866
  }));
3825
- this.bucket.addToResourcePolicy(new awsIam.PolicyStatement({
3867
+ this.bucket.addToResourcePolicy(new iam.PolicyStatement({
3826
3868
  actions: ["s3:PutObject"],
3827
3869
  conditions: {
3828
3870
  StringEquals: {
3829
3871
  "s3:x-amz-acl": "bucket-owner-full-control",
3830
3872
  },
3831
3873
  },
3832
- effect: awsIam.Effect.ALLOW,
3833
- principals: [new awsIam.ServicePrincipal("cloudtrail.amazonaws.com")],
3874
+ effect: iam.Effect.ALLOW,
3875
+ principals: [new iam.ServicePrincipal("cloudtrail.amazonaws.com")],
3834
3876
  resources: [`${this.bucket.bucketArn}/*`],
3835
3877
  }));
3836
3878
  // Add tags to bucket
@@ -3945,9 +3987,9 @@ class JaypieSsoPermissions extends constructs.Construct {
3945
3987
  ],
3946
3988
  },
3947
3989
  managedPolicies: [
3948
- awsIam.ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess")
3990
+ iam.ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess")
3949
3991
  .managedPolicyArn,
3950
- awsIam.ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
3992
+ iam.ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
3951
3993
  ],
3952
3994
  sessionDuration: cdk.Duration.hours(1).toIsoString(),
3953
3995
  tags: [
@@ -4026,10 +4068,10 @@ class JaypieSsoPermissions extends constructs.Construct {
4026
4068
  ],
4027
4069
  },
4028
4070
  managedPolicies: [
4029
- awsIam.ManagedPolicy.fromAwsManagedPolicyName("AmazonQDeveloperAccess")
4071
+ iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonQDeveloperAccess")
4030
4072
  .managedPolicyArn,
4031
- awsIam.ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
4032
- awsIam.ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess")
4073
+ iam.ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
4074
+ iam.ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess")
4033
4075
  .managedPolicyArn,
4034
4076
  ],
4035
4077
  sessionDuration: cdk.Duration.hours(12).toIsoString(),
@@ -4088,12 +4130,12 @@ class JaypieSsoPermissions extends constructs.Construct {
4088
4130
  ],
4089
4131
  },
4090
4132
  managedPolicies: [
4091
- awsIam.ManagedPolicy.fromAwsManagedPolicyName("AmazonQDeveloperAccess")
4133
+ iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonQDeveloperAccess")
4092
4134
  .managedPolicyArn,
4093
- awsIam.ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
4094
- awsIam.ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess")
4135
+ iam.ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
4136
+ iam.ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess")
4095
4137
  .managedPolicyArn,
4096
- awsIam.ManagedPolicy.fromAwsManagedPolicyName("job-function/SystemAdministrator").managedPolicyArn,
4138
+ iam.ManagedPolicy.fromAwsManagedPolicyName("job-function/SystemAdministrator").managedPolicyArn,
4097
4139
  ],
4098
4140
  sessionDuration: cdk.Duration.hours(4).toIsoString(),
4099
4141
  tags: [
@@ -4324,8 +4366,8 @@ class JaypieWebDeploymentBucket extends constructs.Construct {
4324
4366
  }
4325
4367
  let bucketDeployRole;
4326
4368
  if (repo) {
4327
- bucketDeployRole = new awsIam.Role(this, "DestinationBucketDeployRole", {
4328
- assumedBy: new awsIam.FederatedPrincipal(cdk.Fn.importValue(CDK$2.IMPORT.OIDC_PROVIDER), {
4369
+ bucketDeployRole = new iam.Role(this, "DestinationBucketDeployRole", {
4370
+ assumedBy: new iam.FederatedPrincipal(cdk.Fn.importValue(CDK$2.IMPORT.OIDC_PROVIDER), {
4329
4371
  StringLike: {
4330
4372
  "token.actions.githubusercontent.com:sub": repo,
4331
4373
  },
@@ -4334,8 +4376,8 @@ class JaypieWebDeploymentBucket extends constructs.Construct {
4334
4376
  });
4335
4377
  cdk.Tags.of(bucketDeployRole).add(CDK$2.TAG.ROLE, CDK$2.ROLE.DEPLOY);
4336
4378
  // Allow the role to write to the bucket
4337
- bucketDeployRole.addToPolicy(new awsIam.PolicyStatement({
4338
- effect: awsIam.Effect.ALLOW,
4379
+ bucketDeployRole.addToPolicy(new iam.PolicyStatement({
4380
+ effect: iam.Effect.ALLOW,
4339
4381
  actions: [
4340
4382
  "s3:DeleteObject",
4341
4383
  "s3:GetObject",
@@ -4344,16 +4386,16 @@ class JaypieWebDeploymentBucket extends constructs.Construct {
4344
4386
  ],
4345
4387
  resources: [`${this.bucket.bucketArn}/*`],
4346
4388
  }));
4347
- bucketDeployRole.addToPolicy(new awsIam.PolicyStatement({
4348
- effect: awsIam.Effect.ALLOW,
4389
+ bucketDeployRole.addToPolicy(new iam.PolicyStatement({
4390
+ effect: iam.Effect.ALLOW,
4349
4391
  actions: ["s3:ListBucket"],
4350
4392
  resources: [this.bucket.bucketArn],
4351
4393
  }));
4352
4394
  // Allow the role to describe the current stack
4353
4395
  const stack = cdk.Stack.of(this);
4354
- bucketDeployRole.addToPolicy(new awsIam.PolicyStatement({
4396
+ bucketDeployRole.addToPolicy(new iam.PolicyStatement({
4355
4397
  actions: ["cloudformation:DescribeStacks"],
4356
- effect: awsIam.Effect.ALLOW,
4398
+ effect: iam.Effect.ALLOW,
4357
4399
  resources: [
4358
4400
  `arn:aws:cloudformation:${stack.region}:${stack.account}:stack/${stack.stackName}/*`,
4359
4401
  ],
@@ -4540,8 +4582,8 @@ class JaypieWebDeploymentBucket extends constructs.Construct {
4540
4582
  });
4541
4583
  // Add CloudFront invalidation permission to deploy role if it exists
4542
4584
  if (bucketDeployRole) {
4543
- bucketDeployRole.addToPolicy(new awsIam.PolicyStatement({
4544
- effect: awsIam.Effect.ALLOW,
4585
+ bucketDeployRole.addToPolicy(new iam.PolicyStatement({
4586
+ effect: iam.Effect.ALLOW,
4545
4587
  actions: ["cloudfront:CreateInvalidation"],
4546
4588
  resources: [
4547
4589
  `arn:aws:cloudfront::${cdk.Stack.of(this).account}:distribution/${this.distribution.distributionId}`,
@@ -4622,7 +4664,7 @@ class JaypieWebDeploymentBucket extends constructs.Construct {
4622
4664
  let wafLogBucket;
4623
4665
  if (wafLogBucketProp === true) {
4624
4666
  const wafLogBucketId = constructEnvName(`${wafConfig.name}-WafLogBucket`);
4625
- const wafLogBucketName = `aws-waf-logs-${constructEnvName(`${wafConfig.name}-waf`).toLowerCase()}`;
4667
+ const wafLogBucketName = constructWafLogBucketName(wafConfig.name);
4626
4668
  const createdBucket = new s3__namespace.Bucket(this, wafLogBucketId, {
4627
4669
  bucketName: wafLogBucketName,
4628
4670
  lifecycleRules: [
@@ -4661,6 +4703,41 @@ class JaypieWebDeploymentBucket extends constructs.Construct {
4661
4703
  }
4662
4704
  }
4663
4705
  }
4706
+ /**
4707
+ * Emit stack-level CfnOutputs with stable, hash-free logical IDs so they can
4708
+ * be read directly from `cdk-outputs.json` without prefix-matching. Skips
4709
+ * outputs whose underlying resource is absent.
4710
+ *
4711
+ * Logical IDs (with optional `prefix`):
4712
+ * - `${prefix}DestinationBucketName`
4713
+ * - `${prefix}DestinationBucketDeployRoleArn` (when a deploy role exists)
4714
+ * - `${prefix}DistributionId` (when a distribution exists)
4715
+ * - `${prefix}CertificateArn` (when a certificate exists)
4716
+ *
4717
+ * @returns map of created outputs keyed by their logical ID
4718
+ */
4719
+ exportOutputs(options = {}) {
4720
+ const { prefix = "", scope = cdk.Stack.of(this) } = options;
4721
+ const outputs = {};
4722
+ const create = (id, value) => {
4723
+ const logicalId = `${prefix}${id}`;
4724
+ const output = new cdk.CfnOutput(scope, `${logicalId}Export`, { value });
4725
+ output.overrideLogicalId(logicalId);
4726
+ outputs[logicalId] = output;
4727
+ return output;
4728
+ };
4729
+ create("DestinationBucketName", this.bucket.bucketName);
4730
+ if (this.deployRoleArn) {
4731
+ create("DestinationBucketDeployRoleArn", this.deployRoleArn);
4732
+ }
4733
+ if (this.distribution) {
4734
+ create("DistributionId", this.distribution.distributionId);
4735
+ }
4736
+ if (this.certificate) {
4737
+ create("CertificateArn", this.certificate.certificateArn);
4738
+ }
4739
+ return outputs;
4740
+ }
4664
4741
  resolveWafConfig(wafProp, defaultName) {
4665
4742
  if (wafProp === false)
4666
4743
  return undefined;
@@ -5238,6 +5315,7 @@ exports.clearSecretsCache = clearSecretsCache;
5238
5315
  exports.constructEnvName = constructEnvName;
5239
5316
  exports.constructStackName = constructStackName;
5240
5317
  exports.constructTagger = constructTagger;
5318
+ exports.constructWafLogBucketName = constructWafLogBucketName;
5241
5319
  exports.ensureRoute53QueryLoggingPolicy = ensureRoute53QueryLoggingPolicy;
5242
5320
  exports.envHostname = envHostname;
5243
5321
  exports.extendDatadogRole = extendDatadogRole;