@openhi/constructs 0.0.128 → 0.0.130

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.js CHANGED
@@ -785,6 +785,7 @@ __export(src_exports, {
785
785
  DATA_STORE_CHANGE_DETAIL_MAX_UTF8_BYTES: () => DATA_STORE_CHANGE_DETAIL_MAX_UTF8_BYTES,
786
786
  DATA_STORE_CHANGE_DETAIL_TYPE: () => DATA_STORE_CHANGE_DETAIL_TYPE,
787
787
  DATA_STORE_CHANGE_EVENT_SOURCE: () => DATA_STORE_CHANGE_EVENT_SOURCE,
788
+ DEFAULT_PREVIEW_EXPIRATION_DAYS: () => DEFAULT_PREVIEW_EXPIRATION_DAYS,
788
789
  DEMO_DATA_PLANE_FIXTURES: () => DEMO_DATA_PLANE_FIXTURES,
789
790
  DEMO_PERIOD: () => DEMO_PERIOD,
790
791
  DEMO_TENANT_SPECS: () => DEMO_TENANT_SPECS,
@@ -795,6 +796,7 @@ __export(src_exports, {
795
796
  DataStorePostgresReplica: () => DataStorePostgresReplica,
796
797
  DiscoverableStringParameter: () => DiscoverableStringParameter,
797
798
  DynamoDbDataStore: () => DynamoDbDataStore,
799
+ OPENHI_PR_NUMBER_ENV_VAR: () => OPENHI_PR_NUMBER_ENV_VAR,
798
800
  OPENHI_REPO_TAG_KEY_ENV_VAR: () => OPENHI_REPO_TAG_KEY_ENV_VAR,
799
801
  OPENHI_RESOURCE_URN_SYSTEM: () => OPENHI_RESOURCE_URN_SYSTEM,
800
802
  OPENHI_TAG_KEY_PREFIX_ENV_VAR: () => OPENHI_TAG_KEY_PREFIX_ENV_VAR,
@@ -820,6 +822,7 @@ __export(src_exports, {
820
822
  OpsEventBus: () => OpsEventBus,
821
823
  OwningDeleteCascadeLambdas: () => OwningDeleteCascadeLambdas,
822
824
  OwningDeleteCascadeWorkflow: () => OwningDeleteCascadeWorkflow,
825
+ PER_BRANCH_PREVIEW_PREFIX: () => PER_BRANCH_PREVIEW_PREFIX,
823
826
  PLACEHOLDER_TENANT_ID: () => PLACEHOLDER_TENANT_ID,
824
827
  PLACEHOLDER_WORKSPACE_ID: () => PLACEHOLDER_WORKSPACE_ID,
825
828
  PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM: () => PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM,
@@ -2352,6 +2355,8 @@ var import_aws_route53_targets = require("aws-cdk-lib/aws-route53-targets");
2352
2355
  var import_aws_s3 = require("aws-cdk-lib/aws-s3");
2353
2356
  var import_constructs8 = require("constructs");
2354
2357
  var STATIC_HOSTING_SERVICE_TYPE = "website";
2358
+ var PER_BRANCH_PREVIEW_PREFIX = "admin-pr-";
2359
+ var DEFAULT_PREVIEW_EXPIRATION_DAYS = 14;
2355
2360
  var _StaticHosting = class _StaticHosting extends import_constructs8.Construct {
2356
2361
  /**
2357
2362
  * Returns true when `domainName` begins with a wildcard label (`*.`),
@@ -2361,11 +2366,19 @@ var _StaticHosting = class _StaticHosting extends import_constructs8.Construct {
2361
2366
  static isWildcardDomain(domainName) {
2362
2367
  return domainName.startsWith("*.");
2363
2368
  }
2364
- constructor(scope, id, props = {}) {
2369
+ constructor(scope, id, props) {
2365
2370
  super(scope, id);
2366
2371
  const stack = OpenHiService.of(scope);
2367
2372
  const serviceType = props.serviceType ?? STATIC_HOSTING_SERVICE_TYPE;
2368
2373
  const hostingMode = props.hostingMode ?? "spa";
2374
+ const previewLifecycleRules = props.enablePreviewLifecycle ? [
2375
+ {
2376
+ id: "expire-pr-previews",
2377
+ enabled: true,
2378
+ prefix: props.prefixPattern,
2379
+ expiration: props.previewExpiration ?? import_aws_cdk_lib11.Duration.days(DEFAULT_PREVIEW_EXPIRATION_DAYS)
2380
+ }
2381
+ ] : void 0;
2369
2382
  this.bucket = new import_aws_s3.Bucket(this, "bucket", {
2370
2383
  blockPublicAccess: {
2371
2384
  blockPublicAcls: true,
@@ -2373,6 +2386,9 @@ var _StaticHosting = class _StaticHosting extends import_constructs8.Construct {
2373
2386
  ignorePublicAcls: true,
2374
2387
  restrictPublicBuckets: true
2375
2388
  },
2389
+ ...previewLifecycleRules !== void 0 && {
2390
+ lifecycleRules: previewLifecycleRules
2391
+ },
2376
2392
  ...props.bucketProps
2377
2393
  });
2378
2394
  const handlerJs = path6.join(
@@ -6747,6 +6763,26 @@ var _OpenHiAuthService = class _OpenHiAuthService extends OpenHiService {
6747
6763
  });
6748
6764
  return import_aws_cognito4.UserPoolDomain.fromDomainName(scope, "user-pool-domain", domainName);
6749
6765
  }
6766
+ /**
6767
+ * Returns the full Cognito Hosted UI base URL (e.g.
6768
+ * `https://auth-abc.auth.us-east-2.amazoncognito.com`) by looking up
6769
+ * the Auth stack's User Pool Domain from SSM and composing it with the
6770
+ * calling stack's region.
6771
+ *
6772
+ * Equivalent to `UserPoolDomain.baseUrl()` on the concrete construct,
6773
+ * but works across stacks where the looked-up `IUserPoolDomain` is an
6774
+ * `Import` and does not carry the `baseUrl()` method. Assumes the
6775
+ * domain was created as a Cognito-managed prefix domain (the only
6776
+ * variant `OpenHiAuthService.createUserPoolDomain` produces).
6777
+ */
6778
+ static userPoolDomainBaseUrlFromConstruct(scope) {
6779
+ const domainName = DiscoverableStringParameter.valueForLookupName(scope, {
6780
+ ssmParamName: CognitoUserPoolDomain.SSM_PARAM_NAME,
6781
+ serviceType: _OpenHiAuthService.SERVICE_TYPE
6782
+ });
6783
+ const region = import_core.Stack.of(scope).region;
6784
+ return `https://${domainName}.auth.${region}.amazoncognito.com`;
6785
+ }
6750
6786
  /**
6751
6787
  * Returns an IKey (KMS) by looking up the Auth stack's User Pool KMS Key ARN from SSM.
6752
6788
  */
@@ -7344,11 +7380,11 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
7344
7380
  const cognitoScope = new import_constructs21.Construct(this, "runtime-config");
7345
7381
  const userPool = OpenHiAuthService.userPoolFromConstruct(cognitoScope);
7346
7382
  const userPoolClient = OpenHiAuthService.userPoolClientFromConstruct(cognitoScope);
7347
- const userPoolDomain = OpenHiAuthService.userPoolDomainFromConstruct(cognitoScope);
7383
+ const cognitoDomainUrl = OpenHiAuthService.userPoolDomainBaseUrlFromConstruct(cognitoScope);
7348
7384
  return {
7349
7385
  OPENHI_RUNTIME_CONFIG_COGNITO_USER_POOL_ID: userPool.userPoolId,
7350
7386
  OPENHI_RUNTIME_CONFIG_COGNITO_USER_POOL_CLIENT_ID: userPoolClient.userPoolClientId,
7351
- OPENHI_RUNTIME_CONFIG_COGNITO_DOMAIN: userPoolDomain.domainName,
7387
+ OPENHI_RUNTIME_CONFIG_COGNITO_DOMAIN_URL: cognitoDomainUrl,
7352
7388
  OPENHI_RUNTIME_CONFIG_COGNITO_REDIRECT_URI: this.props.runtimeConfig.cognitoRedirectUri,
7353
7389
  OPENHI_RUNTIME_CONFIG_API_BASE_URL: this.props.runtimeConfig.apiBaseUrl
7354
7390
  };
@@ -7395,7 +7431,9 @@ _OpenHiGraphqlService.SERVICE_TYPE = "graphql-api";
7395
7431
  var OpenHiGraphqlService = _OpenHiGraphqlService;
7396
7432
 
7397
7433
  // src/services/open-hi-website-service.ts
7434
+ var import_config5 = __toESM(require_lib());
7398
7435
  var import_aws_s32 = require("aws-cdk-lib/aws-s3");
7436
+ var OPENHI_PR_NUMBER_ENV_VAR = "OPENHI_PR_NUMBER";
7399
7437
  var SSM_PARAM_NAME_FULL_DOMAIN = "WEBSITE_FULL_DOMAIN";
7400
7438
  var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
7401
7439
  /**
@@ -7455,9 +7493,16 @@ var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
7455
7493
  super(ohEnv, _OpenHiWebsiteService.SERVICE_TYPE, props);
7456
7494
  this.props = props;
7457
7495
  this.validateConfig(props);
7496
+ const isReleaseBranch = this.branchName === this.defaultReleaseBranch;
7497
+ this.prNumber = this.resolvePrNumber(props);
7498
+ if (!isReleaseBranch && this.prNumber === void 0) {
7499
+ throw new Error(
7500
+ `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.`
7501
+ );
7502
+ }
7458
7503
  const hostedZone = this.createHostedZone();
7459
7504
  this.fullDomain = this.computeFullDomain(hostedZone);
7460
- const shouldCreateHostingInfra = props.createHostingInfrastructure ?? this.branchName === this.defaultReleaseBranch;
7505
+ const shouldCreateHostingInfra = props.createHostingInfrastructure ?? isReleaseBranch;
7461
7506
  if (shouldCreateHostingInfra) {
7462
7507
  const certificate = this.createCertificate();
7463
7508
  this.staticHosting = this.createStaticHosting({
@@ -7465,6 +7510,8 @@ var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
7465
7510
  hostedZone
7466
7511
  });
7467
7512
  this.createFullDomainParameter();
7513
+ } else if (!isReleaseBranch) {
7514
+ this.perBranchHostname = this.createPerBranchHostname(hostedZone);
7468
7515
  }
7469
7516
  if (props.createStaticContent !== false) {
7470
7517
  const bucket = this.resolveStaticHostingBucket();
@@ -7507,25 +7554,76 @@ var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
7507
7554
  return OpenHiGlobalService.rootWildcardCertificateFromConstruct(this);
7508
7555
  }
7509
7556
  /**
7510
- * Computes the full website domain from `domainPrefix` and the child
7511
- * zone name.
7557
+ * Resolves the PR number from props or the `OPENHI_PR_NUMBER` env var.
7558
+ * Returns `undefined` on release-branch deploys where no PR number is
7559
+ * needed.
7560
+ */
7561
+ resolvePrNumber(props) {
7562
+ if (props.prNumber !== void 0) {
7563
+ return props.prNumber;
7564
+ }
7565
+ const raw = process.env[OPENHI_PR_NUMBER_ENV_VAR]?.trim();
7566
+ if (!raw) {
7567
+ return void 0;
7568
+ }
7569
+ const parsed = Number.parseInt(raw, 10);
7570
+ if (!Number.isInteger(parsed) || parsed <= 0) {
7571
+ throw new Error(
7572
+ `${OPENHI_PR_NUMBER_ENV_VAR} must be a positive integer; got "${raw}".`
7573
+ );
7574
+ }
7575
+ return parsed;
7576
+ }
7577
+ /**
7578
+ * Computes the full website domain from `domainPrefix`, the PR number,
7579
+ * and the child zone name. Release-branch deploys serve at
7580
+ * `\<domainPrefix\>.\<zone\>` (e.g. `admin.dev.openhi.org`); every other
7581
+ * deploy serves a per-PR preview at `\<domainPrefix\>-pr-\<N\>.\<zone\>`
7582
+ * (e.g. `admin-pr-123.dev.openhi.org`).
7512
7583
  */
7513
7584
  computeFullDomain(hostedZone) {
7514
- const prefix = this.props.domainPrefix ?? "www";
7515
- return [prefix, hostedZone.zoneName].join(".");
7585
+ const subDomain = this.computeSubDomain();
7586
+ return [subDomain, hostedZone.zoneName].join(".");
7587
+ }
7588
+ /**
7589
+ * Returns the sub-domain label (left of the zone) for the current
7590
+ * deploy. Used both for {@link fullDomain} and for the per-branch S3
7591
+ * key prefix passed to {@link StaticContent} so the upload prefix
7592
+ * always matches the served hostname.
7593
+ *
7594
+ * Non-release deploys compose the per-PR slug from
7595
+ * {@link PER_BRANCH_PREVIEW_PREFIX} so the per-PR S3 key prefix
7596
+ * matches what `StaticHosting`'s lifecycle rule expires.
7597
+ */
7598
+ computeSubDomain() {
7599
+ const isReleaseBranch = this.branchName === this.defaultReleaseBranch;
7600
+ if (isReleaseBranch) {
7601
+ return this.props.domainPrefix ?? "www";
7602
+ }
7603
+ return `${PER_BRANCH_PREVIEW_PREFIX}${this.prNumber}`;
7516
7604
  }
7517
7605
  /**
7518
7606
  * Creates the StaticHosting infrastructure (bucket + distribution +
7519
- * Lambda@Edge + 4 SSM params + DNS).
7607
+ * Lambda@Edge + 4 SSM params + DNS). The release-branch distribution
7608
+ * adds `*.\<zone\>` as a wildcard alt-name on top of the canonical
7609
+ * hostname so per-PR previews resolve via the same distribution.
7610
+ *
7611
+ * The bucket carries an S3 lifecycle rule that expires per-PR
7612
+ * preview content (keys under {@link PER_BRANCH_PREVIEW_PREFIX})
7613
+ * on non-production stages. PROD never gets the rule — see
7614
+ * `enablePreviewLifecycle`.
7520
7615
  */
7521
7616
  createStaticHosting(deps) {
7522
7617
  const restApi = this.props.restApi === true ? this.resolveRestApi() : void 0;
7618
+ const wildcardSan = `*.${deps.hostedZone.zoneName}`;
7523
7619
  return new StaticHosting(this, "static-hosting", {
7524
7620
  serviceType: _OpenHiWebsiteService.SERVICE_TYPE,
7525
7621
  certificate: deps.certificate,
7526
7622
  hostedZone: deps.hostedZone,
7527
- domainNames: [this.fullDomain],
7623
+ domainNames: [this.fullDomain, wildcardSan],
7528
7624
  description: `OpenHI website (${this.fullDomain})`,
7625
+ prefixPattern: PER_BRANCH_PREVIEW_PREFIX,
7626
+ enablePreviewLifecycle: this.ohEnv.ohStage.stageType !== import_config5.OPEN_HI_STAGE.PROD,
7529
7627
  ...restApi !== void 0 && { restApi }
7530
7628
  });
7531
7629
  }
@@ -7559,6 +7657,12 @@ var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
7559
7657
  * the release-branch deploy publishes to SSM, addressed against
7560
7658
  * {@link OpenHiService.releaseBranchHash}. See
7561
7659
  * {@link resolveStaticHostingBucket}.
7660
+ *
7661
+ * The S3 key prefix is `\<sub-domain\>.\<zone\>/\<contentDest\>` so the
7662
+ * upload location matches the Host-header-derived folder the Lambda@Edge
7663
+ * viewer-request handler prepends. Passing the zone name (rather than
7664
+ * `this.fullDomain`) for the `fullDomain` prop keeps the prefix flat
7665
+ * — `admin-pr-123.dev.openhi.org/`, not `admin-pr-123.admin.dev.openhi.org/`.
7562
7666
  */
7563
7667
  createStaticContent(bucket) {
7564
7668
  const { contentSourceDirectory, contentDestinationDirectory } = this.props;
@@ -7566,7 +7670,21 @@ var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
7566
7670
  bucket,
7567
7671
  contentSourceDirectory,
7568
7672
  contentDestinationDirectory,
7569
- fullDomain: this.fullDomain
7673
+ subDomain: this.computeSubDomain(),
7674
+ fullDomain: this.config.zoneName
7675
+ });
7676
+ }
7677
+ /**
7678
+ * Creates the per-PR `PerBranchHostname` alias record on non-release
7679
+ * branch deploys. The record points `\<domainPrefix\>-pr-\<N\>.\<zone\>`
7680
+ * at the release-branch CloudFront distribution (resolved from SSM
7681
+ * against {@link OpenHiService.releaseBranchHash}).
7682
+ */
7683
+ createPerBranchHostname(hostedZone) {
7684
+ return new PerBranchHostname(this, "per-branch-hostname", {
7685
+ hostname: this.fullDomain,
7686
+ hostedZone,
7687
+ serviceType: _OpenHiWebsiteService.SERVICE_TYPE
7570
7688
  });
7571
7689
  }
7572
7690
  /**
@@ -8112,6 +8230,7 @@ var RenameCascadeWorkflow = class extends import_constructs25.Construct {
8112
8230
  DATA_STORE_CHANGE_DETAIL_MAX_UTF8_BYTES,
8113
8231
  DATA_STORE_CHANGE_DETAIL_TYPE,
8114
8232
  DATA_STORE_CHANGE_EVENT_SOURCE,
8233
+ DEFAULT_PREVIEW_EXPIRATION_DAYS,
8115
8234
  DEMO_DATA_PLANE_FIXTURES,
8116
8235
  DEMO_PERIOD,
8117
8236
  DEMO_TENANT_SPECS,
@@ -8122,6 +8241,7 @@ var RenameCascadeWorkflow = class extends import_constructs25.Construct {
8122
8241
  DataStorePostgresReplica,
8123
8242
  DiscoverableStringParameter,
8124
8243
  DynamoDbDataStore,
8244
+ OPENHI_PR_NUMBER_ENV_VAR,
8125
8245
  OPENHI_REPO_TAG_KEY_ENV_VAR,
8126
8246
  OPENHI_RESOURCE_URN_SYSTEM,
8127
8247
  OPENHI_TAG_KEY_PREFIX_ENV_VAR,
@@ -8147,6 +8267,7 @@ var RenameCascadeWorkflow = class extends import_constructs25.Construct {
8147
8267
  OpsEventBus,
8148
8268
  OwningDeleteCascadeLambdas,
8149
8269
  OwningDeleteCascadeWorkflow,
8270
+ PER_BRANCH_PREVIEW_PREFIX,
8150
8271
  PLACEHOLDER_TENANT_ID,
8151
8272
  PLACEHOLDER_WORKSPACE_ID,
8152
8273
  PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM,