@openhi/constructs 0.0.129 → 0.0.131

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.mjs CHANGED
@@ -1526,6 +1526,8 @@ import { CloudFrontTarget } from "aws-cdk-lib/aws-route53-targets";
1526
1526
  import { Bucket as Bucket2 } from "aws-cdk-lib/aws-s3";
1527
1527
  import { Construct as Construct8 } from "constructs";
1528
1528
  var STATIC_HOSTING_SERVICE_TYPE = "website";
1529
+ var PER_BRANCH_PREVIEW_PREFIX = "admin-pr-";
1530
+ var DEFAULT_PREVIEW_EXPIRATION_DAYS = 14;
1529
1531
  var _StaticHosting = class _StaticHosting extends Construct8 {
1530
1532
  /**
1531
1533
  * Returns true when `domainName` begins with a wildcard label (`*.`),
@@ -1535,11 +1537,19 @@ var _StaticHosting = class _StaticHosting extends Construct8 {
1535
1537
  static isWildcardDomain(domainName) {
1536
1538
  return domainName.startsWith("*.");
1537
1539
  }
1538
- constructor(scope, id, props = {}) {
1540
+ constructor(scope, id, props) {
1539
1541
  super(scope, id);
1540
1542
  const stack = OpenHiService.of(scope);
1541
1543
  const serviceType = props.serviceType ?? STATIC_HOSTING_SERVICE_TYPE;
1542
1544
  const hostingMode = props.hostingMode ?? "spa";
1545
+ const previewLifecycleRules = props.enablePreviewLifecycle ? [
1546
+ {
1547
+ id: "expire-pr-previews",
1548
+ enabled: true,
1549
+ prefix: props.prefixPattern,
1550
+ expiration: props.previewExpiration ?? Duration5.days(DEFAULT_PREVIEW_EXPIRATION_DAYS)
1551
+ }
1552
+ ] : void 0;
1543
1553
  this.bucket = new Bucket2(this, "bucket", {
1544
1554
  blockPublicAccess: {
1545
1555
  blockPublicAcls: true,
@@ -1547,6 +1557,9 @@ var _StaticHosting = class _StaticHosting extends Construct8 {
1547
1557
  ignorePublicAcls: true,
1548
1558
  restrictPublicBuckets: true
1549
1559
  },
1560
+ ...previewLifecycleRules !== void 0 && {
1561
+ lifecycleRules: previewLifecycleRules
1562
+ },
1550
1563
  ...props.bucketProps
1551
1564
  });
1552
1565
  const handlerJs = path6.join(
@@ -3192,7 +3205,9 @@ _OpenHiGraphqlService.SERVICE_TYPE = "graphql-api";
3192
3205
  var OpenHiGraphqlService = _OpenHiGraphqlService;
3193
3206
 
3194
3207
  // src/services/open-hi-website-service.ts
3208
+ var import_config5 = __toESM(require_lib2());
3195
3209
  import { Bucket as Bucket3 } from "aws-cdk-lib/aws-s3";
3210
+ var OPENHI_PR_NUMBER_ENV_VAR = "OPENHI_PR_NUMBER";
3196
3211
  var SSM_PARAM_NAME_FULL_DOMAIN = "WEBSITE_FULL_DOMAIN";
3197
3212
  var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
3198
3213
  /**
@@ -3252,9 +3267,16 @@ var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
3252
3267
  super(ohEnv, _OpenHiWebsiteService.SERVICE_TYPE, props);
3253
3268
  this.props = props;
3254
3269
  this.validateConfig(props);
3270
+ const isReleaseBranch = this.branchName === this.defaultReleaseBranch;
3271
+ this.prNumber = this.resolvePrNumber(props);
3272
+ if (!isReleaseBranch && this.prNumber === void 0) {
3273
+ throw new Error(
3274
+ `OpenHiWebsiteService: prNumber is required on non-release-branch deploys (branchName="${this.branchName}", defaultReleaseBranch="${this.defaultReleaseBranch}"). Pass the \`prNumber\` prop or set the ${OPENHI_PR_NUMBER_ENV_VAR} env var.`
3275
+ );
3276
+ }
3255
3277
  const hostedZone = this.createHostedZone();
3256
3278
  this.fullDomain = this.computeFullDomain(hostedZone);
3257
- const shouldCreateHostingInfra = props.createHostingInfrastructure ?? this.branchName === this.defaultReleaseBranch;
3279
+ const shouldCreateHostingInfra = props.createHostingInfrastructure ?? isReleaseBranch;
3258
3280
  if (shouldCreateHostingInfra) {
3259
3281
  const certificate = this.createCertificate();
3260
3282
  this.staticHosting = this.createStaticHosting({
@@ -3262,6 +3284,8 @@ var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
3262
3284
  hostedZone
3263
3285
  });
3264
3286
  this.createFullDomainParameter();
3287
+ } else if (!isReleaseBranch) {
3288
+ this.perBranchHostname = this.createPerBranchHostname(hostedZone);
3265
3289
  }
3266
3290
  if (props.createStaticContent !== false) {
3267
3291
  const bucket = this.resolveStaticHostingBucket();
@@ -3304,25 +3328,76 @@ var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
3304
3328
  return OpenHiGlobalService.rootWildcardCertificateFromConstruct(this);
3305
3329
  }
3306
3330
  /**
3307
- * Computes the full website domain from `domainPrefix` and the child
3308
- * zone name.
3331
+ * Resolves the PR number from props or the `OPENHI_PR_NUMBER` env var.
3332
+ * Returns `undefined` on release-branch deploys where no PR number is
3333
+ * needed.
3334
+ */
3335
+ resolvePrNumber(props) {
3336
+ if (props.prNumber !== void 0) {
3337
+ return props.prNumber;
3338
+ }
3339
+ const raw = process.env[OPENHI_PR_NUMBER_ENV_VAR]?.trim();
3340
+ if (!raw) {
3341
+ return void 0;
3342
+ }
3343
+ const parsed = Number.parseInt(raw, 10);
3344
+ if (!Number.isInteger(parsed) || parsed <= 0) {
3345
+ throw new Error(
3346
+ `${OPENHI_PR_NUMBER_ENV_VAR} must be a positive integer; got "${raw}".`
3347
+ );
3348
+ }
3349
+ return parsed;
3350
+ }
3351
+ /**
3352
+ * Computes the full website domain from `domainPrefix`, the PR number,
3353
+ * and the child zone name. Release-branch deploys serve at
3354
+ * `\<domainPrefix\>.\<zone\>` (e.g. `admin.dev.openhi.org`); every other
3355
+ * deploy serves a per-PR preview at `\<domainPrefix\>-pr-\<N\>.\<zone\>`
3356
+ * (e.g. `admin-pr-123.dev.openhi.org`).
3309
3357
  */
3310
3358
  computeFullDomain(hostedZone) {
3311
- const prefix = this.props.domainPrefix ?? "www";
3312
- return [prefix, hostedZone.zoneName].join(".");
3359
+ const subDomain = this.computeSubDomain();
3360
+ return [subDomain, hostedZone.zoneName].join(".");
3361
+ }
3362
+ /**
3363
+ * Returns the sub-domain label (left of the zone) for the current
3364
+ * deploy. Used both for {@link fullDomain} and for the per-branch S3
3365
+ * key prefix passed to {@link StaticContent} so the upload prefix
3366
+ * always matches the served hostname.
3367
+ *
3368
+ * Non-release deploys compose the per-PR slug from
3369
+ * {@link PER_BRANCH_PREVIEW_PREFIX} so the per-PR S3 key prefix
3370
+ * matches what `StaticHosting`'s lifecycle rule expires.
3371
+ */
3372
+ computeSubDomain() {
3373
+ const isReleaseBranch = this.branchName === this.defaultReleaseBranch;
3374
+ if (isReleaseBranch) {
3375
+ return this.props.domainPrefix ?? "www";
3376
+ }
3377
+ return `${PER_BRANCH_PREVIEW_PREFIX}${this.prNumber}`;
3313
3378
  }
3314
3379
  /**
3315
3380
  * Creates the StaticHosting infrastructure (bucket + distribution +
3316
- * Lambda@Edge + 4 SSM params + DNS).
3381
+ * Lambda@Edge + 4 SSM params + DNS). The release-branch distribution
3382
+ * adds `*.\<zone\>` as a wildcard alt-name on top of the canonical
3383
+ * hostname so per-PR previews resolve via the same distribution.
3384
+ *
3385
+ * The bucket carries an S3 lifecycle rule that expires per-PR
3386
+ * preview content (keys under {@link PER_BRANCH_PREVIEW_PREFIX})
3387
+ * on non-production stages. PROD never gets the rule — see
3388
+ * `enablePreviewLifecycle`.
3317
3389
  */
3318
3390
  createStaticHosting(deps) {
3319
3391
  const restApi = this.props.restApi === true ? this.resolveRestApi() : void 0;
3392
+ const wildcardSan = `*.${deps.hostedZone.zoneName}`;
3320
3393
  return new StaticHosting(this, "static-hosting", {
3321
3394
  serviceType: _OpenHiWebsiteService.SERVICE_TYPE,
3322
3395
  certificate: deps.certificate,
3323
3396
  hostedZone: deps.hostedZone,
3324
- domainNames: [this.fullDomain],
3397
+ domainNames: [this.fullDomain, wildcardSan],
3325
3398
  description: `OpenHI website (${this.fullDomain})`,
3399
+ prefixPattern: PER_BRANCH_PREVIEW_PREFIX,
3400
+ enablePreviewLifecycle: this.ohEnv.ohStage.stageType !== import_config5.OPEN_HI_STAGE.PROD,
3326
3401
  ...restApi !== void 0 && { restApi }
3327
3402
  });
3328
3403
  }
@@ -3356,6 +3431,12 @@ var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
3356
3431
  * the release-branch deploy publishes to SSM, addressed against
3357
3432
  * {@link OpenHiService.releaseBranchHash}. See
3358
3433
  * {@link resolveStaticHostingBucket}.
3434
+ *
3435
+ * The S3 key prefix is `\<sub-domain\>.\<zone\>/\<contentDest\>` so the
3436
+ * upload location matches the Host-header-derived folder the Lambda@Edge
3437
+ * viewer-request handler prepends. Passing the zone name (rather than
3438
+ * `this.fullDomain`) for the `fullDomain` prop keeps the prefix flat
3439
+ * — `admin-pr-123.dev.openhi.org/`, not `admin-pr-123.admin.dev.openhi.org/`.
3359
3440
  */
3360
3441
  createStaticContent(bucket) {
3361
3442
  const { contentSourceDirectory, contentDestinationDirectory } = this.props;
@@ -3363,7 +3444,21 @@ var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
3363
3444
  bucket,
3364
3445
  contentSourceDirectory,
3365
3446
  contentDestinationDirectory,
3366
- fullDomain: this.fullDomain
3447
+ subDomain: this.computeSubDomain(),
3448
+ fullDomain: this.config.zoneName
3449
+ });
3450
+ }
3451
+ /**
3452
+ * Creates the per-PR `PerBranchHostname` alias record on non-release
3453
+ * branch deploys. The record points `\<domainPrefix\>-pr-\<N\>.\<zone\>`
3454
+ * at the release-branch CloudFront distribution (resolved from SSM
3455
+ * against {@link OpenHiService.releaseBranchHash}).
3456
+ */
3457
+ createPerBranchHostname(hostedZone) {
3458
+ return new PerBranchHostname(this, "per-branch-hostname", {
3459
+ hostname: this.fullDomain,
3460
+ hostedZone,
3461
+ serviceType: _OpenHiWebsiteService.SERVICE_TYPE
3367
3462
  });
3368
3463
  }
3369
3464
  /**
@@ -3922,6 +4017,7 @@ export {
3922
4017
  DATA_STORE_CHANGE_DETAIL_MAX_UTF8_BYTES,
3923
4018
  DATA_STORE_CHANGE_DETAIL_TYPE,
3924
4019
  DATA_STORE_CHANGE_EVENT_SOURCE,
4020
+ DEFAULT_PREVIEW_EXPIRATION_DAYS,
3925
4021
  DEMO_DATA_PLANE_FIXTURES,
3926
4022
  DEMO_PERIOD,
3927
4023
  DEMO_TENANT_SPECS,
@@ -3932,6 +4028,7 @@ export {
3932
4028
  DataStorePostgresReplica,
3933
4029
  DiscoverableStringParameter,
3934
4030
  DynamoDbDataStore,
4031
+ OPENHI_PR_NUMBER_ENV_VAR,
3935
4032
  OPENHI_REPO_TAG_KEY_ENV_VAR,
3936
4033
  OPENHI_RESOURCE_URN_SYSTEM,
3937
4034
  OPENHI_TAG_KEY_PREFIX_ENV_VAR,
@@ -3957,6 +4054,7 @@ export {
3957
4054
  OpsEventBus,
3958
4055
  OwningDeleteCascadeLambdas,
3959
4056
  OwningDeleteCascadeWorkflow,
4057
+ PER_BRANCH_PREVIEW_PREFIX,
3960
4058
  PLACEHOLDER_TENANT_ID,
3961
4059
  PLACEHOLDER_WORKSPACE_ID,
3962
4060
  PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM,