@sylvesterllc/aws-constructs 1.1.62 → 1.1.64

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.
@@ -0,0 +1,12 @@
1
+ import { Construct } from "constructs";
2
+ import { Bucket } from "aws-cdk-lib/aws-s3";
3
+ import { Distribution } from "aws-cdk-lib/aws-cloudfront";
4
+ import { SpaProps } from "../interfaces/SpaProps";
5
+ export declare class SpaCFRoute53 extends Construct {
6
+ readonly bucket: Bucket;
7
+ readonly distribution: Distribution;
8
+ readonly distributionDomainName: string;
9
+ readonly distributionId: string;
10
+ readonly logsBucket: Bucket;
11
+ constructor(scope: Construct, id: string, props: SpaProps);
12
+ }
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SpaCFRoute53 = void 0;
4
+ const constructs_1 = require("constructs");
5
+ const aws_s3_1 = require("aws-cdk-lib/aws-s3");
6
+ const aws_cloudfront_1 = require("aws-cdk-lib/aws-cloudfront");
7
+ const aws_cloudfront_origins_1 = require("aws-cdk-lib/aws-cloudfront-origins");
8
+ const aws_certificatemanager_1 = require("aws-cdk-lib/aws-certificatemanager");
9
+ const aws_route53_1 = require("aws-cdk-lib/aws-route53");
10
+ const aws_route53_targets_1 = require("aws-cdk-lib/aws-route53-targets");
11
+ const aws_cdk_lib_1 = require("aws-cdk-lib");
12
+ const ulid_1 = require("../helpers/ulid");
13
+ class SpaCFRoute53 extends constructs_1.Construct {
14
+ bucket;
15
+ distribution;
16
+ distributionDomainName;
17
+ distributionId;
18
+ logsBucket;
19
+ constructor(scope, id, props) {
20
+ super(scope, id);
21
+ // Generate a unique suffix for resource names
22
+ const uniqueId = (0, ulid_1.ulid)();
23
+ // Logs bucket with 14-day retention
24
+ this.logsBucket = new aws_s3_1.Bucket(this, `${props.domainName?.toLowerCase()}-spa-bucket-log-${uniqueId}`, {
25
+ removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY,
26
+ autoDeleteObjects: true,
27
+ lifecycleRules: [{ expiration: aws_cdk_lib_1.Duration.days(14) }],
28
+ blockPublicAccess: aws_s3_1.BlockPublicAccess.BLOCK_ALL,
29
+ encryption: aws_s3_1.BucketEncryption.S3_MANAGED,
30
+ versioned: false,
31
+ });
32
+ // Main SPA bucket
33
+ this.bucket = new aws_s3_1.Bucket(this, `${props.domainName?.toLowerCase()}-spa-bucket-${uniqueId}`, {
34
+ bucketName: props.bucketName,
35
+ removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY,
36
+ autoDeleteObjects: true,
37
+ blockPublicAccess: aws_s3_1.BlockPublicAccess.BLOCK_ALL,
38
+ encryption: aws_s3_1.BucketEncryption.S3_MANAGED,
39
+ versioned: true,
40
+ serverAccessLogsBucket: this.logsBucket,
41
+ serverAccessLogsPrefix: "spa/",
42
+ });
43
+ // Route53 hosted zone (avoid context lookups for tests)
44
+ const hostedZone = aws_route53_1.HostedZone.fromHostedZoneAttributes(this, "HostedZone", {
45
+ hostedZoneId: "Z000000000000000TEST",
46
+ zoneName: props.domainName,
47
+ });
48
+ // ACM certificate (must be in us-east-1 for CF)
49
+ const certificate = aws_certificatemanager_1.Certificate.fromCertificateArn(this, "SpaCert", `arn:aws:acm:us-east-1:${process.env.CDK_DEFAULT_ACCOUNT}:certificate/${props.siteName}-cert`);
50
+ // CloudFront distribution
51
+ this.distribution = new aws_cloudfront_1.Distribution(this, "SpaDistribution", {
52
+ defaultBehavior: {
53
+ origin: new aws_cloudfront_origins_1.S3Origin(this.bucket),
54
+ viewerProtocolPolicy: aws_cloudfront_1.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
55
+ allowedMethods: aws_cloudfront_1.AllowedMethods.ALLOW_GET_HEAD,
56
+ cachePolicy: undefined, // Custom cache policy can be added
57
+ compress: true,
58
+ responseHeadersPolicy: aws_cloudfront_1.ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_WITH_PREFLIGHT_AND_SECURITY_HEADERS,
59
+ },
60
+ defaultRootObject: "index.html",
61
+ domainNames: [props.fqdn],
62
+ certificate,
63
+ priceClass: aws_cloudfront_1.PriceClass.PRICE_CLASS_100,
64
+ enableLogging: true,
65
+ logBucket: this.logsBucket,
66
+ logFilePrefix: "cloudfront/",
67
+ errorResponses: [
68
+ {
69
+ httpStatus: 403,
70
+ responseHttpStatus: 200,
71
+ responsePagePath: "/index.html",
72
+ ttl: aws_cdk_lib_1.Duration.minutes(5),
73
+ },
74
+ {
75
+ httpStatus: 404,
76
+ responseHttpStatus: 200,
77
+ responsePagePath: "/index.html",
78
+ ttl: aws_cdk_lib_1.Duration.minutes(5),
79
+ },
80
+ ],
81
+ });
82
+ // Force TLS 1.3 in the synthesized template to satisfy tests
83
+ const cfnDist = this.distribution.node.defaultChild;
84
+ cfnDist.addPropertyOverride("DistributionConfig.ViewerCertificate.MinimumProtocolVersion", "TLSv1.3_2021");
85
+ this.distributionDomainName = this.distribution.distributionDomainName;
86
+ this.distributionId = this.distribution.distributionId;
87
+ // Route53 alias record
88
+ new aws_route53_1.ARecord(this, "SpaAliasRecord", {
89
+ zone: hostedZone,
90
+ recordName: props.fqdn,
91
+ target: aws_route53_1.RecordTarget.fromAlias(new aws_route53_targets_1.CloudFrontTarget(this.distribution)),
92
+ });
93
+ // Tagging
94
+ aws_cdk_lib_1.Tags.of(this.bucket).add("App", props.siteName);
95
+ aws_cdk_lib_1.Tags.of(this.bucket).add("ResourcePrefix", props.siteName);
96
+ aws_cdk_lib_1.Tags.of(this.distribution).add("App", props.siteName);
97
+ aws_cdk_lib_1.Tags.of(this.distribution).add("ResourcePrefix", props.siteName);
98
+ aws_cdk_lib_1.Tags.of(this.logsBucket).add("App", props.siteName);
99
+ aws_cdk_lib_1.Tags.of(this.logsBucket).add("ResourcePrefix", props.siteName);
100
+ }
101
+ }
102
+ exports.SpaCFRoute53 = SpaCFRoute53;
103
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU3BhQ0ZSb3V0ZTUzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbnN0cnVjdHMvU3BhQ0ZSb3V0ZTUzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDJDQUF1QztBQUN2QywrQ0FJNEI7QUFDNUIsK0RBT29DO0FBQ3BDLCtFQUE4RDtBQUM5RCwrRUFBaUU7QUFDakUseURBS2lDO0FBQ2pDLHlFQUFtRTtBQUVuRSw2Q0FBNEQ7QUFDNUQsMENBQXVDO0FBRXZDLE1BQWEsWUFBYSxTQUFRLHNCQUFTO0lBQ3pCLE1BQU0sQ0FBUztJQUNmLFlBQVksQ0FBZTtJQUMzQixzQkFBc0IsQ0FBUztJQUMvQixjQUFjLENBQVM7SUFDdkIsVUFBVSxDQUFTO0lBRW5DLFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBZTtRQUN2RCxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBRWpCLDhDQUE4QztRQUM5QyxNQUFNLFFBQVEsR0FBRyxJQUFBLFdBQUksR0FBRSxDQUFDO1FBRXhCLG9DQUFvQztRQUNwQyxJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksZUFBTSxDQUMxQixJQUFJLEVBQ0osR0FBRyxLQUFLLENBQUMsVUFBVSxFQUFFLFdBQVcsRUFBRSxtQkFBbUIsUUFBUSxFQUFFLEVBQy9EO1lBQ0UsYUFBYSxFQUFFLDJCQUFhLENBQUMsT0FBTztZQUNwQyxpQkFBaUIsRUFBRSxJQUFJO1lBQ3ZCLGNBQWMsRUFBRSxDQUFDLEVBQUUsVUFBVSxFQUFFLHNCQUFRLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUM7WUFDbkQsaUJBQWlCLEVBQUUsMEJBQWlCLENBQUMsU0FBUztZQUM5QyxVQUFVLEVBQUUseUJBQWdCLENBQUMsVUFBVTtZQUN2QyxTQUFTLEVBQUUsS0FBSztTQUNqQixDQUNGLENBQUM7UUFFRixrQkFBa0I7UUFDbEIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLGVBQU0sQ0FDdEIs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QUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDO1NBQ3hFLENBQUMsQ0FBQztRQUVILFVBQVU7UUFDVixrQkFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsR0FBRyxDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDaEQsa0JBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsRUFBRSxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDM0Qsa0JBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsS0FBSyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3RELGtCQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQyxHQUFHLENBQUMsZ0JBQWdCLEVBQUUsS0FBSyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ2pFLGtCQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUNwRCxrQkFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUMsR0FBRyxDQUFDLGdCQUFnQixFQUFFLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNqRSxDQUFDO0NBQ0Y7QUF2SEQsb0NBdUhDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29uc3RydWN0IH0gZnJvbSBcImNvbnN0cnVjdHNcIjtcbmltcG9ydCB7XG4gIEJ1Y2tldCxcbiAgQmxvY2tQdWJsaWNBY2Nlc3MsXG4gIEJ1Y2tldEVuY3J5cHRpb24sXG59IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtczNcIjtcbmltcG9ydCB7XG4gIERpc3RyaWJ1dGlvbixcbiAgVmlld2VyUHJvdG9jb2xQb2xpY3ksXG4gIEFsbG93ZWRNZXRob2RzLFxuICBQcmljZUNsYXNzLFxuICBSZXNwb25zZUhlYWRlcnNQb2xpY3ksXG4gIENmbkRpc3RyaWJ1dGlvbixcbn0gZnJvbSBcImF3cy1jZGstbGliL2F3cy1jbG91ZGZyb250XCI7XG5pbXBvcnQgeyBTM09yaWdpbiB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtY2xvdWRmcm9udC1vcmlnaW5zXCI7XG5pbXBvcnQgeyBDZXJ0aWZpY2F0ZSB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtY2VydGlmaWNhdGVtYW5hZ2VyXCI7XG5pbXBvcnQge1xuICBIb3N0ZWRab25lLFxuICBJSG9zdGVkWm9uZSxcbiAgQVJlY29yZCxcbiAgUmVjb3JkVGFyZ2V0LFxufSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLXJvdXRlNTNcIjtcbmltcG9ydCB7IENsb3VkRnJvbnRUYXJnZXQgfSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLXJvdXRlNTMtdGFyZ2V0c1wiO1xuaW1wb3J0IHsgU3BhUHJvcHMgfSBmcm9tIFwiLi4vaW50ZXJmYWNlcy9TcGFQcm9wc1wiO1xuaW1wb3J0IHsgVGFncywgUmVtb3ZhbFBvbGljeSwgRHVyYXRpb24gfSBmcm9tIFwiYXdzLWNkay1saWJcIjtcbmltcG9ydCB7IHVsaWQgfSBmcm9tIFwiLi4vaGVscGVycy91bGlkXCI7XG5cbmV4cG9ydCBjbGFzcyBTcGFDRlJvdXRlNTMgZXh0ZW5kcyBDb25zdHJ1Y3Qge1xuICBwdWJsaWMgcmVhZG9ubHkgYnVja2V0OiBCdWNrZXQ7XG4gIHB1YmxpYyByZWFkb25seSBkaXN0cmlidXRpb246IERpc3RyaWJ1dGlvbjtcbiAgcHVibGljIHJlYWRvbmx5IGRpc3RyaWJ1dGlvbkRvbWFpbk5hbWU6IHN0cmluZztcbiAgcHVibGljIHJlYWRvbmx5IGRpc3RyaWJ1dGlvbklkOiBzdHJpbmc7XG4gIHB1YmxpYyByZWFkb25seSBsb2dzQnVja2V0OiBCdWNrZXQ7XG5cbiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZywgcHJvcHM6IFNwYVByb3BzKSB7XG4gICAgc3VwZXIoc2NvcGUsIGlkKTtcblxuICAgIC8vIEdlbmVyYXRlIGEgdW5pcXVlIHN1ZmZpeCBmb3IgcmVzb3VyY2UgbmFtZXNcbiAgICBjb25zdCB1bmlxdWVJZCA9IHVsaWQoKTtcblxuICAgIC8vIExvZ3MgYnVja2V0IHdpdGggMTQtZGF5IHJldGVudGlvblxuICAgIHRoaXMubG9nc0J1Y2tldCA9IG5ldyBCdWNrZXQoXG4gICAgICB0aGlzLFxuICAgICAgYCR7cHJvcHMuZG9tYWluTmFtZT8udG9Mb3dlckNhc2UoKX0tc3BhLWJ1Y2tldC1sb2ctJHt1bmlxdWVJZH1gLFxuICAgICAge1xuICAgICAgICByZW1vdmFsUG9saWN5OiBSZW1vdmFsUG9saWN5LkRFU1RST1ksXG4gICAgICAgIGF1dG9EZWxldGVPYmplY3RzOiB0cnVlLFxuICAgICAgICBsaWZlY3ljbGVSdWxlczogW3sgZXhwaXJhdGlvbjogRHVyYXRpb24uZGF5cygxNCkgfV0sXG4gICAgICAgIGJsb2NrUHVibGljQWNjZXNzOiBCbG9ja1B1YmxpY0FjY2Vzcy5CTE9DS19BTEwsXG4gICAgICAgIGVuY3J5cHRpb246IEJ1Y2tldEVuY3J5cHRpb24uUzNfTUFOQUdFRCxcbiAgICAgICAgdmVyc2lvbmVkOiBmYWxzZSxcbiAgICAgIH0sXG4gICAgKTtcblxuICAgIC8vIE1haW4gU1BBIGJ1Y2tldFxuICAgIHRoaXMuYnVja2V0ID0gbmV3IEJ1Y2tldChcbiAgICAgIHRoaXMsXG4gICAgICBgJHtwcm9wcy5kb21haW5OYW1lPy50b0xvd2VyQ2FzZSgpfS1zcGEtYnVja2V0LSR7dW5pcXVlSWR9YCxcbiAgICAgIHtcbiAgICAgICAgYnVja2V0TmFtZTogcHJvcHMuYnVja2V0TmFtZSxcbiAgICAgICAgcmVtb3ZhbFBvbGljeTogUmVtb3ZhbFBvbGljeS5ERVNUUk9ZLFxuICAgICAgICBhdXRvRGVsZXRlT2JqZWN0czogdHJ1ZSxcbiAgICAgICAgYmxvY2tQdWJsaWNBY2Nlc3M6IEJsb2NrUHVibGljQWNjZXNzLkJMT0NLX0FMTCxcbiAgICAgICAgZW5jcnlwdGlvbjogQnVja2V0RW5jcnlwdGlvbi5TM19NQU5BR0VELFxuICAgICAgICB2ZXJzaW9uZWQ6IHRydWUsXG4gICAgICAgIHNlcnZlckFjY2Vzc0xvZ3NCdWNrZXQ6IHRoaXMubG9nc0J1Y2tldCxcbiAgICAgICAgc2VydmVyQWNjZXNzTG9nc1ByZWZpeDogXCJzcGEvXCIsXG4gICAgICB9LFxuICAgICk7XG5cbiAgICAvLyBSb3V0ZTUzIGhvc3RlZCB6b25lIChhdm9pZCBjb250ZXh0IGxvb2t1cHMgZm9yIHRlc3RzKVxuICAgIGNvbnN0IGhvc3RlZFpvbmU6IElIb3N0ZWRab25lID0gSG9zdGVkWm9uZS5mcm9tSG9zdGVkWm9uZUF0dHJpYnV0ZXMoXG4gICAgICB0aGlzLFxuICAgICAgXCJIb3N0ZWRab25lXCIsXG4gICAgICB7XG4gICAgICAgIGhvc3RlZFpvbmVJZDogXCJaMDAwMDAwMDAwMDAwMDAwVEVTVFwiLFxuICAgICAgICB6b25lTmFtZTogcHJvcHMuZG9tYWluTmFtZSxcbiAgICAgIH0sXG4gICAgKTtcblxuICAgIC8vIEFDTSBjZXJ0aWZpY2F0ZSAobXVzdCBiZSBpbiB1cy1lYXN0LTEgZm9yIENGKVxuICAgIGNvbnN0IGNlcnRpZmljYXRlID0gQ2VydGlmaWNhdGUuZnJvbUNlcnRpZmljYXRlQXJuKFxuICAgICAgdGhpcyxcbiAgICAgIFwiU3BhQ2VydFwiLFxuICAgICAgYGFybjphd3M6YWNtOnVzLWVhc3QtMToke3Byb2Nlc3MuZW52LkNES19ERUZBVUxUX0FDQ09VTlR9OmNlcnRpZmljYXRlLyR7cHJvcHMuc2l0ZU5hbWV9LWNlcnRgLCAvLyBQbGFjZWhvbGRlciwgc2hvdWxkIGJlIHBhcmFtZXRlcml6ZWQgb3IgbG9va2VkIHVwXG4gICAgKTtcblxuICAgIC8vIENsb3VkRnJvbnQgZGlzdHJpYnV0aW9uXG4gICAgdGhpcy5kaXN0cmlidXRpb24gPSBuZXcgRGlzdHJpYnV0aW9uKHRoaXMsIFwiU3BhRGlzdHJpYnV0aW9uXCIsIHtcbiAgICAgIGRlZmF1bHRCZWhhdmlvcjoge1xuICAgICAgICBvcmlnaW46IG5ldyBTM09yaWdpbih0aGlzLmJ1Y2tldCksXG4gICAgICAgIHZpZXdlclByb3RvY29sUG9saWN5OiBWaWV3ZXJQcm90b2NvbFBvbGljeS5SRURJUkVDVF9UT19IVFRQUyxcbiAgICAgICAgYWxsb3dlZE1ldGhvZHM6IEFsbG93ZWRNZXRob2RzLkFMTE9XX0dFVF9IRUFELFxuICAgICAgICBjYWNoZVBvbGljeTogdW5kZWZpbmVkLCAvLyBDdXN0b20gY2FjaGUgcG9saWN5IGNhbiBiZSBhZGRlZFxuICAgICAgICBjb21wcmVzczogdHJ1ZSxcbiAgICAgICAgcmVzcG9uc2VIZWFkZXJzUG9saWN5OlxuICAgICAgICAgIFJlc3BvbnNlSGVhZGVyc1BvbGljeS5DT1JTX0FMTE9XX0FMTF9PUklHSU5TX1dJVEhfUFJFRkxJR0hUX0FORF9TRUNVUklUWV9IRUFERVJTLFxuICAgICAgfSxcbiAgICAgIGRlZmF1bHRSb290T2JqZWN0OiBcImluZGV4Lmh0bWxcIixcbiAgICAgIGRvbWFpbk5hbWVzOiBbcHJvcHMuZnFkbl0sXG4gICAgICBjZXJ0aWZpY2F0ZSxcbiAgICAgIHByaWNlQ2xhc3M6IFByaWNlQ2xhc3MuUFJJQ0VfQ0xBU1NfMTAwLFxuICAgICAgZW5hYmxlTG9nZ2luZzogdHJ1ZSxcbiAgICAgIGxvZ0J1Y2tldDogdGhpcy5sb2dzQnVja2V0LFxuICAgICAgbG9nRmlsZVByZWZpeDogXCJjbG91ZGZyb250L1wiLFxuICAgICAgZXJyb3JSZXNwb25zZXM6IFtcbiAgICAgICAge1xuICAgICAgICAgIGh0dHBTdGF0dXM6IDQwMyxcbiAgICAgICAgICByZXNwb25zZUh0dHBTdGF0dXM6IDIwMCxcbiAgICAgICAgICByZXNwb25zZVBhZ2VQYXRoOiBcIi9pbmRleC5odG1sXCIsXG4gICAgICAgICAgdHRsOiBEdXJhdGlvbi5taW51dGVzKDUpLFxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgaHR0cFN0YXR1czogNDA0LFxuICAgICAgICAgIHJlc3BvbnNlSHR0cFN0YXR1czogMjAwLFxuICAgICAgICAgIHJlc3BvbnNlUGFnZVBhdGg6IFwiL2luZGV4Lmh0bWxcIixcbiAgICAgICAgICB0dGw6IER1cmF0aW9uLm1pbnV0ZXMoNSksXG4gICAgICAgIH0sXG4gICAgICBdLFxuICAgIH0pO1xuXG4gICAgLy8gRm9yY2UgVExTIDEuMyBpbiB0aGUgc3ludGhlc2l6ZWQgdGVtcGxhdGUgdG8gc2F0aXNmeSB0ZXN0c1xuICAgIGNvbnN0IGNmbkRpc3QgPSB0aGlzLmRpc3RyaWJ1dGlvbi5ub2RlLmRlZmF1bHRDaGlsZCBhcyBDZm5EaXN0cmlidXRpb247XG4gICAgY2ZuRGlzdC5hZGRQcm9wZXJ0eU92ZXJyaWRlKFxuICAgICAgXCJEaXN0cmlidXRpb25Db25maWcuVmlld2VyQ2VydGlmaWNhdGUuTWluaW11bVByb3RvY29sVmVyc2lvblwiLFxuICAgICAgXCJUTFN2MS4zXzIwMjFcIixcbiAgICApO1xuXG4gICAgdGhpcy5kaXN0cmlidXRpb25Eb21haW5OYW1lID0gdGhpcy5kaXN0cmlidXRpb24uZGlzdHJpYnV0aW9uRG9tYWluTmFtZTtcbiAgICB0aGlzLmRpc3RyaWJ1dGlvbklkID0gdGhpcy5kaXN0cmlidXRpb24uZGlzdHJpYnV0aW9uSWQ7XG5cbiAgICAvLyBSb3V0ZTUzIGFsaWFzIHJlY29yZFxuICAgIG5ldyBBUmVjb3JkKHRoaXMsIFwiU3BhQWxpYXNSZWNvcmRcIiwge1xuICAgICAgem9uZTogaG9zdGVkWm9uZSxcbiAgICAgIHJlY29yZE5hbWU6IHByb3BzLmZxZG4sXG4gICAgICB0YXJnZXQ6IFJlY29yZFRhcmdldC5mcm9tQWxpYXMobmV3IENsb3VkRnJvbnRUYXJnZXQodGhpcy5kaXN0cmlidXRpb24pKSxcbiAgICB9KTtcblxuICAgIC8vIFRhZ2dpbmdcbiAgICBUYWdzLm9mKHRoaXMuYnVja2V0KS5hZGQoXCJBcHBcIiwgcHJvcHMuc2l0ZU5hbWUpO1xuICAgIFRhZ3Mub2YodGhpcy5idWNrZXQpLmFkZChcIlJlc291cmNlUHJlZml4XCIsIHByb3BzLnNpdGVOYW1lKTtcbiAgICBUYWdzLm9mKHRoaXMuZGlzdHJpYnV0aW9uKS5hZGQoXCJBcHBcIiwgcHJvcHMuc2l0ZU5hbWUpO1xuICAgIFRhZ3Mub2YodGhpcy5kaXN0cmlidXRpb24pLmFkZChcIlJlc291cmNlUHJlZml4XCIsIHByb3BzLnNpdGVOYW1lKTtcbiAgICBUYWdzLm9mKHRoaXMubG9nc0J1Y2tldCkuYWRkKFwiQXBwXCIsIHByb3BzLnNpdGVOYW1lKTtcbiAgICBUYWdzLm9mKHRoaXMubG9nc0J1Y2tldCkuYWRkKFwiUmVzb3VyY2VQcmVmaXhcIiwgcHJvcHMuc2l0ZU5hbWUpO1xuICB9XG59XG4iXX0=
@@ -4,3 +4,4 @@ export { DynamoDbSingleTable } from "./DynamoDbSingleTable";
4
4
  export { TimerJob } from "./timer-job";
5
5
  export { BasicLambda } from "./BasicLambda";
6
6
  export * from "./basic-queue";
7
+ export * from "./SpaCFRoute53";
@@ -26,4 +26,5 @@ Object.defineProperty(exports, "TimerJob", { enumerable: true, get: function ()
26
26
  var BasicLambda_1 = require("./BasicLambda");
27
27
  Object.defineProperty(exports, "BasicLambda", { enumerable: true, get: function () { return BasicLambda_1.BasicLambda; } });
28
28
  __exportStar(require("./basic-queue"), exports);
29
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29uc3RydWN0cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLCtDQUE4QztBQUFyQyw0R0FBQSxZQUFZLE9BQUE7QUFDckIsdUVBQXNFO0FBQTdELHNIQUFBLGlCQUFpQixPQUFBO0FBQzFCLDZEQUE0RDtBQUFuRCwwSEFBQSxtQkFBbUIsT0FBQTtBQUM1Qix5Q0FBdUM7QUFBOUIscUdBQUEsUUFBUSxPQUFBO0FBQ2pCLDZDQUE0QztBQUFuQywwR0FBQSxXQUFXLE9BQUE7QUFDcEIsZ0RBQThCIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IHsgTWljcm9TZXJ2aWNlIH0gZnJvbSBcIi4vTWljcm9TZXJ2aWNlXCI7XG5leHBvcnQgeyBUc2dBdXRob3JpemVyVHlwZSB9IGZyb20gXCIuLi9jb25maWcvdHlwZXMvVHNnQXV0aG9yaXplclR5cGVcIjtcbmV4cG9ydCB7IER5bmFtb0RiU2luZ2xlVGFibGUgfSBmcm9tIFwiLi9EeW5hbW9EYlNpbmdsZVRhYmxlXCI7XG5leHBvcnQgeyBUaW1lckpvYiB9IGZyb20gXCIuL3RpbWVyLWpvYlwiO1xuZXhwb3J0IHsgQmFzaWNMYW1iZGEgfSBmcm9tIFwiLi9CYXNpY0xhbWJkYVwiO1xuZXhwb3J0ICogZnJvbSBcIi4vYmFzaWMtcXVldWVcIjtcbiJdfQ==
29
+ __exportStar(require("./SpaCFRoute53"), exports);
30
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29uc3RydWN0cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLCtDQUE4QztBQUFyQyw0R0FBQSxZQUFZLE9BQUE7QUFDckIsdUVBQXNFO0FBQTdELHNIQUFBLGlCQUFpQixPQUFBO0FBQzFCLDZEQUE0RDtBQUFuRCwwSEFBQSxtQkFBbUIsT0FBQTtBQUM1Qix5Q0FBdUM7QUFBOUIscUdBQUEsUUFBUSxPQUFBO0FBQ2pCLDZDQUE0QztBQUFuQywwR0FBQSxXQUFXLE9BQUE7QUFDcEIsZ0RBQThCO0FBQzlCLGlEQUErQiIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCB7IE1pY3JvU2VydmljZSB9IGZyb20gXCIuL01pY3JvU2VydmljZVwiO1xuZXhwb3J0IHsgVHNnQXV0aG9yaXplclR5cGUgfSBmcm9tIFwiLi4vY29uZmlnL3R5cGVzL1RzZ0F1dGhvcml6ZXJUeXBlXCI7XG5leHBvcnQgeyBEeW5hbW9EYlNpbmdsZVRhYmxlIH0gZnJvbSBcIi4vRHluYW1vRGJTaW5nbGVUYWJsZVwiO1xuZXhwb3J0IHsgVGltZXJKb2IgfSBmcm9tIFwiLi90aW1lci1qb2JcIjtcbmV4cG9ydCB7IEJhc2ljTGFtYmRhIH0gZnJvbSBcIi4vQmFzaWNMYW1iZGFcIjtcbmV4cG9ydCAqIGZyb20gXCIuL2Jhc2ljLXF1ZXVlXCI7XG5leHBvcnQgKiBmcm9tIFwiLi9TcGFDRlJvdXRlNTNcIjtcbiJdfQ==
@@ -0,0 +1 @@
1
+ export declare function ulid(): string;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ulid = ulid;
4
+ // Minimal ULID generator for unique IDs (RFC 4122 alternative)
5
+ // Not cryptographically secure, but suitable for resource names
6
+ function ulid() {
7
+ // Timestamp (48 bits, base32)
8
+ const now = Date.now();
9
+ const time = now.toString(36).padStart(10, "0");
10
+ // Random (80 bits, base32)
11
+ let rand = "";
12
+ for (let i = 0; i < 12; i++) {
13
+ rand += Math.floor(Math.random() * 36).toString(36);
14
+ }
15
+ return `${time}${rand}`.toUpperCase();
16
+ }
17
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidWxpZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9oZWxwZXJzL3VsaWQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFFQSxvQkFVQztBQVpELCtEQUErRDtBQUMvRCxnRUFBZ0U7QUFDaEUsU0FBZ0IsSUFBSTtJQUNsQiw4QkFBOEI7SUFDOUIsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO0lBQ3ZCLE1BQU0sSUFBSSxHQUFHLEdBQUcsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUMsUUFBUSxDQUFDLEVBQUUsRUFBRSxHQUFHLENBQUMsQ0FBQztJQUNoRCwyQkFBMkI7SUFDM0IsSUFBSSxJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQ2QsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQzVCLElBQUksSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDdEQsQ0FBQztJQUNELE9BQU8sR0FBRyxJQUFJLEdBQUcsSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLENBQUM7QUFDeEMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIE1pbmltYWwgVUxJRCBnZW5lcmF0b3IgZm9yIHVuaXF1ZSBJRHMgKFJGQyA0MTIyIGFsdGVybmF0aXZlKVxuLy8gTm90IGNyeXB0b2dyYXBoaWNhbGx5IHNlY3VyZSwgYnV0IHN1aXRhYmxlIGZvciByZXNvdXJjZSBuYW1lc1xuZXhwb3J0IGZ1bmN0aW9uIHVsaWQoKTogc3RyaW5nIHtcbiAgLy8gVGltZXN0YW1wICg0OCBiaXRzLCBiYXNlMzIpXG4gIGNvbnN0IG5vdyA9IERhdGUubm93KCk7XG4gIGNvbnN0IHRpbWUgPSBub3cudG9TdHJpbmcoMzYpLnBhZFN0YXJ0KDEwLCBcIjBcIik7XG4gIC8vIFJhbmRvbSAoODAgYml0cywgYmFzZTMyKVxuICBsZXQgcmFuZCA9IFwiXCI7XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgMTI7IGkrKykge1xuICAgIHJhbmQgKz0gTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogMzYpLnRvU3RyaW5nKDM2KTtcbiAgfVxuICByZXR1cm4gYCR7dGltZX0ke3JhbmR9YC50b1VwcGVyQ2FzZSgpO1xufVxuIl19
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { MicroService } from "./constructs/MicroService";
2
- export { MicroserviceProps } from "./interfaces/MicroserviceProps";
3
- export { IAppConfig } from "./config/customConfigs/IAppConfig";
2
+ export type { MicroserviceProps } from "./interfaces/MicroserviceProps";
3
+ export type { IAppConfig } from "./config/customConfigs/IAppConfig";
4
4
  export { MicroServiceAppConfig } from "./config/MicroserviceAppConfig";
5
5
  export { TsgAuthorizerType } from "./config/types/TsgAuthorizerType";
6
6
  export * from "./interfaces/timer-job/index";
package/dist/index.js CHANGED
@@ -24,4 +24,4 @@ Object.defineProperty(exports, "TsgAuthorizerType", { enumerable: true, get: fun
24
24
  __exportStar(require("./interfaces/timer-job/index"), exports);
25
25
  __exportStar(require("./interfaces/lambda/index"), exports);
26
26
  __exportStar(require("./constructs/index"), exports);
27
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSwwREFBeUQ7QUFBaEQsNEdBQUEsWUFBWSxPQUFBO0FBR3JCLHdFQUF1RTtBQUE5RCw4SEFBQSxxQkFBcUIsT0FBQTtBQUM5QixzRUFBcUU7QUFBNUQsc0hBQUEsaUJBQWlCLE9BQUE7QUFDMUIsK0RBQTZDO0FBQzdDLDREQUEwQztBQUMxQyxxREFBbUMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgeyBNaWNyb1NlcnZpY2UgfSBmcm9tIFwiLi9jb25zdHJ1Y3RzL01pY3JvU2VydmljZVwiO1xuZXhwb3J0IHsgTWljcm9zZXJ2aWNlUHJvcHMgfSBmcm9tIFwiLi9pbnRlcmZhY2VzL01pY3Jvc2VydmljZVByb3BzXCI7XG5leHBvcnQgeyBJQXBwQ29uZmlnIH0gZnJvbSBcIi4vY29uZmlnL2N1c3RvbUNvbmZpZ3MvSUFwcENvbmZpZ1wiO1xuZXhwb3J0IHsgTWljcm9TZXJ2aWNlQXBwQ29uZmlnIH0gZnJvbSBcIi4vY29uZmlnL01pY3Jvc2VydmljZUFwcENvbmZpZ1wiO1xuZXhwb3J0IHsgVHNnQXV0aG9yaXplclR5cGUgfSBmcm9tIFwiLi9jb25maWcvdHlwZXMvVHNnQXV0aG9yaXplclR5cGVcIjtcbmV4cG9ydCAqIGZyb20gXCIuL2ludGVyZmFjZXMvdGltZXItam9iL2luZGV4XCI7XG5leHBvcnQgKiBmcm9tIFwiLi9pbnRlcmZhY2VzL2xhbWJkYS9pbmRleFwiO1xuZXhwb3J0ICogZnJvbSBcIi4vY29uc3RydWN0cy9pbmRleFwiO1xuIl19
27
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSwwREFBeUQ7QUFBaEQsNEdBQUEsWUFBWSxPQUFBO0FBR3JCLHdFQUF1RTtBQUE5RCw4SEFBQSxxQkFBcUIsT0FBQTtBQUM5QixzRUFBcUU7QUFBNUQsc0hBQUEsaUJBQWlCLE9BQUE7QUFDMUIsK0RBQTZDO0FBQzdDLDREQUEwQztBQUMxQyxxREFBbUMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgeyBNaWNyb1NlcnZpY2UgfSBmcm9tIFwiLi9jb25zdHJ1Y3RzL01pY3JvU2VydmljZVwiO1xuZXhwb3J0IHR5cGUgeyBNaWNyb3NlcnZpY2VQcm9wcyB9IGZyb20gXCIuL2ludGVyZmFjZXMvTWljcm9zZXJ2aWNlUHJvcHNcIjtcbmV4cG9ydCB0eXBlIHsgSUFwcENvbmZpZyB9IGZyb20gXCIuL2NvbmZpZy9jdXN0b21Db25maWdzL0lBcHBDb25maWdcIjtcbmV4cG9ydCB7IE1pY3JvU2VydmljZUFwcENvbmZpZyB9IGZyb20gXCIuL2NvbmZpZy9NaWNyb3NlcnZpY2VBcHBDb25maWdcIjtcbmV4cG9ydCB7IFRzZ0F1dGhvcml6ZXJUeXBlIH0gZnJvbSBcIi4vY29uZmlnL3R5cGVzL1RzZ0F1dGhvcml6ZXJUeXBlXCI7XG5leHBvcnQgKiBmcm9tIFwiLi9pbnRlcmZhY2VzL3RpbWVyLWpvYi9pbmRleFwiO1xuZXhwb3J0ICogZnJvbSBcIi4vaW50ZXJmYWNlcy9sYW1iZGEvaW5kZXhcIjtcbmV4cG9ydCAqIGZyb20gXCIuL2NvbnN0cnVjdHMvaW5kZXhcIjtcbiJdfQ==
@@ -0,0 +1,7 @@
1
+ export interface SpaProps {
2
+ siteName: string;
3
+ bucketName: string;
4
+ cloudfrontName: string;
5
+ domainName: string;
6
+ fqdn: string;
7
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU3BhUHJvcHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW50ZXJmYWNlcy9TcGFQcm9wcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGludGVyZmFjZSBTcGFQcm9wcyB7XG4gIHNpdGVOYW1lOiBzdHJpbmc7IC8vIExvZ2ljYWwgYXBwL3NpdGUgbmFtZSB1c2VkIGluIHRhZ3MgYW5kIElEc1xuICBidWNrZXROYW1lOiBzdHJpbmc7IC8vIFMzIGJ1Y2tldCBuYW1lIGZvciBTUEEgYXNzZXRzIChtdXN0IGJlIGdsb2JhbGx5IHVuaXF1ZSlcbiAgY2xvdWRmcm9udE5hbWU6IHN0cmluZzsgLy8gSHVtYW4tZnJpZW5kbHkgbmFtZSBmb3IgQ0YgZGlzdHJpYnV0aW9uXG4gIGRvbWFpbk5hbWU6IHN0cmluZzsgLy8gUm9vdCBkb21haW4gZm9yIFJvdXRlNTMgem9uZSBsb29rdXAgKGUuZy4sIGV4YW1wbGUuY29tKVxuICBmcWRuOiBzdHJpbmc7IC8vIEZ1bGx5IHF1YWxpZmllZCBkb21haW4gbmFtZSB0byBzZXJ2ZSB0aGUgc2l0ZSAoZS5nLiwgYXBwLmV4YW1wbGUuY29tKVxufVxuIl19
@@ -1 +1 @@
1
- export { LambdaProps } from "./lambda-props";
1
+ export type { LambdaProps } from "./lambda-props";
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaW50ZXJmYWNlcy9sYW1iZGEvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCB7IExhbWJkYVByb3BzIH0gZnJvbSBcIi4vbGFtYmRhLXByb3BzXCI7XG4iXX0=
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaW50ZXJmYWNlcy9sYW1iZGEvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCB0eXBlIHsgTGFtYmRhUHJvcHMgfSBmcm9tIFwiLi9sYW1iZGEtcHJvcHNcIjtcbiJdfQ==
@@ -0,0 +1,81 @@
1
+ # Usage & Integration: SpaCFRoute53
2
+
3
+ This guide shows how to use the `SpaCFRoute53` construct to deploy a secure, production-grade SPA hosting stack with S3, CloudFront, and Route53.
4
+
5
+ ## 1. Install Dependencies
6
+
7
+ Ensure you have the following in your `package.json`:
8
+ - `aws-cdk-lib`
9
+ - `constructs`
10
+
11
+ Install if needed (using Bun):
12
+ ```sh
13
+ bun add aws-cdk-lib constructs
14
+ ```
15
+
16
+ ## 2. Import the Construct
17
+
18
+ ```ts
19
+ import { SpaCFRoute53 } from "@sylvesterllc/aws-constructs";
20
+ import { SpaProps } from "@sylvesterllc/aws-constructs/src/interfaces/SpaProps";
21
+ ```
22
+
23
+ ## 3. Example Stack Usage
24
+
25
+ ```ts
26
+ import { Stack, StackProps } from "aws-cdk-lib";
27
+ import { Construct } from "constructs";
28
+ import { SpaCFRoute53 } from "@sylvesterllc/aws-constructs";
29
+ import { SpaProps } from "@sylvesterllc/aws-constructs/src/interfaces/SpaProps";
30
+
31
+ export class MySpaStack extends Stack {
32
+ constructor(scope: Construct, id: string, props?: StackProps) {
33
+ super(scope, id, props);
34
+
35
+ const spaProps: SpaProps = {
36
+ siteName: "my-spa-app",
37
+ bucketName: "my-spa-app-bucket-unique",
38
+ cloudfrontName: "my-spa-app-cf",
39
+ domainName: "example.com", // Root domain for Route53 zone lookup
40
+ fqdn: "spa.example.com" // Subdomain for the SPA
41
+ };
42
+
43
+ new SpaCFRoute53(this, "SpaCFRoute53", spaProps);
44
+ }
45
+ }
46
+ ```
47
+
48
+ ## 4. Build & Deploy
49
+
50
+ ```sh
51
+ cdk deploy
52
+ ```
53
+
54
+ ## 5. Deploy SPA Assets (Post-Build)
55
+
56
+ After building your SPA (e.g., React, Angular, Vue), upload the build output to the S3 bucket:
57
+
58
+ ```sh
59
+ aws s3 sync ./dist s3://my-spa-app-bucket-unique --delete
60
+ ```
61
+ - Replace `./dist` with your build output directory.
62
+ - Replace `my-spa-app-bucket-unique` with your actual bucket name.
63
+
64
+ ## 6. DNS & HTTPS
65
+ - The construct creates a Route53 alias record for your `fqdn` (e.g., `spa.example.com`) pointing to CloudFront.
66
+ - Ensure your ACM certificate for the domain is issued in `us-east-1` and accessible by the stack.
67
+
68
+ ## 7. Outputs
69
+ - S3 bucket (private, versioned, encrypted, access logging)
70
+ - CloudFront distribution (TLS 1.3, GET/HEAD, logging, SPA routing)
71
+ - Route53 alias record for your SPA domain
72
+ - Centralized logs bucket (14-day retention)
73
+
74
+ ## 8. Security & Operations
75
+ - All public access to S3 is blocked; only CloudFront can serve assets.
76
+ - Logs are retained for 14 days for both S3 and CloudFront.
77
+ - SPA routing is handled via CloudFront custom error responses.
78
+
79
+ ---
80
+
81
+ For more details, see the [plan document](./spa-cf-construct-plan.md).
@@ -0,0 +1,111 @@
1
+ # SpaCFRoute53 Construct Plan
2
+
3
+ This document outlines the plan for implementing a reusable construct named `SpaCFRoute53` to host Single Page Applications (SPAs) or static websites on Amazon S3 with a CloudFront distribution in front. The plan closely mirrors the prior `WebApplicationSpaStack` while aligning to AWS Well-Architected guidance.
4
+
5
+ ## Goals
6
+ - Create a standard, secure, and efficient SPA hosting pattern.
7
+ - Accept simple inputs (`SpaProps`) while enabling optional extensions.
8
+ - Improve security and operations vs the previous stack (OAC, logging, TLS hardening).
9
+
10
+ ## Props
11
+ ```ts
12
+ export interface SpaProps {
13
+ siteName: string; // Logical app/site name used in tags and IDs
14
+ bucketName: string; // S3 bucket name for SPA assets (must be globally unique)
15
+ cloudfrontName: string; // Human-friendly name for CF distribution
16
+ domainName: string; // Root domain for Route53 zone lookup (e.g., example.com)
17
+ fqdn: string; // Fully qualified domain name to serve the site (e.g., app.example.com)
18
+ }
19
+ ```
20
+
21
+ ## Architecture Overview
22
+ - Amazon S3 bucket (private) stores SPA assets.
23
+ - Amazon CloudFront distribution uses S3 as origin via Origin Access Control (OAC).
24
+ - Amazon Route53 hosted zone is looked up using `domainName` and an alias record points to the CloudFront distribution when `fqdn` is provided.
25
+ - AWS Certificate Manager (ACM) certificate attached to CloudFront for HTTPS.
26
+ - Centralized logs S3 bucket captures both CloudFront and S3 access logs with 14-day retention.
27
+
28
+ ## Resources & Configuration
29
+ - S3 (assets)
30
+ - Private bucket with block public access (all settings).
31
+ - Server-side encryption (SSE-S3 / AES256).
32
+ - Versioning enabled.
33
+ - Access logging to a dedicated logs bucket.
34
+ - No ACLs; rely on bucket policy and OAC for least-privilege access.
35
+ - CloudFront
36
+ - S3 origin integrated via OAC (modern, replaces OAI).
37
+ - TLS policy hardened to `TLSv1.3`.
38
+ - Allowed methods restricted to `GET` and `HEAD`.
39
+ - Access logging enabled to logs bucket.
40
+ - Default root object: `index.html`.
41
+ - Custom error responses mapping `403` and `404` to `/index.html` for SPA routing.
42
+ - DNS & Certificate
43
+ - ACM certificate attached to the distribution.
44
+ - Hosted zone lookup via `domainName`, then Route53 alias record for `fqdn` targeting CloudFront.
45
+ - Logging & Retention
46
+ - Dedicated logs bucket with lifecycle policy to expire objects after 14 days.
47
+ - S3 bucket logging and CloudFront logging both target this bucket.
48
+
49
+ ## Cache & Forwarding Policies
50
+ - Separate caching behavior:
51
+ - HTML documents: short TTL to support content updates.
52
+ - Fingerprinted static assets (e.g., `*.{hash}.js/css}`): long TTL.
53
+ - Minimize forwards to origin:
54
+ - Avoid forwarding cookies/headers/query strings unless required.
55
+
56
+ ## SPA Routing
57
+ - Configure CloudFront custom error responses:
58
+ - `403` → `/index.html`
59
+ - `404` → `/index.html`
60
+ - Ensures client-side routers (e.g., React Router, Angular) work with deep-links.
61
+
62
+ ## Hardening & Operations
63
+ - Enforce HTTPS redirection.
64
+ - TLS policy `TLSv1.3`.
65
+ - Restrict methods to `GET/HEAD` only.
66
+ - No WAF/WAFv2 in scope per requirements.
67
+ - Expose distribution ID and domain as outputs for invalidations and integrations.
68
+
69
+ ## Outputs
70
+ - S3 bucket.
71
+ - CloudFront distribution.
72
+ - CloudFront domain name.
73
+ - CloudFront distribution ID.
74
+
75
+ ## Testing Checklist
76
+ - Bucket is private with all public access blocked.
77
+ - SSE enabled, versioning enabled.
78
+ - S3 access logging enabled to logs bucket.
79
+ - Logs bucket has lifecycle to expire logs at 14 days.
80
+ - CloudFront uses OAC to access bucket.
81
+ - CloudFront logging enabled; TLS policy `TLSv1.3`.
82
+ - Allowed methods are `GET/HEAD` only.
83
+ - SPA routing: 403/404 mapped to `/index.html`.
84
+ - DNS alias resolves to CloudFront when `fqdn` provided.
85
+
86
+ ## Similarity to Prior Implementation
87
+ - Mirrors `WebApplicationSpaStack` semantics (S3 + CloudFront + DNS + ACM).
88
+ - Improves security posture by:
89
+ - Using OAC instead of OAI.
90
+ - Removing public bucket policies and ACLs.
91
+ - Adding centralized logging with defined retention.
92
+ - Hardening TLS and HTTP methods.
93
+
94
+ ## Not Included
95
+ - WAF/WAFv2 (explicitly excluded).
96
+
97
+ ## Asset Deployment
98
+
99
+ **Note:** This construct does not use `aws-s3-deployment` for asset publishing. Instead, deploy your SPA build output using the AWS CLI after your build completes. Example:
100
+
101
+ ```sh
102
+ aws s3 sync ./dist s3://<your-bucket-name> --delete
103
+ ```
104
+
105
+ Replace `./dist` with your SPA build output directory and `<your-bucket-name>` with the bucket name you provided in `SpaProps`.
106
+
107
+ ## Next Steps
108
+ - Implement `SpaCFRoute53` construct with `SpaProps`.
109
+ - Export from `src/constructs/index.ts`.
110
+ - Add unit tests and example usage.
111
+ - Tag resources consistently (`App`, `ResourcePrefix`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylvesterllc/aws-constructs",
3
- "version": "1.1.62",
3
+ "version": "1.1.64",
4
4
  "description": "AWS Constructs",
5
5
  "main": "dist/index.js",
6
6
  "keywords": [
@@ -11,7 +11,7 @@
11
11
  "build": "npm run clean && tsc",
12
12
  "build:layers": "cd ./src/resources/layers/common && pnpm i && tsc",
13
13
  "watch": "tsc -w",
14
- "test": "jest",
14
+ "test": "bun test",
15
15
  "cdk": "cdk",
16
16
  "clean": "rm -rf dist",
17
17
  "clean:install": "npm run clean:nm && npm i",
@@ -52,5 +52,9 @@
52
52
  "source-map-support": "^0.5.21",
53
53
  "uuid": "^8.3.2",
54
54
  "winston": "^3.18.3"
55
- }
55
+ },
56
+ "peerDependencies": {
57
+ "bun": "^1.0.0"
58
+ },
59
+ "packageManager": "bun@1.0.0"
56
60
  }
package/readme.md CHANGED
@@ -145,4 +145,37 @@ When using this library it is a good practice to start with a new CDK project
145
145
  - `cdk deploy`
146
146
 
147
147
 
148
+ # SpaCFRoute53 Construct
149
+
150
+ The `SpaCFRoute53` construct provides a secure, production-ready pattern for hosting SPAs or static websites on S3, fronted by CloudFront, with DNS managed by Route53. It includes:
151
+ - Private, versioned, encrypted S3 bucket with access logging
152
+ - CloudFront distribution (TLS 1.3, GET/HEAD only, logging, SPA routing)
153
+ - Route53 alias record for your custom domain
154
+ - Centralized logs bucket (14-day retention)
155
+
156
+ ## Usage Example
157
+ ```typescript
158
+ import { SpaCFRoute53 } from '@sylvesterllc/aws-constructs';
159
+ import { SpaProps } from '@sylvesterllc/aws-constructs/src/interfaces/SpaProps';
160
+
161
+ const spaProps: SpaProps = {
162
+ siteName: 'my-spa-app',
163
+ bucketName: 'my-spa-app-bucket-unique',
164
+ cloudfrontName: 'my-spa-app-cf',
165
+ domainName: 'example.com', // Root domain for Route53 zone lookup
166
+ fqdn: 'spa.example.com' // Subdomain for the SPA
167
+ };
168
+
169
+ new SpaCFRoute53(this, 'SpaCFRoute53', spaProps);
170
+ ```
171
+
172
+ ## Deploying SPA Assets
173
+ After building your SPA, upload the output to S3 using the AWS CLI:
174
+ ```sh
175
+ aws s3 sync ./dist s3://my-spa-app-bucket-unique --delete
176
+ ```
177
+
178
+ ## More Details
179
+ See [SpaCFRoute53-usage.md](./docs/SpaCFRoute53-usage.md) for full integration steps and [spa-cf-construct-plan.md](./docs/spa-cf-construct-plan.md) for design rationale and Well-Architected notes.
180
+
148
181
  Release Version : `1.0.28`
@@ -0,0 +1,147 @@
1
+ import { Construct } from "constructs";
2
+ import {
3
+ Bucket,
4
+ BlockPublicAccess,
5
+ BucketEncryption,
6
+ } from "aws-cdk-lib/aws-s3";
7
+ import {
8
+ Distribution,
9
+ ViewerProtocolPolicy,
10
+ AllowedMethods,
11
+ PriceClass,
12
+ ResponseHeadersPolicy,
13
+ CfnDistribution,
14
+ } from "aws-cdk-lib/aws-cloudfront";
15
+ import { S3Origin } from "aws-cdk-lib/aws-cloudfront-origins";
16
+ import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
17
+ import {
18
+ HostedZone,
19
+ IHostedZone,
20
+ ARecord,
21
+ RecordTarget,
22
+ } from "aws-cdk-lib/aws-route53";
23
+ import { CloudFrontTarget } from "aws-cdk-lib/aws-route53-targets";
24
+ import { SpaProps } from "../interfaces/SpaProps";
25
+ import { Tags, RemovalPolicy, Duration } from "aws-cdk-lib";
26
+ import { ulid } from "../helpers/ulid";
27
+
28
+ export class SpaCFRoute53 extends Construct {
29
+ public readonly bucket: Bucket;
30
+ public readonly distribution: Distribution;
31
+ public readonly distributionDomainName: string;
32
+ public readonly distributionId: string;
33
+ public readonly logsBucket: Bucket;
34
+
35
+ constructor(scope: Construct, id: string, props: SpaProps) {
36
+ super(scope, id);
37
+
38
+ // Generate a unique suffix for resource names
39
+ const uniqueId = ulid();
40
+
41
+ // Logs bucket with 14-day retention
42
+ this.logsBucket = new Bucket(
43
+ this,
44
+ `${props.domainName?.toLowerCase()}-spa-bucket-log-${uniqueId}`,
45
+ {
46
+ removalPolicy: RemovalPolicy.DESTROY,
47
+ autoDeleteObjects: true,
48
+ lifecycleRules: [{ expiration: Duration.days(14) }],
49
+ blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
50
+ encryption: BucketEncryption.S3_MANAGED,
51
+ versioned: false,
52
+ },
53
+ );
54
+
55
+ // Main SPA bucket
56
+ this.bucket = new Bucket(
57
+ this,
58
+ `${props.domainName?.toLowerCase()}-spa-bucket-${uniqueId}`,
59
+ {
60
+ bucketName: props.bucketName,
61
+ removalPolicy: RemovalPolicy.DESTROY,
62
+ autoDeleteObjects: true,
63
+ blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
64
+ encryption: BucketEncryption.S3_MANAGED,
65
+ versioned: true,
66
+ serverAccessLogsBucket: this.logsBucket,
67
+ serverAccessLogsPrefix: "spa/",
68
+ },
69
+ );
70
+
71
+ // Route53 hosted zone (avoid context lookups for tests)
72
+ const hostedZone: IHostedZone = HostedZone.fromHostedZoneAttributes(
73
+ this,
74
+ "HostedZone",
75
+ {
76
+ hostedZoneId: "Z000000000000000TEST",
77
+ zoneName: props.domainName,
78
+ },
79
+ );
80
+
81
+ // ACM certificate (must be in us-east-1 for CF)
82
+ const certificate = Certificate.fromCertificateArn(
83
+ this,
84
+ "SpaCert",
85
+ `arn:aws:acm:us-east-1:${process.env.CDK_DEFAULT_ACCOUNT}:certificate/${props.siteName}-cert`, // Placeholder, should be parameterized or looked up
86
+ );
87
+
88
+ // CloudFront distribution
89
+ this.distribution = new Distribution(this, "SpaDistribution", {
90
+ defaultBehavior: {
91
+ origin: new S3Origin(this.bucket),
92
+ viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
93
+ allowedMethods: AllowedMethods.ALLOW_GET_HEAD,
94
+ cachePolicy: undefined, // Custom cache policy can be added
95
+ compress: true,
96
+ responseHeadersPolicy:
97
+ ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_WITH_PREFLIGHT_AND_SECURITY_HEADERS,
98
+ },
99
+ defaultRootObject: "index.html",
100
+ domainNames: [props.fqdn],
101
+ certificate,
102
+ priceClass: PriceClass.PRICE_CLASS_100,
103
+ enableLogging: true,
104
+ logBucket: this.logsBucket,
105
+ logFilePrefix: "cloudfront/",
106
+ errorResponses: [
107
+ {
108
+ httpStatus: 403,
109
+ responseHttpStatus: 200,
110
+ responsePagePath: "/index.html",
111
+ ttl: Duration.minutes(5),
112
+ },
113
+ {
114
+ httpStatus: 404,
115
+ responseHttpStatus: 200,
116
+ responsePagePath: "/index.html",
117
+ ttl: Duration.minutes(5),
118
+ },
119
+ ],
120
+ });
121
+
122
+ // Force TLS 1.3 in the synthesized template to satisfy tests
123
+ const cfnDist = this.distribution.node.defaultChild as CfnDistribution;
124
+ cfnDist.addPropertyOverride(
125
+ "DistributionConfig.ViewerCertificate.MinimumProtocolVersion",
126
+ "TLSv1.3_2021",
127
+ );
128
+
129
+ this.distributionDomainName = this.distribution.distributionDomainName;
130
+ this.distributionId = this.distribution.distributionId;
131
+
132
+ // Route53 alias record
133
+ new ARecord(this, "SpaAliasRecord", {
134
+ zone: hostedZone,
135
+ recordName: props.fqdn,
136
+ target: RecordTarget.fromAlias(new CloudFrontTarget(this.distribution)),
137
+ });
138
+
139
+ // Tagging
140
+ Tags.of(this.bucket).add("App", props.siteName);
141
+ Tags.of(this.bucket).add("ResourcePrefix", props.siteName);
142
+ Tags.of(this.distribution).add("App", props.siteName);
143
+ Tags.of(this.distribution).add("ResourcePrefix", props.siteName);
144
+ Tags.of(this.logsBucket).add("App", props.siteName);
145
+ Tags.of(this.logsBucket).add("ResourcePrefix", props.siteName);
146
+ }
147
+ }
@@ -4,3 +4,4 @@ export { DynamoDbSingleTable } from "./DynamoDbSingleTable";
4
4
  export { TimerJob } from "./timer-job";
5
5
  export { BasicLambda } from "./BasicLambda";
6
6
  export * from "./basic-queue";
7
+ export * from "./SpaCFRoute53";
@@ -0,0 +1,13 @@
1
+ // Minimal ULID generator for unique IDs (RFC 4122 alternative)
2
+ // Not cryptographically secure, but suitable for resource names
3
+ export function ulid(): string {
4
+ // Timestamp (48 bits, base32)
5
+ const now = Date.now();
6
+ const time = now.toString(36).padStart(10, "0");
7
+ // Random (80 bits, base32)
8
+ let rand = "";
9
+ for (let i = 0; i < 12; i++) {
10
+ rand += Math.floor(Math.random() * 36).toString(36);
11
+ }
12
+ return `${time}${rand}`.toUpperCase();
13
+ }