@codedrifters/constructs 0.0.17 → 0.0.19
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/chunk-LZOMFHX3.mjs +38 -0
- package/lib/chunk-LZOMFHX3.mjs.map +1 -0
- package/lib/index.js +15 -3
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +17 -31
- package/lib/index.mjs.map +1 -1
- package/lib/static-hosting.viewer-request-handler.d.mts +42 -0
- package/lib/static-hosting.viewer-request-handler.d.ts +42 -0
- package/lib/static-hosting.viewer-request-handler.js +65 -0
- package/lib/static-hosting.viewer-request-handler.js.map +1 -0
- package/lib/static-hosting.viewer-request-handler.mjs +40 -0
- package/lib/static-hosting.viewer-request-handler.mjs.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
+
}) : x)(function(x) {
|
|
10
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
+
});
|
|
13
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
14
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
+
mod
|
|
31
|
+
));
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
__require,
|
|
35
|
+
__commonJS,
|
|
36
|
+
__toESM
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=chunk-LZOMFHX3.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/lib/index.js
CHANGED
|
@@ -173,13 +173,13 @@ var require_lib = __commonJS({
|
|
|
173
173
|
});
|
|
174
174
|
|
|
175
175
|
// src/index.ts
|
|
176
|
-
var
|
|
177
|
-
__export(
|
|
176
|
+
var src_exports = {};
|
|
177
|
+
__export(src_exports, {
|
|
178
178
|
PrivateBucket: () => PrivateBucket,
|
|
179
179
|
StaticContent: () => StaticContent,
|
|
180
180
|
StaticHosting: () => StaticHosting
|
|
181
181
|
});
|
|
182
|
-
module.exports = __toCommonJS(
|
|
182
|
+
module.exports = __toCommonJS(src_exports);
|
|
183
183
|
|
|
184
184
|
// src/s3/private-bucket.ts
|
|
185
185
|
var import_aws_cdk_lib = require("aws-cdk-lib");
|
|
@@ -238,6 +238,8 @@ var StaticContent = class extends import_constructs.Construct {
|
|
|
238
238
|
};
|
|
239
239
|
|
|
240
240
|
// src/static-hosting/static-hosting.ts
|
|
241
|
+
var fs = __toESM(require("fs"));
|
|
242
|
+
var path = __toESM(require("path"));
|
|
241
243
|
var import_aws_cdk_lib2 = require("aws-cdk-lib");
|
|
242
244
|
var import_aws_certificatemanager = require("aws-cdk-lib/aws-certificatemanager");
|
|
243
245
|
var import_aws_cloudfront = require("aws-cdk-lib/aws-cloudfront");
|
|
@@ -287,7 +289,17 @@ var StaticHosting = class extends import_constructs2.Construct {
|
|
|
287
289
|
})
|
|
288
290
|
});
|
|
289
291
|
}
|
|
292
|
+
const handlerJs = path.join(
|
|
293
|
+
__dirname,
|
|
294
|
+
"static-hosting.viewer-request-handler.js"
|
|
295
|
+
);
|
|
296
|
+
const handlerTs = path.join(
|
|
297
|
+
__dirname,
|
|
298
|
+
"static-hosting.viewer-request-handler.ts"
|
|
299
|
+
);
|
|
300
|
+
const handlerEntry = fs.existsSync(handlerJs) ? handlerJs : handlerTs;
|
|
290
301
|
const handler = new import_aws_lambda_nodejs.NodejsFunction(this, "viewer-request-handler", {
|
|
302
|
+
entry: handlerEntry,
|
|
291
303
|
memorySize: 128,
|
|
292
304
|
runtime: import_aws_lambda.Runtime.NODEJS_24_X,
|
|
293
305
|
logGroup: new import_aws_logs.LogGroup(this, "viewer-request-handler-log-group", {
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../utils/src/aws/aws-types.ts","../../utils/src/git/git-utils.ts","../../utils/src/string/string-utils.ts","../../utils/src/index.ts","../src/index.ts","../src/s3/private-bucket.ts","../src/static-hosting/static-content.ts","../src/static-hosting/static-hosting.ts"],"sourcesContent":["/**\n * Stage Types\n *\n * What stage of deployment is this? Dev, staging, or prod?\n */\nexport const AWS_STAGE_TYPE = {\n /**\n * Development environment, typically used for testing and development.\n */\n DEV: \"dev\",\n\n /**\n * Staging environment, used for pre-production testing.\n */\n STAGE: \"stage\",\n\n /**\n * Production environment, used for live deployments.\n */\n PROD: \"prod\",\n} as const;\n\n/**\n * Above const as a type.\n */\nexport type AwsStageType = (typeof AWS_STAGE_TYPE)[keyof typeof AWS_STAGE_TYPE];\n\n/**\n * Deployment target role: whether an (account, region) is the primary or\n * secondary deployment target (e.g. primary vs replica region).\n */\nexport const DEPLOYMENT_TARGET_ROLE = {\n /**\n * Account and region that represents the primary region for this service.\n * For example, the base DynamoDB Region for global tables.\n */\n PRIMARY: \"primary\",\n /**\n * Account and region that represents a secondary region for this service.\n * For example, a replica region for a global DynamoDB table.\n */\n SECONDARY: \"secondary\",\n} as const;\n\n/**\n * Type for deployment target role values.\n */\nexport type DeploymentTargetRoleType =\n (typeof DEPLOYMENT_TARGET_ROLE)[keyof typeof DEPLOYMENT_TARGET_ROLE];\n\n/**\n * Environment types (primary/secondary).\n *\n * @deprecated Use {@link DEPLOYMENT_TARGET_ROLE} instead. This constant is maintained for backward compatibility.\n */\nexport const AWS_ENVIRONMENT_TYPE = DEPLOYMENT_TARGET_ROLE;\n\n/**\n * Type for environment type values.\n *\n * @deprecated Use {@link DeploymentTargetRoleType} instead. This type is maintained for backward compatibility.\n */\nexport type AwsEnvironmentType = DeploymentTargetRoleType;\n","import { execSync } from \"node:child_process\";\n\n/**\n * Returns the current full git branch name\n *\n * ie: feature/1234 returns feature/1234\n *\n */\nexport const findGitBranch = (): string => {\n return execSync(\"git rev-parse --abbrev-ref HEAD\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\");\n};\n\nexport const findGitRepoName = (): string => {\n /**\n * When running in github actions this will be populated.\n */\n if (process.env.GITHUB_REPOSITORY) {\n return process.env.GITHUB_REPOSITORY;\n }\n\n /**\n * locally, we need to extract the repo name from the git config.\n */\n const remote = execSync(\"git config --get remote.origin.url\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\")\n .trim();\n\n const match = remote.match(/[:\\/]([^/]+\\/[^/]+?)(?:\\.git)?$/);\n const repoName = match ? match[1] : \"error-repo-name\";\n\n return repoName;\n};\n","import * as crypto from \"node:crypto\";\n\n/**\n *\n * @param inString string to hash\n * @param trimLength trim to this length (defaults to 999 chars)\n * @returns\n */\nexport const hashString = (inString: string, trimLength: number = 999) => {\n return crypto\n .createHash(\"sha256\")\n .update(inString)\n .digest(\"hex\")\n .substring(0, trimLength);\n};\n\n/**\n *\n * @param inputString string to truncate\n * @param maxLength max length of this string\n * @returns trimmed string\n */\nexport const trimStringLength = (inputString: string, maxLength: number) => {\n return inputString.length < maxLength\n ? inputString\n : inputString.substring(0, maxLength);\n};\n","export * from \"./aws/aws-types\";\nexport * from \"./git/git-utils\";\nexport * from \"./string/string-utils\";\n","export * from \"./s3\";\nexport * from \"./static-hosting\";\n","import { RemovalPolicy } from \"aws-cdk-lib\";\nimport {\n BlockPublicAccess,\n Bucket,\n BucketProps,\n ObjectOwnership,\n} from \"aws-cdk-lib/aws-s3\";\nimport { Construct } from \"constructs\";\n\nexport interface PrivateBucketProps extends BucketProps {}\n\nexport class PrivateBucket extends Bucket {\n constructor(scope: Construct, id: string, props: PrivateBucketProps = {}) {\n const defaultProps = {\n removalPolicy: props.removalPolicy ?? RemovalPolicy.RETAIN,\n autoDeleteObjects: props.removalPolicy === RemovalPolicy.DESTROY,\n };\n\n const requiredProps = {\n publicReadAccess: false,\n blockPublicAccess: BlockPublicAccess.BLOCK_ALL,\n enforceSSL: true,\n objectOwnership: ObjectOwnership.BUCKET_OWNER_ENFORCED,\n };\n\n super(scope, id, { ...defaultProps, ...props, ...requiredProps });\n }\n}\n","// eslint-disable-next-line import/no-extraneous-dependencies\nimport { findGitBranch } from \"@codedrifters/utils\";\nimport { Bucket } from \"aws-cdk-lib/aws-s3\";\nimport { BucketDeployment, Source } from \"aws-cdk-lib/aws-s3-deployment\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { paramCase } from \"change-case\";\nimport { Construct } from \"constructs\";\n\n/*******************************************************************************\n *\n * STATIC CONTENT UPLOADER\n *\n * This construct uploads a directory of content from a local location into S3.\n *\n * To support PR and branch specific builds, each S3 bucket can store content\n * for multiple domains and builds, using the following format:\n *\n * S3-bucket/domain/*\n *\n * A bucket used to store content for stage.openhi.org might have the\n * following directory structure (all in the same bucket).\n *\n * /stage.openhi.org/* -> serves content to stage.openhi.org\n * /feature-7.stage.openhi.org/* -> serves content to feature-7.stage.openhi.org\n * /pr-123.stage.openhi.org/* -> serves content to pr-123.stage.openhi.org\n *\n ******************************************************************************/\n\nexport interface StaticContentProps {\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Absolute path to directory containing content for the website.\n */\n readonly contentSourceDirectory: string;\n\n /**\n * Directory to place content into. Should start with a slash.\n * Example: '/widget'\n */\n readonly contentDestinationDirectory: string;\n\n /**\n * The sub domain prefix (ie: images)\n *\n * @default git branch name\n */\n readonly subDomain?: string;\n\n /**\n * The full domain (ie: staging.codedrifters.com)\n */\n readonly fullDomain: string;\n}\n\nexport class StaticContent extends Construct {\n constructor(scope: Construct, id: string, props: StaticContentProps) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n contentSourceDirectory,\n contentDestinationDirectory,\n subDomain,\n fullDomain,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n subDomain: findGitBranch(),\n ...props,\n };\n\n /***************************************************************************\n *\n * Import and build some values from Param Store during deployment.\n *\n **************************************************************************/\n\n const keyPrefix = [paramCase(subDomain), fullDomain].join(\".\");\n\n const bucketArn = StringParameter.valueForStringParameter(\n this,\n bucketArnParamName,\n );\n const bucket = Bucket.fromBucketArn(this, \"bucket\", bucketArn);\n\n /***************************************************************************\n *\n * Gather the sources we'll be deploying. We need to have an empty source\n * for tests since it will change all the time and bork up the test\n * snapshots if we don't.\n *\n **************************************************************************/\n\n const sources = process.env.JEST_WORKER_ID\n ? []\n : [Source.asset(contentSourceDirectory)];\n\n new BucketDeployment(this, \"deploy\", {\n sources,\n destinationBucket: bucket,\n retainOnDelete: false,\n destinationKeyPrefix: `${keyPrefix}${contentDestinationDirectory}`,\n });\n }\n}\n","import { Duration, StackProps } from \"aws-cdk-lib\";\nimport {\n Certificate,\n CertificateValidation,\n} from \"aws-cdk-lib/aws-certificatemanager\";\nimport {\n AccessLevel,\n AllowedMethods,\n CacheCookieBehavior,\n CacheHeaderBehavior,\n CachePolicy,\n CacheQueryStringBehavior,\n Distribution,\n LambdaEdgeEventType,\n S3OriginAccessControl,\n Signing,\n ViewerProtocolPolicy,\n} from \"aws-cdk-lib/aws-cloudfront\";\nimport { S3BucketOrigin } from \"aws-cdk-lib/aws-cloudfront-origins\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { LogGroup, RetentionDays } from \"aws-cdk-lib/aws-logs\";\nimport {\n ARecord,\n HostedZone,\n HostedZoneAttributes,\n IHostedZone,\n RecordTarget,\n} from \"aws-cdk-lib/aws-route53\";\nimport { CloudFrontTarget } from \"aws-cdk-lib/aws-route53-targets\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\nimport { PrivateBucket, PrivateBucketProps } from \"../s3/private-bucket\";\n\nexport interface StaticDomainProps {\n /**\n * The base domain (ie: codedrifters.com)\n */\n readonly baseDomain: string;\n\n /**\n * Hosted zone ID for the base domain.\n */\n readonly hostedZoneAttributes: HostedZoneAttributes;\n}\n\nexport interface StaticHostingProps extends StackProps {\n /**\n * Short description used in various places for traceability.\n */\n readonly description?: string;\n\n /**\n * Values used to connect a domain name to the cloudfront distribution. If not\n * supplied, cloudfront doesn't use a custom domain.\n */\n readonly staticDomainProps?: StaticDomainProps;\n\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution Domain Name.\n */\n readonly distributionDomainParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution ID.\n */\n readonly distributionIDParamName?: string;\n\n /**\n * Props to pass to the private S3 bucket.\n */\n readonly privateBucketProps?: PrivateBucketProps;\n}\n\nexport class StaticHosting extends Construct {\n /**\n * Full domain name used as basis for hosting.\n */\n public readonly fullDomain: string;\n\n constructor(scope: Construct, id: string, props: StaticHostingProps = {}) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n distributionDomainParamName,\n distributionIDParamName,\n staticDomainProps,\n privateBucketProps,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n distributionDomainParamName: \"/STATIC_WEBSITE/DISTRIBUTION_DOMAIN\",\n distributionIDParamName: \"/STATIC_WEBSITE/DISTRIBUTION_ID\",\n ...props,\n };\n\n const { baseDomain, hostedZoneAttributes } = staticDomainProps ?? {};\n\n /***************************************************************************\n *\n * PRIVATE BUCKET\n *\n * A bucket to store the files within.\n * Save ARN for later deploys.\n *\n **************************************************************************/\n\n const bucket = new PrivateBucket(\n this,\n \"static-hosting-bucket\",\n privateBucketProps,\n );\n\n /***************************************************************************\n *\n * DNS & Wildcard Certificate\n *\n * If a zone Id as passed in, find the hosted zone and create a wildcard\n * certificate for the domain.\n *\n **************************************************************************/\n\n let zone: IHostedZone | undefined;\n let certificate: Certificate | undefined;\n\n if (hostedZoneAttributes && baseDomain) {\n zone = HostedZone.fromHostedZoneAttributes(\n this,\n \"zone\",\n hostedZoneAttributes,\n );\n certificate = new Certificate(this, \"wildcard-certificate\", {\n domainName: `*.${baseDomain}`,\n subjectAlternativeNames: [baseDomain],\n validation: CertificateValidation.fromDnsMultiZone({\n [`*.${baseDomain}`]: zone,\n [baseDomain]: zone,\n }),\n });\n }\n\n /******************************************************************************\n *\n * LAMBDA@EDGE FUNCTION\n *\n * This handles rewriting the path from domain name.\n *\n *****************************************************************************/\n\n const handler = new NodejsFunction(this, \"viewer-request-handler\", {\n memorySize: 128,\n runtime: Runtime.NODEJS_24_X,\n logGroup: new LogGroup(this, \"viewer-request-handler-log-group\", {\n retention: RetentionDays.ONE_MONTH,\n }),\n });\n\n /******************************************************************************\n *\n * CLOUDFRONT CONFIG\n *\n * Setup a CloudFront Distribution for the bucket.\n *\n *****************************************************************************/\n\n const cachePolicy = new CachePolicy(this, \"cloudfront-policy\", {\n comment: \"Relatively conservative TTL policy.\",\n maxTtl: Duration.seconds(300),\n minTtl: Duration.seconds(0),\n defaultTtl: Duration.seconds(60),\n headerBehavior: CacheHeaderBehavior.none(),\n queryStringBehavior: CacheQueryStringBehavior.none(),\n cookieBehavior: CacheCookieBehavior.none(),\n enableAcceptEncodingGzip: true,\n enableAcceptEncodingBrotli: true,\n });\n\n const oac = new S3OriginAccessControl(this, \"MyOAC\", {\n signing: Signing.SIGV4_NO_OVERRIDE,\n });\n const origin = S3BucketOrigin.withOriginAccessControl(bucket, {\n originAccessControl: oac,\n originAccessLevels: [AccessLevel.READ],\n });\n\n const distribution = new Distribution(this, \"cloudfront-distribution\", {\n comment: `Distribution for ${props.description ?? id}`,\n\n /**\n * Only if domain was supplied\n */\n ...(certificate && baseDomain\n ? {\n certificate,\n domainNames: [baseDomain, `*.${baseDomain}`],\n }\n : {}),\n\n defaultBehavior: {\n origin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n cachePolicy,\n allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,\n edgeLambdas: [\n {\n functionVersion: handler.currentVersion,\n eventType: LambdaEdgeEventType.VIEWER_REQUEST,\n },\n ],\n },\n defaultRootObject: \"index.html\",\n });\n\n /**\n * We finally have enough information to set the full domain.\n */\n this.fullDomain =\n certificate && baseDomain ? baseDomain : distribution.domainName;\n\n /***************************************************************************\n *\n * DNS ENTRY\n *\n * Link cloudfront to both the root fulldomain and all possible subdomains.\n *\n **************************************************************************/\n\n if (zone) {\n new ARecord(this, \"root-dns-entry\", {\n zone,\n recordName: baseDomain ? baseDomain : \"\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n\n new ARecord(this, \"wc-dns-entry\", {\n zone,\n recordName: baseDomain ? `*.${baseDomain}` : \"*\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n }\n\n /***************************************************************************\n *\n * EXPORTS\n *\n * Used by content uploader later.\n *\n **************************************************************************/\n\n new StringParameter(this, \"dist-domain\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionDomainParamName,\n stringValue: distribution.domainName,\n });\n\n new StringParameter(this, \"dist-id\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionIDParamName,\n stringValue: distribution.distributionId,\n });\n\n new StringParameter(this, \"bucket-arn\", {\n description: `GENERATED DO NOT CHANGE - S3 Bucket ARN for (${props.description ?? id}).`,\n parameterName: bucketArnParamName,\n stringValue: bucket.bucketArn,\n });\n }\n}\n"],"mappings":"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","names":["exports","findGitBranch","exports","exports","exports","import_aws_s3","import_aws_cdk_lib","import_aws_ssm","import_constructs"]}
|
|
1
|
+
{"version":3,"sources":["../../utils/src/aws/aws-types.ts","../../utils/src/git/git-utils.ts","../../utils/src/string/string-utils.ts","../../utils/src/index.ts","../src/index.ts","../src/s3/private-bucket.ts","../src/static-hosting/static-content.ts","../src/static-hosting/static-hosting.ts"],"sourcesContent":["/**\n * Stage Types\n *\n * What stage of deployment is this? Dev, staging, or prod?\n */\nexport const AWS_STAGE_TYPE = {\n /**\n * Development environment, typically used for testing and development.\n */\n DEV: \"dev\",\n\n /**\n * Staging environment, used for pre-production testing.\n */\n STAGE: \"stage\",\n\n /**\n * Production environment, used for live deployments.\n */\n PROD: \"prod\",\n} as const;\n\n/**\n * Above const as a type.\n */\nexport type AwsStageType = (typeof AWS_STAGE_TYPE)[keyof typeof AWS_STAGE_TYPE];\n\n/**\n * Deployment target role: whether an (account, region) is the primary or\n * secondary deployment target (e.g. primary vs replica region).\n */\nexport const DEPLOYMENT_TARGET_ROLE = {\n /**\n * Account and region that represents the primary region for this service.\n * For example, the base DynamoDB Region for global tables.\n */\n PRIMARY: \"primary\",\n /**\n * Account and region that represents a secondary region for this service.\n * For example, a replica region for a global DynamoDB table.\n */\n SECONDARY: \"secondary\",\n} as const;\n\n/**\n * Type for deployment target role values.\n */\nexport type DeploymentTargetRoleType =\n (typeof DEPLOYMENT_TARGET_ROLE)[keyof typeof DEPLOYMENT_TARGET_ROLE];\n\n/**\n * Environment types (primary/secondary).\n *\n * @deprecated Use {@link DEPLOYMENT_TARGET_ROLE} instead. This constant is maintained for backward compatibility.\n */\nexport const AWS_ENVIRONMENT_TYPE = DEPLOYMENT_TARGET_ROLE;\n\n/**\n * Type for environment type values.\n *\n * @deprecated Use {@link DeploymentTargetRoleType} instead. This type is maintained for backward compatibility.\n */\nexport type AwsEnvironmentType = DeploymentTargetRoleType;\n","import { execSync } from \"node:child_process\";\n\n/**\n * Returns the current full git branch name\n *\n * ie: feature/1234 returns feature/1234\n *\n */\nexport const findGitBranch = (): string => {\n return execSync(\"git rev-parse --abbrev-ref HEAD\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\");\n};\n\nexport const findGitRepoName = (): string => {\n /**\n * When running in github actions this will be populated.\n */\n if (process.env.GITHUB_REPOSITORY) {\n return process.env.GITHUB_REPOSITORY;\n }\n\n /**\n * locally, we need to extract the repo name from the git config.\n */\n const remote = execSync(\"git config --get remote.origin.url\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\")\n .trim();\n\n const match = remote.match(/[:\\/]([^/]+\\/[^/]+?)(?:\\.git)?$/);\n const repoName = match ? match[1] : \"error-repo-name\";\n\n return repoName;\n};\n","import * as crypto from \"node:crypto\";\n\n/**\n *\n * @param inString string to hash\n * @param trimLength trim to this length (defaults to 999 chars)\n * @returns\n */\nexport const hashString = (inString: string, trimLength: number = 999) => {\n return crypto\n .createHash(\"sha256\")\n .update(inString)\n .digest(\"hex\")\n .substring(0, trimLength);\n};\n\n/**\n *\n * @param inputString string to truncate\n * @param maxLength max length of this string\n * @returns trimmed string\n */\nexport const trimStringLength = (inputString: string, maxLength: number) => {\n return inputString.length < maxLength\n ? inputString\n : inputString.substring(0, maxLength);\n};\n","export * from \"./aws/aws-types\";\nexport * from \"./git/git-utils\";\nexport * from \"./string/string-utils\";\n","export * from \"./s3\";\nexport * from \"./static-hosting\";\n","import { RemovalPolicy } from \"aws-cdk-lib\";\nimport {\n BlockPublicAccess,\n Bucket,\n BucketProps,\n ObjectOwnership,\n} from \"aws-cdk-lib/aws-s3\";\nimport { Construct } from \"constructs\";\n\nexport interface PrivateBucketProps extends BucketProps {}\n\nexport class PrivateBucket extends Bucket {\n constructor(scope: Construct, id: string, props: PrivateBucketProps = {}) {\n const defaultProps = {\n removalPolicy: props.removalPolicy ?? RemovalPolicy.RETAIN,\n autoDeleteObjects: props.removalPolicy === RemovalPolicy.DESTROY,\n };\n\n const requiredProps = {\n publicReadAccess: false,\n blockPublicAccess: BlockPublicAccess.BLOCK_ALL,\n enforceSSL: true,\n objectOwnership: ObjectOwnership.BUCKET_OWNER_ENFORCED,\n };\n\n super(scope, id, { ...defaultProps, ...props, ...requiredProps });\n }\n}\n","// eslint-disable-next-line import/no-extraneous-dependencies\nimport { findGitBranch } from \"@codedrifters/utils\";\nimport { Bucket } from \"aws-cdk-lib/aws-s3\";\nimport { BucketDeployment, Source } from \"aws-cdk-lib/aws-s3-deployment\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { paramCase } from \"change-case\";\nimport { Construct } from \"constructs\";\n\n/*******************************************************************************\n *\n * STATIC CONTENT UPLOADER\n *\n * This construct uploads a directory of content from a local location into S3.\n *\n * To support PR and branch specific builds, each S3 bucket can store content\n * for multiple domains and builds, using the following format:\n *\n * S3-bucket/domain/*\n *\n * A bucket used to store content for stage.openhi.org might have the\n * following directory structure (all in the same bucket).\n *\n * /stage.openhi.org/* -> serves content to stage.openhi.org\n * /feature-7.stage.openhi.org/* -> serves content to feature-7.stage.openhi.org\n * /pr-123.stage.openhi.org/* -> serves content to pr-123.stage.openhi.org\n *\n ******************************************************************************/\n\nexport interface StaticContentProps {\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Absolute path to directory containing content for the website.\n */\n readonly contentSourceDirectory: string;\n\n /**\n * Directory to place content into. Should start with a slash.\n * Example: '/widget'\n */\n readonly contentDestinationDirectory: string;\n\n /**\n * The sub domain prefix (ie: images)\n *\n * @default git branch name\n */\n readonly subDomain?: string;\n\n /**\n * The full domain (ie: staging.codedrifters.com)\n */\n readonly fullDomain: string;\n}\n\nexport class StaticContent extends Construct {\n constructor(scope: Construct, id: string, props: StaticContentProps) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n contentSourceDirectory,\n contentDestinationDirectory,\n subDomain,\n fullDomain,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n subDomain: findGitBranch(),\n ...props,\n };\n\n /***************************************************************************\n *\n * Import and build some values from Param Store during deployment.\n *\n **************************************************************************/\n\n const keyPrefix = [paramCase(subDomain), fullDomain].join(\".\");\n\n const bucketArn = StringParameter.valueForStringParameter(\n this,\n bucketArnParamName,\n );\n const bucket = Bucket.fromBucketArn(this, \"bucket\", bucketArn);\n\n /***************************************************************************\n *\n * Gather the sources we'll be deploying. We need to have an empty source\n * for tests since it will change all the time and bork up the test\n * snapshots if we don't.\n *\n **************************************************************************/\n\n const sources = process.env.JEST_WORKER_ID\n ? []\n : [Source.asset(contentSourceDirectory)];\n\n new BucketDeployment(this, \"deploy\", {\n sources,\n destinationBucket: bucket,\n retainOnDelete: false,\n destinationKeyPrefix: `${keyPrefix}${contentDestinationDirectory}`,\n });\n }\n}\n","import * as fs from \"fs\";\nimport * as path from \"path\";\nimport { Duration, StackProps } from \"aws-cdk-lib\";\nimport {\n Certificate,\n CertificateValidation,\n} from \"aws-cdk-lib/aws-certificatemanager\";\nimport {\n AccessLevel,\n AllowedMethods,\n CacheCookieBehavior,\n CacheHeaderBehavior,\n CachePolicy,\n CacheQueryStringBehavior,\n Distribution,\n LambdaEdgeEventType,\n S3OriginAccessControl,\n Signing,\n ViewerProtocolPolicy,\n} from \"aws-cdk-lib/aws-cloudfront\";\nimport { S3BucketOrigin } from \"aws-cdk-lib/aws-cloudfront-origins\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { LogGroup, RetentionDays } from \"aws-cdk-lib/aws-logs\";\nimport {\n ARecord,\n HostedZone,\n HostedZoneAttributes,\n IHostedZone,\n RecordTarget,\n} from \"aws-cdk-lib/aws-route53\";\nimport { CloudFrontTarget } from \"aws-cdk-lib/aws-route53-targets\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\nimport { PrivateBucket, PrivateBucketProps } from \"../s3/private-bucket\";\n\nexport interface StaticDomainProps {\n /**\n * The base domain (ie: codedrifters.com)\n */\n readonly baseDomain: string;\n\n /**\n * Hosted zone ID for the base domain.\n */\n readonly hostedZoneAttributes: HostedZoneAttributes;\n}\n\nexport interface StaticHostingProps extends StackProps {\n /**\n * Short description used in various places for traceability.\n */\n readonly description?: string;\n\n /**\n * Values used to connect a domain name to the cloudfront distribution. If not\n * supplied, cloudfront doesn't use a custom domain.\n */\n readonly staticDomainProps?: StaticDomainProps;\n\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution Domain Name.\n */\n readonly distributionDomainParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution ID.\n */\n readonly distributionIDParamName?: string;\n\n /**\n * Props to pass to the private S3 bucket.\n */\n readonly privateBucketProps?: PrivateBucketProps;\n}\n\nexport class StaticHosting extends Construct {\n /**\n * Full domain name used as basis for hosting.\n */\n public readonly fullDomain: string;\n\n constructor(scope: Construct, id: string, props: StaticHostingProps = {}) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n distributionDomainParamName,\n distributionIDParamName,\n staticDomainProps,\n privateBucketProps,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n distributionDomainParamName: \"/STATIC_WEBSITE/DISTRIBUTION_DOMAIN\",\n distributionIDParamName: \"/STATIC_WEBSITE/DISTRIBUTION_ID\",\n ...props,\n };\n\n const { baseDomain, hostedZoneAttributes } = staticDomainProps ?? {};\n\n /***************************************************************************\n *\n * PRIVATE BUCKET\n *\n * A bucket to store the files within.\n * Save ARN for later deploys.\n *\n **************************************************************************/\n\n const bucket = new PrivateBucket(\n this,\n \"static-hosting-bucket\",\n privateBucketProps,\n );\n\n /***************************************************************************\n *\n * DNS & Wildcard Certificate\n *\n * If a zone Id as passed in, find the hosted zone and create a wildcard\n * certificate for the domain.\n *\n **************************************************************************/\n\n let zone: IHostedZone | undefined;\n let certificate: Certificate | undefined;\n\n if (hostedZoneAttributes && baseDomain) {\n zone = HostedZone.fromHostedZoneAttributes(\n this,\n \"zone\",\n hostedZoneAttributes,\n );\n certificate = new Certificate(this, \"wildcard-certificate\", {\n domainName: `*.${baseDomain}`,\n subjectAlternativeNames: [baseDomain],\n validation: CertificateValidation.fromDnsMultiZone({\n [`*.${baseDomain}`]: zone,\n [baseDomain]: zone,\n }),\n });\n }\n\n /******************************************************************************\n *\n * LAMBDA@EDGE FUNCTION\n *\n * This handles rewriting the path from domain name.\n *\n *****************************************************************************/\n\n // Explicit entry required: when omitted, NodejsFunction infers the path from the\n // call site (the built lib/index.js), so it looks for index.viewer-request-handler.js\n // in the package. That file is only emitted if we add it as a separate tsup entry.\n // Use .js when present (built package); fall back to .ts for tests running from source.\n const handlerJs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.js\",\n );\n const handlerTs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.ts\",\n );\n const handlerEntry = fs.existsSync(handlerJs) ? handlerJs : handlerTs;\n\n const handler = new NodejsFunction(this, \"viewer-request-handler\", {\n entry: handlerEntry,\n memorySize: 128,\n runtime: Runtime.NODEJS_24_X,\n logGroup: new LogGroup(this, \"viewer-request-handler-log-group\", {\n retention: RetentionDays.ONE_MONTH,\n }),\n });\n\n /******************************************************************************\n *\n * CLOUDFRONT CONFIG\n *\n * Setup a CloudFront Distribution for the bucket.\n *\n *****************************************************************************/\n\n const cachePolicy = new CachePolicy(this, \"cloudfront-policy\", {\n comment: \"Relatively conservative TTL policy.\",\n maxTtl: Duration.seconds(300),\n minTtl: Duration.seconds(0),\n defaultTtl: Duration.seconds(60),\n headerBehavior: CacheHeaderBehavior.none(),\n queryStringBehavior: CacheQueryStringBehavior.none(),\n cookieBehavior: CacheCookieBehavior.none(),\n enableAcceptEncodingGzip: true,\n enableAcceptEncodingBrotli: true,\n });\n\n const oac = new S3OriginAccessControl(this, \"MyOAC\", {\n signing: Signing.SIGV4_NO_OVERRIDE,\n });\n const origin = S3BucketOrigin.withOriginAccessControl(bucket, {\n originAccessControl: oac,\n originAccessLevels: [AccessLevel.READ],\n });\n\n const distribution = new Distribution(this, \"cloudfront-distribution\", {\n comment: `Distribution for ${props.description ?? id}`,\n\n /**\n * Only if domain was supplied\n */\n ...(certificate && baseDomain\n ? {\n certificate,\n domainNames: [baseDomain, `*.${baseDomain}`],\n }\n : {}),\n\n defaultBehavior: {\n origin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n cachePolicy,\n allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,\n edgeLambdas: [\n {\n functionVersion: handler.currentVersion,\n eventType: LambdaEdgeEventType.VIEWER_REQUEST,\n },\n ],\n },\n defaultRootObject: \"index.html\",\n });\n\n /**\n * We finally have enough information to set the full domain.\n */\n this.fullDomain =\n certificate && baseDomain ? baseDomain : distribution.domainName;\n\n /***************************************************************************\n *\n * DNS ENTRY\n *\n * Link cloudfront to both the root fulldomain and all possible subdomains.\n *\n **************************************************************************/\n\n if (zone) {\n new ARecord(this, \"root-dns-entry\", {\n zone,\n recordName: baseDomain ? baseDomain : \"\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n\n new ARecord(this, \"wc-dns-entry\", {\n zone,\n recordName: baseDomain ? `*.${baseDomain}` : \"*\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n }\n\n /***************************************************************************\n *\n * EXPORTS\n *\n * Used by content uploader later.\n *\n **************************************************************************/\n\n new StringParameter(this, \"dist-domain\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionDomainParamName,\n stringValue: distribution.domainName,\n });\n\n new StringParameter(this, \"dist-id\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionIDParamName,\n stringValue: distribution.distributionId,\n });\n\n new StringParameter(this, \"bucket-arn\", {\n description: `GENERATED DO NOT CHANGE - S3 Bucket ARN for (${props.description ?? id}).`,\n parameterName: bucketArnParamName,\n stringValue: bucket.bucketArn,\n });\n }\n}\n"],"mappings":"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","names":["exports","findGitBranch","exports","exports","exports","import_aws_s3","import_aws_cdk_lib","import_aws_ssm","import_constructs"]}
|
package/lib/index.mjs
CHANGED
|
@@ -1,34 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
-
}) : x)(function(x) {
|
|
10
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
-
});
|
|
13
|
-
var __commonJS = (cb, mod) => function __require2() {
|
|
14
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
15
|
-
};
|
|
16
|
-
var __copyProps = (to, from, except, desc) => {
|
|
17
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
-
for (let key of __getOwnPropNames(from))
|
|
19
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
-
}
|
|
22
|
-
return to;
|
|
23
|
-
};
|
|
24
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
-
mod
|
|
31
|
-
));
|
|
1
|
+
import {
|
|
2
|
+
__commonJS,
|
|
3
|
+
__require,
|
|
4
|
+
__toESM
|
|
5
|
+
} from "./chunk-LZOMFHX3.mjs";
|
|
32
6
|
|
|
33
7
|
// ../utils/lib/aws/aws-types.js
|
|
34
8
|
var require_aws_types = __commonJS({
|
|
@@ -233,6 +207,8 @@ var StaticContent = class extends Construct {
|
|
|
233
207
|
};
|
|
234
208
|
|
|
235
209
|
// src/static-hosting/static-hosting.ts
|
|
210
|
+
import * as fs from "fs";
|
|
211
|
+
import * as path from "path";
|
|
236
212
|
import { Duration } from "aws-cdk-lib";
|
|
237
213
|
import {
|
|
238
214
|
Certificate,
|
|
@@ -301,7 +277,17 @@ var StaticHosting = class extends Construct2 {
|
|
|
301
277
|
})
|
|
302
278
|
});
|
|
303
279
|
}
|
|
280
|
+
const handlerJs = path.join(
|
|
281
|
+
__dirname,
|
|
282
|
+
"static-hosting.viewer-request-handler.js"
|
|
283
|
+
);
|
|
284
|
+
const handlerTs = path.join(
|
|
285
|
+
__dirname,
|
|
286
|
+
"static-hosting.viewer-request-handler.ts"
|
|
287
|
+
);
|
|
288
|
+
const handlerEntry = fs.existsSync(handlerJs) ? handlerJs : handlerTs;
|
|
304
289
|
const handler = new NodejsFunction(this, "viewer-request-handler", {
|
|
290
|
+
entry: handlerEntry,
|
|
305
291
|
memorySize: 128,
|
|
306
292
|
runtime: Runtime.NODEJS_24_X,
|
|
307
293
|
logGroup: new LogGroup(this, "viewer-request-handler-log-group", {
|
package/lib/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../utils/src/aws/aws-types.ts","../../utils/src/git/git-utils.ts","../../utils/src/string/string-utils.ts","../../utils/src/index.ts","../src/s3/private-bucket.ts","../src/static-hosting/static-content.ts","../src/static-hosting/static-hosting.ts"],"sourcesContent":["/**\n * Stage Types\n *\n * What stage of deployment is this? Dev, staging, or prod?\n */\nexport const AWS_STAGE_TYPE = {\n /**\n * Development environment, typically used for testing and development.\n */\n DEV: \"dev\",\n\n /**\n * Staging environment, used for pre-production testing.\n */\n STAGE: \"stage\",\n\n /**\n * Production environment, used for live deployments.\n */\n PROD: \"prod\",\n} as const;\n\n/**\n * Above const as a type.\n */\nexport type AwsStageType = (typeof AWS_STAGE_TYPE)[keyof typeof AWS_STAGE_TYPE];\n\n/**\n * Deployment target role: whether an (account, region) is the primary or\n * secondary deployment target (e.g. primary vs replica region).\n */\nexport const DEPLOYMENT_TARGET_ROLE = {\n /**\n * Account and region that represents the primary region for this service.\n * For example, the base DynamoDB Region for global tables.\n */\n PRIMARY: \"primary\",\n /**\n * Account and region that represents a secondary region for this service.\n * For example, a replica region for a global DynamoDB table.\n */\n SECONDARY: \"secondary\",\n} as const;\n\n/**\n * Type for deployment target role values.\n */\nexport type DeploymentTargetRoleType =\n (typeof DEPLOYMENT_TARGET_ROLE)[keyof typeof DEPLOYMENT_TARGET_ROLE];\n\n/**\n * Environment types (primary/secondary).\n *\n * @deprecated Use {@link DEPLOYMENT_TARGET_ROLE} instead. This constant is maintained for backward compatibility.\n */\nexport const AWS_ENVIRONMENT_TYPE = DEPLOYMENT_TARGET_ROLE;\n\n/**\n * Type for environment type values.\n *\n * @deprecated Use {@link DeploymentTargetRoleType} instead. This type is maintained for backward compatibility.\n */\nexport type AwsEnvironmentType = DeploymentTargetRoleType;\n","import { execSync } from \"node:child_process\";\n\n/**\n * Returns the current full git branch name\n *\n * ie: feature/1234 returns feature/1234\n *\n */\nexport const findGitBranch = (): string => {\n return execSync(\"git rev-parse --abbrev-ref HEAD\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\");\n};\n\nexport const findGitRepoName = (): string => {\n /**\n * When running in github actions this will be populated.\n */\n if (process.env.GITHUB_REPOSITORY) {\n return process.env.GITHUB_REPOSITORY;\n }\n\n /**\n * locally, we need to extract the repo name from the git config.\n */\n const remote = execSync(\"git config --get remote.origin.url\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\")\n .trim();\n\n const match = remote.match(/[:\\/]([^/]+\\/[^/]+?)(?:\\.git)?$/);\n const repoName = match ? match[1] : \"error-repo-name\";\n\n return repoName;\n};\n","import * as crypto from \"node:crypto\";\n\n/**\n *\n * @param inString string to hash\n * @param trimLength trim to this length (defaults to 999 chars)\n * @returns\n */\nexport const hashString = (inString: string, trimLength: number = 999) => {\n return crypto\n .createHash(\"sha256\")\n .update(inString)\n .digest(\"hex\")\n .substring(0, trimLength);\n};\n\n/**\n *\n * @param inputString string to truncate\n * @param maxLength max length of this string\n * @returns trimmed string\n */\nexport const trimStringLength = (inputString: string, maxLength: number) => {\n return inputString.length < maxLength\n ? inputString\n : inputString.substring(0, maxLength);\n};\n","export * from \"./aws/aws-types\";\nexport * from \"./git/git-utils\";\nexport * from \"./string/string-utils\";\n","import { RemovalPolicy } from \"aws-cdk-lib\";\nimport {\n BlockPublicAccess,\n Bucket,\n BucketProps,\n ObjectOwnership,\n} from \"aws-cdk-lib/aws-s3\";\nimport { Construct } from \"constructs\";\n\nexport interface PrivateBucketProps extends BucketProps {}\n\nexport class PrivateBucket extends Bucket {\n constructor(scope: Construct, id: string, props: PrivateBucketProps = {}) {\n const defaultProps = {\n removalPolicy: props.removalPolicy ?? RemovalPolicy.RETAIN,\n autoDeleteObjects: props.removalPolicy === RemovalPolicy.DESTROY,\n };\n\n const requiredProps = {\n publicReadAccess: false,\n blockPublicAccess: BlockPublicAccess.BLOCK_ALL,\n enforceSSL: true,\n objectOwnership: ObjectOwnership.BUCKET_OWNER_ENFORCED,\n };\n\n super(scope, id, { ...defaultProps, ...props, ...requiredProps });\n }\n}\n","// eslint-disable-next-line import/no-extraneous-dependencies\nimport { findGitBranch } from \"@codedrifters/utils\";\nimport { Bucket } from \"aws-cdk-lib/aws-s3\";\nimport { BucketDeployment, Source } from \"aws-cdk-lib/aws-s3-deployment\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { paramCase } from \"change-case\";\nimport { Construct } from \"constructs\";\n\n/*******************************************************************************\n *\n * STATIC CONTENT UPLOADER\n *\n * This construct uploads a directory of content from a local location into S3.\n *\n * To support PR and branch specific builds, each S3 bucket can store content\n * for multiple domains and builds, using the following format:\n *\n * S3-bucket/domain/*\n *\n * A bucket used to store content for stage.openhi.org might have the\n * following directory structure (all in the same bucket).\n *\n * /stage.openhi.org/* -> serves content to stage.openhi.org\n * /feature-7.stage.openhi.org/* -> serves content to feature-7.stage.openhi.org\n * /pr-123.stage.openhi.org/* -> serves content to pr-123.stage.openhi.org\n *\n ******************************************************************************/\n\nexport interface StaticContentProps {\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Absolute path to directory containing content for the website.\n */\n readonly contentSourceDirectory: string;\n\n /**\n * Directory to place content into. Should start with a slash.\n * Example: '/widget'\n */\n readonly contentDestinationDirectory: string;\n\n /**\n * The sub domain prefix (ie: images)\n *\n * @default git branch name\n */\n readonly subDomain?: string;\n\n /**\n * The full domain (ie: staging.codedrifters.com)\n */\n readonly fullDomain: string;\n}\n\nexport class StaticContent extends Construct {\n constructor(scope: Construct, id: string, props: StaticContentProps) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n contentSourceDirectory,\n contentDestinationDirectory,\n subDomain,\n fullDomain,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n subDomain: findGitBranch(),\n ...props,\n };\n\n /***************************************************************************\n *\n * Import and build some values from Param Store during deployment.\n *\n **************************************************************************/\n\n const keyPrefix = [paramCase(subDomain), fullDomain].join(\".\");\n\n const bucketArn = StringParameter.valueForStringParameter(\n this,\n bucketArnParamName,\n );\n const bucket = Bucket.fromBucketArn(this, \"bucket\", bucketArn);\n\n /***************************************************************************\n *\n * Gather the sources we'll be deploying. We need to have an empty source\n * for tests since it will change all the time and bork up the test\n * snapshots if we don't.\n *\n **************************************************************************/\n\n const sources = process.env.JEST_WORKER_ID\n ? []\n : [Source.asset(contentSourceDirectory)];\n\n new BucketDeployment(this, \"deploy\", {\n sources,\n destinationBucket: bucket,\n retainOnDelete: false,\n destinationKeyPrefix: `${keyPrefix}${contentDestinationDirectory}`,\n });\n }\n}\n","import { Duration, StackProps } from \"aws-cdk-lib\";\nimport {\n Certificate,\n CertificateValidation,\n} from \"aws-cdk-lib/aws-certificatemanager\";\nimport {\n AccessLevel,\n AllowedMethods,\n CacheCookieBehavior,\n CacheHeaderBehavior,\n CachePolicy,\n CacheQueryStringBehavior,\n Distribution,\n LambdaEdgeEventType,\n S3OriginAccessControl,\n Signing,\n ViewerProtocolPolicy,\n} from \"aws-cdk-lib/aws-cloudfront\";\nimport { S3BucketOrigin } from \"aws-cdk-lib/aws-cloudfront-origins\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { LogGroup, RetentionDays } from \"aws-cdk-lib/aws-logs\";\nimport {\n ARecord,\n HostedZone,\n HostedZoneAttributes,\n IHostedZone,\n RecordTarget,\n} from \"aws-cdk-lib/aws-route53\";\nimport { CloudFrontTarget } from \"aws-cdk-lib/aws-route53-targets\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\nimport { PrivateBucket, PrivateBucketProps } from \"../s3/private-bucket\";\n\nexport interface StaticDomainProps {\n /**\n * The base domain (ie: codedrifters.com)\n */\n readonly baseDomain: string;\n\n /**\n * Hosted zone ID for the base domain.\n */\n readonly hostedZoneAttributes: HostedZoneAttributes;\n}\n\nexport interface StaticHostingProps extends StackProps {\n /**\n * Short description used in various places for traceability.\n */\n readonly description?: string;\n\n /**\n * Values used to connect a domain name to the cloudfront distribution. If not\n * supplied, cloudfront doesn't use a custom domain.\n */\n readonly staticDomainProps?: StaticDomainProps;\n\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution Domain Name.\n */\n readonly distributionDomainParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution ID.\n */\n readonly distributionIDParamName?: string;\n\n /**\n * Props to pass to the private S3 bucket.\n */\n readonly privateBucketProps?: PrivateBucketProps;\n}\n\nexport class StaticHosting extends Construct {\n /**\n * Full domain name used as basis for hosting.\n */\n public readonly fullDomain: string;\n\n constructor(scope: Construct, id: string, props: StaticHostingProps = {}) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n distributionDomainParamName,\n distributionIDParamName,\n staticDomainProps,\n privateBucketProps,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n distributionDomainParamName: \"/STATIC_WEBSITE/DISTRIBUTION_DOMAIN\",\n distributionIDParamName: \"/STATIC_WEBSITE/DISTRIBUTION_ID\",\n ...props,\n };\n\n const { baseDomain, hostedZoneAttributes } = staticDomainProps ?? {};\n\n /***************************************************************************\n *\n * PRIVATE BUCKET\n *\n * A bucket to store the files within.\n * Save ARN for later deploys.\n *\n **************************************************************************/\n\n const bucket = new PrivateBucket(\n this,\n \"static-hosting-bucket\",\n privateBucketProps,\n );\n\n /***************************************************************************\n *\n * DNS & Wildcard Certificate\n *\n * If a zone Id as passed in, find the hosted zone and create a wildcard\n * certificate for the domain.\n *\n **************************************************************************/\n\n let zone: IHostedZone | undefined;\n let certificate: Certificate | undefined;\n\n if (hostedZoneAttributes && baseDomain) {\n zone = HostedZone.fromHostedZoneAttributes(\n this,\n \"zone\",\n hostedZoneAttributes,\n );\n certificate = new Certificate(this, \"wildcard-certificate\", {\n domainName: `*.${baseDomain}`,\n subjectAlternativeNames: [baseDomain],\n validation: CertificateValidation.fromDnsMultiZone({\n [`*.${baseDomain}`]: zone,\n [baseDomain]: zone,\n }),\n });\n }\n\n /******************************************************************************\n *\n * LAMBDA@EDGE FUNCTION\n *\n * This handles rewriting the path from domain name.\n *\n *****************************************************************************/\n\n const handler = new NodejsFunction(this, \"viewer-request-handler\", {\n memorySize: 128,\n runtime: Runtime.NODEJS_24_X,\n logGroup: new LogGroup(this, \"viewer-request-handler-log-group\", {\n retention: RetentionDays.ONE_MONTH,\n }),\n });\n\n /******************************************************************************\n *\n * CLOUDFRONT CONFIG\n *\n * Setup a CloudFront Distribution for the bucket.\n *\n *****************************************************************************/\n\n const cachePolicy = new CachePolicy(this, \"cloudfront-policy\", {\n comment: \"Relatively conservative TTL policy.\",\n maxTtl: Duration.seconds(300),\n minTtl: Duration.seconds(0),\n defaultTtl: Duration.seconds(60),\n headerBehavior: CacheHeaderBehavior.none(),\n queryStringBehavior: CacheQueryStringBehavior.none(),\n cookieBehavior: CacheCookieBehavior.none(),\n enableAcceptEncodingGzip: true,\n enableAcceptEncodingBrotli: true,\n });\n\n const oac = new S3OriginAccessControl(this, \"MyOAC\", {\n signing: Signing.SIGV4_NO_OVERRIDE,\n });\n const origin = S3BucketOrigin.withOriginAccessControl(bucket, {\n originAccessControl: oac,\n originAccessLevels: [AccessLevel.READ],\n });\n\n const distribution = new Distribution(this, \"cloudfront-distribution\", {\n comment: `Distribution for ${props.description ?? id}`,\n\n /**\n * Only if domain was supplied\n */\n ...(certificate && baseDomain\n ? {\n certificate,\n domainNames: [baseDomain, `*.${baseDomain}`],\n }\n : {}),\n\n defaultBehavior: {\n origin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n cachePolicy,\n allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,\n edgeLambdas: [\n {\n functionVersion: handler.currentVersion,\n eventType: LambdaEdgeEventType.VIEWER_REQUEST,\n },\n ],\n },\n defaultRootObject: \"index.html\",\n });\n\n /**\n * We finally have enough information to set the full domain.\n */\n this.fullDomain =\n certificate && baseDomain ? baseDomain : distribution.domainName;\n\n /***************************************************************************\n *\n * DNS ENTRY\n *\n * Link cloudfront to both the root fulldomain and all possible subdomains.\n *\n **************************************************************************/\n\n if (zone) {\n new ARecord(this, \"root-dns-entry\", {\n zone,\n recordName: baseDomain ? baseDomain : \"\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n\n new ARecord(this, \"wc-dns-entry\", {\n zone,\n recordName: baseDomain ? `*.${baseDomain}` : \"*\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n }\n\n /***************************************************************************\n *\n * EXPORTS\n *\n * Used by content uploader later.\n *\n **************************************************************************/\n\n new StringParameter(this, \"dist-domain\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionDomainParamName,\n stringValue: distribution.domainName,\n });\n\n new StringParameter(this, \"dist-id\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionIDParamName,\n stringValue: distribution.distributionId,\n });\n\n new StringParameter(this, \"bucket-arn\", {\n description: `GENERATED DO NOT CHANGE - S3 Bucket ARN for (${props.description ?? id}).`,\n parameterName: bucketArnParamName,\n stringValue: bucket.bucketArn,\n });\n }\n}\n"],"mappings":"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","names":["findGitBranch","Bucket","StringParameter","Construct","Construct","StringParameter"]}
|
|
1
|
+
{"version":3,"sources":["../../utils/src/aws/aws-types.ts","../../utils/src/git/git-utils.ts","../../utils/src/string/string-utils.ts","../../utils/src/index.ts","../src/s3/private-bucket.ts","../src/static-hosting/static-content.ts","../src/static-hosting/static-hosting.ts"],"sourcesContent":["/**\n * Stage Types\n *\n * What stage of deployment is this? Dev, staging, or prod?\n */\nexport const AWS_STAGE_TYPE = {\n /**\n * Development environment, typically used for testing and development.\n */\n DEV: \"dev\",\n\n /**\n * Staging environment, used for pre-production testing.\n */\n STAGE: \"stage\",\n\n /**\n * Production environment, used for live deployments.\n */\n PROD: \"prod\",\n} as const;\n\n/**\n * Above const as a type.\n */\nexport type AwsStageType = (typeof AWS_STAGE_TYPE)[keyof typeof AWS_STAGE_TYPE];\n\n/**\n * Deployment target role: whether an (account, region) is the primary or\n * secondary deployment target (e.g. primary vs replica region).\n */\nexport const DEPLOYMENT_TARGET_ROLE = {\n /**\n * Account and region that represents the primary region for this service.\n * For example, the base DynamoDB Region for global tables.\n */\n PRIMARY: \"primary\",\n /**\n * Account and region that represents a secondary region for this service.\n * For example, a replica region for a global DynamoDB table.\n */\n SECONDARY: \"secondary\",\n} as const;\n\n/**\n * Type for deployment target role values.\n */\nexport type DeploymentTargetRoleType =\n (typeof DEPLOYMENT_TARGET_ROLE)[keyof typeof DEPLOYMENT_TARGET_ROLE];\n\n/**\n * Environment types (primary/secondary).\n *\n * @deprecated Use {@link DEPLOYMENT_TARGET_ROLE} instead. This constant is maintained for backward compatibility.\n */\nexport const AWS_ENVIRONMENT_TYPE = DEPLOYMENT_TARGET_ROLE;\n\n/**\n * Type for environment type values.\n *\n * @deprecated Use {@link DeploymentTargetRoleType} instead. This type is maintained for backward compatibility.\n */\nexport type AwsEnvironmentType = DeploymentTargetRoleType;\n","import { execSync } from \"node:child_process\";\n\n/**\n * Returns the current full git branch name\n *\n * ie: feature/1234 returns feature/1234\n *\n */\nexport const findGitBranch = (): string => {\n return execSync(\"git rev-parse --abbrev-ref HEAD\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\");\n};\n\nexport const findGitRepoName = (): string => {\n /**\n * When running in github actions this will be populated.\n */\n if (process.env.GITHUB_REPOSITORY) {\n return process.env.GITHUB_REPOSITORY;\n }\n\n /**\n * locally, we need to extract the repo name from the git config.\n */\n const remote = execSync(\"git config --get remote.origin.url\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\")\n .trim();\n\n const match = remote.match(/[:\\/]([^/]+\\/[^/]+?)(?:\\.git)?$/);\n const repoName = match ? match[1] : \"error-repo-name\";\n\n return repoName;\n};\n","import * as crypto from \"node:crypto\";\n\n/**\n *\n * @param inString string to hash\n * @param trimLength trim to this length (defaults to 999 chars)\n * @returns\n */\nexport const hashString = (inString: string, trimLength: number = 999) => {\n return crypto\n .createHash(\"sha256\")\n .update(inString)\n .digest(\"hex\")\n .substring(0, trimLength);\n};\n\n/**\n *\n * @param inputString string to truncate\n * @param maxLength max length of this string\n * @returns trimmed string\n */\nexport const trimStringLength = (inputString: string, maxLength: number) => {\n return inputString.length < maxLength\n ? inputString\n : inputString.substring(0, maxLength);\n};\n","export * from \"./aws/aws-types\";\nexport * from \"./git/git-utils\";\nexport * from \"./string/string-utils\";\n","import { RemovalPolicy } from \"aws-cdk-lib\";\nimport {\n BlockPublicAccess,\n Bucket,\n BucketProps,\n ObjectOwnership,\n} from \"aws-cdk-lib/aws-s3\";\nimport { Construct } from \"constructs\";\n\nexport interface PrivateBucketProps extends BucketProps {}\n\nexport class PrivateBucket extends Bucket {\n constructor(scope: Construct, id: string, props: PrivateBucketProps = {}) {\n const defaultProps = {\n removalPolicy: props.removalPolicy ?? RemovalPolicy.RETAIN,\n autoDeleteObjects: props.removalPolicy === RemovalPolicy.DESTROY,\n };\n\n const requiredProps = {\n publicReadAccess: false,\n blockPublicAccess: BlockPublicAccess.BLOCK_ALL,\n enforceSSL: true,\n objectOwnership: ObjectOwnership.BUCKET_OWNER_ENFORCED,\n };\n\n super(scope, id, { ...defaultProps, ...props, ...requiredProps });\n }\n}\n","// eslint-disable-next-line import/no-extraneous-dependencies\nimport { findGitBranch } from \"@codedrifters/utils\";\nimport { Bucket } from \"aws-cdk-lib/aws-s3\";\nimport { BucketDeployment, Source } from \"aws-cdk-lib/aws-s3-deployment\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { paramCase } from \"change-case\";\nimport { Construct } from \"constructs\";\n\n/*******************************************************************************\n *\n * STATIC CONTENT UPLOADER\n *\n * This construct uploads a directory of content from a local location into S3.\n *\n * To support PR and branch specific builds, each S3 bucket can store content\n * for multiple domains and builds, using the following format:\n *\n * S3-bucket/domain/*\n *\n * A bucket used to store content for stage.openhi.org might have the\n * following directory structure (all in the same bucket).\n *\n * /stage.openhi.org/* -> serves content to stage.openhi.org\n * /feature-7.stage.openhi.org/* -> serves content to feature-7.stage.openhi.org\n * /pr-123.stage.openhi.org/* -> serves content to pr-123.stage.openhi.org\n *\n ******************************************************************************/\n\nexport interface StaticContentProps {\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Absolute path to directory containing content for the website.\n */\n readonly contentSourceDirectory: string;\n\n /**\n * Directory to place content into. Should start with a slash.\n * Example: '/widget'\n */\n readonly contentDestinationDirectory: string;\n\n /**\n * The sub domain prefix (ie: images)\n *\n * @default git branch name\n */\n readonly subDomain?: string;\n\n /**\n * The full domain (ie: staging.codedrifters.com)\n */\n readonly fullDomain: string;\n}\n\nexport class StaticContent extends Construct {\n constructor(scope: Construct, id: string, props: StaticContentProps) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n contentSourceDirectory,\n contentDestinationDirectory,\n subDomain,\n fullDomain,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n subDomain: findGitBranch(),\n ...props,\n };\n\n /***************************************************************************\n *\n * Import and build some values from Param Store during deployment.\n *\n **************************************************************************/\n\n const keyPrefix = [paramCase(subDomain), fullDomain].join(\".\");\n\n const bucketArn = StringParameter.valueForStringParameter(\n this,\n bucketArnParamName,\n );\n const bucket = Bucket.fromBucketArn(this, \"bucket\", bucketArn);\n\n /***************************************************************************\n *\n * Gather the sources we'll be deploying. We need to have an empty source\n * for tests since it will change all the time and bork up the test\n * snapshots if we don't.\n *\n **************************************************************************/\n\n const sources = process.env.JEST_WORKER_ID\n ? []\n : [Source.asset(contentSourceDirectory)];\n\n new BucketDeployment(this, \"deploy\", {\n sources,\n destinationBucket: bucket,\n retainOnDelete: false,\n destinationKeyPrefix: `${keyPrefix}${contentDestinationDirectory}`,\n });\n }\n}\n","import * as fs from \"fs\";\nimport * as path from \"path\";\nimport { Duration, StackProps } from \"aws-cdk-lib\";\nimport {\n Certificate,\n CertificateValidation,\n} from \"aws-cdk-lib/aws-certificatemanager\";\nimport {\n AccessLevel,\n AllowedMethods,\n CacheCookieBehavior,\n CacheHeaderBehavior,\n CachePolicy,\n CacheQueryStringBehavior,\n Distribution,\n LambdaEdgeEventType,\n S3OriginAccessControl,\n Signing,\n ViewerProtocolPolicy,\n} from \"aws-cdk-lib/aws-cloudfront\";\nimport { S3BucketOrigin } from \"aws-cdk-lib/aws-cloudfront-origins\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { LogGroup, RetentionDays } from \"aws-cdk-lib/aws-logs\";\nimport {\n ARecord,\n HostedZone,\n HostedZoneAttributes,\n IHostedZone,\n RecordTarget,\n} from \"aws-cdk-lib/aws-route53\";\nimport { CloudFrontTarget } from \"aws-cdk-lib/aws-route53-targets\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\nimport { PrivateBucket, PrivateBucketProps } from \"../s3/private-bucket\";\n\nexport interface StaticDomainProps {\n /**\n * The base domain (ie: codedrifters.com)\n */\n readonly baseDomain: string;\n\n /**\n * Hosted zone ID for the base domain.\n */\n readonly hostedZoneAttributes: HostedZoneAttributes;\n}\n\nexport interface StaticHostingProps extends StackProps {\n /**\n * Short description used in various places for traceability.\n */\n readonly description?: string;\n\n /**\n * Values used to connect a domain name to the cloudfront distribution. If not\n * supplied, cloudfront doesn't use a custom domain.\n */\n readonly staticDomainProps?: StaticDomainProps;\n\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution Domain Name.\n */\n readonly distributionDomainParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution ID.\n */\n readonly distributionIDParamName?: string;\n\n /**\n * Props to pass to the private S3 bucket.\n */\n readonly privateBucketProps?: PrivateBucketProps;\n}\n\nexport class StaticHosting extends Construct {\n /**\n * Full domain name used as basis for hosting.\n */\n public readonly fullDomain: string;\n\n constructor(scope: Construct, id: string, props: StaticHostingProps = {}) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n distributionDomainParamName,\n distributionIDParamName,\n staticDomainProps,\n privateBucketProps,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n distributionDomainParamName: \"/STATIC_WEBSITE/DISTRIBUTION_DOMAIN\",\n distributionIDParamName: \"/STATIC_WEBSITE/DISTRIBUTION_ID\",\n ...props,\n };\n\n const { baseDomain, hostedZoneAttributes } = staticDomainProps ?? {};\n\n /***************************************************************************\n *\n * PRIVATE BUCKET\n *\n * A bucket to store the files within.\n * Save ARN for later deploys.\n *\n **************************************************************************/\n\n const bucket = new PrivateBucket(\n this,\n \"static-hosting-bucket\",\n privateBucketProps,\n );\n\n /***************************************************************************\n *\n * DNS & Wildcard Certificate\n *\n * If a zone Id as passed in, find the hosted zone and create a wildcard\n * certificate for the domain.\n *\n **************************************************************************/\n\n let zone: IHostedZone | undefined;\n let certificate: Certificate | undefined;\n\n if (hostedZoneAttributes && baseDomain) {\n zone = HostedZone.fromHostedZoneAttributes(\n this,\n \"zone\",\n hostedZoneAttributes,\n );\n certificate = new Certificate(this, \"wildcard-certificate\", {\n domainName: `*.${baseDomain}`,\n subjectAlternativeNames: [baseDomain],\n validation: CertificateValidation.fromDnsMultiZone({\n [`*.${baseDomain}`]: zone,\n [baseDomain]: zone,\n }),\n });\n }\n\n /******************************************************************************\n *\n * LAMBDA@EDGE FUNCTION\n *\n * This handles rewriting the path from domain name.\n *\n *****************************************************************************/\n\n // Explicit entry required: when omitted, NodejsFunction infers the path from the\n // call site (the built lib/index.js), so it looks for index.viewer-request-handler.js\n // in the package. That file is only emitted if we add it as a separate tsup entry.\n // Use .js when present (built package); fall back to .ts for tests running from source.\n const handlerJs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.js\",\n );\n const handlerTs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.ts\",\n );\n const handlerEntry = fs.existsSync(handlerJs) ? handlerJs : handlerTs;\n\n const handler = new NodejsFunction(this, \"viewer-request-handler\", {\n entry: handlerEntry,\n memorySize: 128,\n runtime: Runtime.NODEJS_24_X,\n logGroup: new LogGroup(this, \"viewer-request-handler-log-group\", {\n retention: RetentionDays.ONE_MONTH,\n }),\n });\n\n /******************************************************************************\n *\n * CLOUDFRONT CONFIG\n *\n * Setup a CloudFront Distribution for the bucket.\n *\n *****************************************************************************/\n\n const cachePolicy = new CachePolicy(this, \"cloudfront-policy\", {\n comment: \"Relatively conservative TTL policy.\",\n maxTtl: Duration.seconds(300),\n minTtl: Duration.seconds(0),\n defaultTtl: Duration.seconds(60),\n headerBehavior: CacheHeaderBehavior.none(),\n queryStringBehavior: CacheQueryStringBehavior.none(),\n cookieBehavior: CacheCookieBehavior.none(),\n enableAcceptEncodingGzip: true,\n enableAcceptEncodingBrotli: true,\n });\n\n const oac = new S3OriginAccessControl(this, \"MyOAC\", {\n signing: Signing.SIGV4_NO_OVERRIDE,\n });\n const origin = S3BucketOrigin.withOriginAccessControl(bucket, {\n originAccessControl: oac,\n originAccessLevels: [AccessLevel.READ],\n });\n\n const distribution = new Distribution(this, \"cloudfront-distribution\", {\n comment: `Distribution for ${props.description ?? id}`,\n\n /**\n * Only if domain was supplied\n */\n ...(certificate && baseDomain\n ? {\n certificate,\n domainNames: [baseDomain, `*.${baseDomain}`],\n }\n : {}),\n\n defaultBehavior: {\n origin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n cachePolicy,\n allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,\n edgeLambdas: [\n {\n functionVersion: handler.currentVersion,\n eventType: LambdaEdgeEventType.VIEWER_REQUEST,\n },\n ],\n },\n defaultRootObject: \"index.html\",\n });\n\n /**\n * We finally have enough information to set the full domain.\n */\n this.fullDomain =\n certificate && baseDomain ? baseDomain : distribution.domainName;\n\n /***************************************************************************\n *\n * DNS ENTRY\n *\n * Link cloudfront to both the root fulldomain and all possible subdomains.\n *\n **************************************************************************/\n\n if (zone) {\n new ARecord(this, \"root-dns-entry\", {\n zone,\n recordName: baseDomain ? baseDomain : \"\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n\n new ARecord(this, \"wc-dns-entry\", {\n zone,\n recordName: baseDomain ? `*.${baseDomain}` : \"*\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n }\n\n /***************************************************************************\n *\n * EXPORTS\n *\n * Used by content uploader later.\n *\n **************************************************************************/\n\n new StringParameter(this, \"dist-domain\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionDomainParamName,\n stringValue: distribution.domainName,\n });\n\n new StringParameter(this, \"dist-id\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionIDParamName,\n stringValue: distribution.distributionId,\n });\n\n new StringParameter(this, \"bucket-arn\", {\n description: `GENERATED DO NOT CHANGE - S3 Bucket ARN for (${props.description ?? id}).`,\n parameterName: bucketArnParamName,\n stringValue: bucket.bucketArn,\n });\n }\n}\n"],"mappings":"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","names":["findGitBranch","Bucket","StringParameter","Construct","Construct","StringParameter"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { CloudFrontRequest, CloudFrontRequestEvent } from 'aws-lambda';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* Viewer Request Handler
|
|
6
|
+
*
|
|
7
|
+
* Handles requests at the CloudFront viewer request stage.
|
|
8
|
+
*
|
|
9
|
+
* - Logs the request
|
|
10
|
+
* - Adds default document if needed
|
|
11
|
+
* - Prepends folder with domain
|
|
12
|
+
*
|
|
13
|
+
* @param event CloudFrontRequestEvent
|
|
14
|
+
* @returns CloudFrontRequest
|
|
15
|
+
*/
|
|
16
|
+
declare const handler: (event: CloudFrontRequestEvent) => Promise<CloudFrontRequest>;
|
|
17
|
+
/**
|
|
18
|
+
* This is a helper used in the main handler. It's split into it's own export
|
|
19
|
+
* to make testing easier.
|
|
20
|
+
*
|
|
21
|
+
* Adds default document to request URI if needed. For SPA (single-page app)
|
|
22
|
+
* deployments, path-like URIs (e.g. /dashboard, /patients/123) are rewritten
|
|
23
|
+
* to /index.html so the one root index is served and the client-side router
|
|
24
|
+
* can handle the path. Only URIs whose last segment looks like a static file
|
|
25
|
+
* (contains a dot) are left unchanged.
|
|
26
|
+
*
|
|
27
|
+
* @param request
|
|
28
|
+
* @returns
|
|
29
|
+
*/
|
|
30
|
+
declare const addDefaultDocument: (request: CloudFrontRequest) => CloudFrontRequest;
|
|
31
|
+
/**
|
|
32
|
+
* This is a helper used in the main handler. It's split into it's own export
|
|
33
|
+
* to make testing easier.
|
|
34
|
+
*
|
|
35
|
+
* Adds domain folder to request URI.
|
|
36
|
+
*
|
|
37
|
+
* @param request
|
|
38
|
+
* @returns
|
|
39
|
+
*/
|
|
40
|
+
declare const addDomainFolder: (request: CloudFrontRequest) => CloudFrontRequest;
|
|
41
|
+
|
|
42
|
+
export { addDefaultDocument, addDomainFolder, handler };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { CloudFrontRequest, CloudFrontRequestEvent } from 'aws-lambda';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* Viewer Request Handler
|
|
6
|
+
*
|
|
7
|
+
* Handles requests at the CloudFront viewer request stage.
|
|
8
|
+
*
|
|
9
|
+
* - Logs the request
|
|
10
|
+
* - Adds default document if needed
|
|
11
|
+
* - Prepends folder with domain
|
|
12
|
+
*
|
|
13
|
+
* @param event CloudFrontRequestEvent
|
|
14
|
+
* @returns CloudFrontRequest
|
|
15
|
+
*/
|
|
16
|
+
declare const handler: (event: CloudFrontRequestEvent) => Promise<CloudFrontRequest>;
|
|
17
|
+
/**
|
|
18
|
+
* This is a helper used in the main handler. It's split into it's own export
|
|
19
|
+
* to make testing easier.
|
|
20
|
+
*
|
|
21
|
+
* Adds default document to request URI if needed. For SPA (single-page app)
|
|
22
|
+
* deployments, path-like URIs (e.g. /dashboard, /patients/123) are rewritten
|
|
23
|
+
* to /index.html so the one root index is served and the client-side router
|
|
24
|
+
* can handle the path. Only URIs whose last segment looks like a static file
|
|
25
|
+
* (contains a dot) are left unchanged.
|
|
26
|
+
*
|
|
27
|
+
* @param request
|
|
28
|
+
* @returns
|
|
29
|
+
*/
|
|
30
|
+
declare const addDefaultDocument: (request: CloudFrontRequest) => CloudFrontRequest;
|
|
31
|
+
/**
|
|
32
|
+
* This is a helper used in the main handler. It's split into it's own export
|
|
33
|
+
* to make testing easier.
|
|
34
|
+
*
|
|
35
|
+
* Adds domain folder to request URI.
|
|
36
|
+
*
|
|
37
|
+
* @param request
|
|
38
|
+
* @returns
|
|
39
|
+
*/
|
|
40
|
+
declare const addDomainFolder: (request: CloudFrontRequest) => CloudFrontRequest;
|
|
41
|
+
|
|
42
|
+
export { addDefaultDocument, addDomainFolder, handler };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/static-hosting/static-hosting.viewer-request-handler.ts
|
|
21
|
+
var static_hosting_viewer_request_handler_exports = {};
|
|
22
|
+
__export(static_hosting_viewer_request_handler_exports, {
|
|
23
|
+
addDefaultDocument: () => addDefaultDocument,
|
|
24
|
+
addDomainFolder: () => addDomainFolder,
|
|
25
|
+
handler: () => handler
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(static_hosting_viewer_request_handler_exports);
|
|
28
|
+
var handler = async (event) => {
|
|
29
|
+
if (process.env.JEST_WORKER_ID === void 0) {
|
|
30
|
+
console.log("Request Event: ", JSON.stringify(event, null, 2));
|
|
31
|
+
}
|
|
32
|
+
let request = event.Records[0].cf.request;
|
|
33
|
+
request = addDefaultDocument(request);
|
|
34
|
+
request = addDomainFolder(request);
|
|
35
|
+
if (process.env.JEST_WORKER_ID === void 0) {
|
|
36
|
+
console.log("Resulting Request: ", JSON.stringify(request, null, 2));
|
|
37
|
+
}
|
|
38
|
+
return request;
|
|
39
|
+
};
|
|
40
|
+
var addDefaultDocument = (request) => {
|
|
41
|
+
if (request.uri === "/" || request.uri === "/index.html") {
|
|
42
|
+
request.uri = "/index.html";
|
|
43
|
+
return request;
|
|
44
|
+
}
|
|
45
|
+
const segments = request.uri.split("/").filter(Boolean);
|
|
46
|
+
const lastSegment = segments[segments.length - 1] ?? "";
|
|
47
|
+
const looksLikeStaticFile = lastSegment.includes(".");
|
|
48
|
+
if (looksLikeStaticFile) {
|
|
49
|
+
return request;
|
|
50
|
+
}
|
|
51
|
+
request.uri = "/index.html";
|
|
52
|
+
return request;
|
|
53
|
+
};
|
|
54
|
+
var addDomainFolder = (request) => {
|
|
55
|
+
const hostHeader = request.headers.host[0].value;
|
|
56
|
+
request.uri = `/${hostHeader}${request.uri}`;
|
|
57
|
+
return request;
|
|
58
|
+
};
|
|
59
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
60
|
+
0 && (module.exports = {
|
|
61
|
+
addDefaultDocument,
|
|
62
|
+
addDomainFolder,
|
|
63
|
+
handler
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=static-hosting.viewer-request-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/static-hosting/static-hosting.viewer-request-handler.ts"],"sourcesContent":["import { CloudFrontRequest, CloudFrontRequestEvent } from \"aws-lambda\";\n\n/**\n *\n * Viewer Request Handler\n *\n * Handles requests at the CloudFront viewer request stage.\n *\n * - Logs the request\n * - Adds default document if needed\n * - Prepends folder with domain\n *\n * @param event CloudFrontRequestEvent\n * @returns CloudFrontRequest\n */\nexport const handler = async (\n event: CloudFrontRequestEvent,\n): Promise<CloudFrontRequest> => {\n // log request\n if (process.env.JEST_WORKER_ID === undefined) {\n console.log(\"Request Event: \", JSON.stringify(event, null, 2));\n }\n\n let request = event.Records[0].cf.request;\n\n // add index if needed\n request = addDefaultDocument(request);\n\n // prepend folder with domain\n request = addDomainFolder(request);\n\n if (process.env.JEST_WORKER_ID === undefined) {\n console.log(\"Resulting Request: \", JSON.stringify(request, null, 2));\n }\n\n // return body\n return request;\n};\n\n/**\n * This is a helper used in the main handler. It's split into it's own export\n * to make testing easier.\n *\n * Adds default document to request URI if needed. For SPA (single-page app)\n * deployments, path-like URIs (e.g. /dashboard, /patients/123) are rewritten\n * to /index.html so the one root index is served and the client-side router\n * can handle the path. Only URIs whose last segment looks like a static file\n * (contains a dot) are left unchanged.\n *\n * @param request\n * @returns\n */\nexport const addDefaultDocument = (request: CloudFrontRequest) => {\n if (request.uri === \"/\" || request.uri === \"/index.html\") {\n request.uri = \"/index.html\";\n return request;\n }\n\n const segments = request.uri.split(\"/\").filter(Boolean);\n const lastSegment = segments[segments.length - 1] ?? \"\";\n const looksLikeStaticFile = lastSegment.includes(\".\");\n\n if (looksLikeStaticFile) {\n return request;\n }\n\n request.uri = \"/index.html\";\n return request;\n};\n\n/**\n * This is a helper used in the main handler. It's split into it's own export\n * to make testing easier.\n *\n * Adds domain folder to request URI.\n *\n * @param request\n * @returns\n */\nexport const addDomainFolder = (request: CloudFrontRequest) => {\n const hostHeader = request.headers.host[0].value;\n request.uri = `/${hostHeader}${request.uri}`;\n return request;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeO,IAAM,UAAU,OACrB,UAC+B;AAE/B,MAAI,QAAQ,IAAI,mBAAmB,QAAW;AAC5C,YAAQ,IAAI,mBAAmB,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA,EAC/D;AAEA,MAAI,UAAU,MAAM,QAAQ,CAAC,EAAE,GAAG;AAGlC,YAAU,mBAAmB,OAAO;AAGpC,YAAU,gBAAgB,OAAO;AAEjC,MAAI,QAAQ,IAAI,mBAAmB,QAAW;AAC5C,YAAQ,IAAI,uBAAuB,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,EACrE;AAGA,SAAO;AACT;AAeO,IAAM,qBAAqB,CAAC,YAA+B;AAChE,MAAI,QAAQ,QAAQ,OAAO,QAAQ,QAAQ,eAAe;AACxD,YAAQ,MAAM;AACd,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,QAAQ,IAAI,MAAM,GAAG,EAAE,OAAO,OAAO;AACtD,QAAM,cAAc,SAAS,SAAS,SAAS,CAAC,KAAK;AACrD,QAAM,sBAAsB,YAAY,SAAS,GAAG;AAEpD,MAAI,qBAAqB;AACvB,WAAO;AAAA,EACT;AAEA,UAAQ,MAAM;AACd,SAAO;AACT;AAWO,IAAM,kBAAkB,CAAC,YAA+B;AAC7D,QAAM,aAAa,QAAQ,QAAQ,KAAK,CAAC,EAAE;AAC3C,UAAQ,MAAM,IAAI,UAAU,GAAG,QAAQ,GAAG;AAC1C,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import "./chunk-LZOMFHX3.mjs";
|
|
2
|
+
|
|
3
|
+
// src/static-hosting/static-hosting.viewer-request-handler.ts
|
|
4
|
+
var handler = async (event) => {
|
|
5
|
+
if (process.env.JEST_WORKER_ID === void 0) {
|
|
6
|
+
console.log("Request Event: ", JSON.stringify(event, null, 2));
|
|
7
|
+
}
|
|
8
|
+
let request = event.Records[0].cf.request;
|
|
9
|
+
request = addDefaultDocument(request);
|
|
10
|
+
request = addDomainFolder(request);
|
|
11
|
+
if (process.env.JEST_WORKER_ID === void 0) {
|
|
12
|
+
console.log("Resulting Request: ", JSON.stringify(request, null, 2));
|
|
13
|
+
}
|
|
14
|
+
return request;
|
|
15
|
+
};
|
|
16
|
+
var addDefaultDocument = (request) => {
|
|
17
|
+
if (request.uri === "/" || request.uri === "/index.html") {
|
|
18
|
+
request.uri = "/index.html";
|
|
19
|
+
return request;
|
|
20
|
+
}
|
|
21
|
+
const segments = request.uri.split("/").filter(Boolean);
|
|
22
|
+
const lastSegment = segments[segments.length - 1] ?? "";
|
|
23
|
+
const looksLikeStaticFile = lastSegment.includes(".");
|
|
24
|
+
if (looksLikeStaticFile) {
|
|
25
|
+
return request;
|
|
26
|
+
}
|
|
27
|
+
request.uri = "/index.html";
|
|
28
|
+
return request;
|
|
29
|
+
};
|
|
30
|
+
var addDomainFolder = (request) => {
|
|
31
|
+
const hostHeader = request.headers.host[0].value;
|
|
32
|
+
request.uri = `/${hostHeader}${request.uri}`;
|
|
33
|
+
return request;
|
|
34
|
+
};
|
|
35
|
+
export {
|
|
36
|
+
addDefaultDocument,
|
|
37
|
+
addDomainFolder,
|
|
38
|
+
handler
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=static-hosting.viewer-request-handler.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/static-hosting/static-hosting.viewer-request-handler.ts"],"sourcesContent":["import { CloudFrontRequest, CloudFrontRequestEvent } from \"aws-lambda\";\n\n/**\n *\n * Viewer Request Handler\n *\n * Handles requests at the CloudFront viewer request stage.\n *\n * - Logs the request\n * - Adds default document if needed\n * - Prepends folder with domain\n *\n * @param event CloudFrontRequestEvent\n * @returns CloudFrontRequest\n */\nexport const handler = async (\n event: CloudFrontRequestEvent,\n): Promise<CloudFrontRequest> => {\n // log request\n if (process.env.JEST_WORKER_ID === undefined) {\n console.log(\"Request Event: \", JSON.stringify(event, null, 2));\n }\n\n let request = event.Records[0].cf.request;\n\n // add index if needed\n request = addDefaultDocument(request);\n\n // prepend folder with domain\n request = addDomainFolder(request);\n\n if (process.env.JEST_WORKER_ID === undefined) {\n console.log(\"Resulting Request: \", JSON.stringify(request, null, 2));\n }\n\n // return body\n return request;\n};\n\n/**\n * This is a helper used in the main handler. It's split into it's own export\n * to make testing easier.\n *\n * Adds default document to request URI if needed. For SPA (single-page app)\n * deployments, path-like URIs (e.g. /dashboard, /patients/123) are rewritten\n * to /index.html so the one root index is served and the client-side router\n * can handle the path. Only URIs whose last segment looks like a static file\n * (contains a dot) are left unchanged.\n *\n * @param request\n * @returns\n */\nexport const addDefaultDocument = (request: CloudFrontRequest) => {\n if (request.uri === \"/\" || request.uri === \"/index.html\") {\n request.uri = \"/index.html\";\n return request;\n }\n\n const segments = request.uri.split(\"/\").filter(Boolean);\n const lastSegment = segments[segments.length - 1] ?? \"\";\n const looksLikeStaticFile = lastSegment.includes(\".\");\n\n if (looksLikeStaticFile) {\n return request;\n }\n\n request.uri = \"/index.html\";\n return request;\n};\n\n/**\n * This is a helper used in the main handler. It's split into it's own export\n * to make testing easier.\n *\n * Adds domain folder to request URI.\n *\n * @param request\n * @returns\n */\nexport const addDomainFolder = (request: CloudFrontRequest) => {\n const hostHeader = request.headers.host[0].value;\n request.uri = `/${hostHeader}${request.uri}`;\n return request;\n};\n"],"mappings":";;;AAeO,IAAM,UAAU,OACrB,UAC+B;AAE/B,MAAI,QAAQ,IAAI,mBAAmB,QAAW;AAC5C,YAAQ,IAAI,mBAAmB,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA,EAC/D;AAEA,MAAI,UAAU,MAAM,QAAQ,CAAC,EAAE,GAAG;AAGlC,YAAU,mBAAmB,OAAO;AAGpC,YAAU,gBAAgB,OAAO;AAEjC,MAAI,QAAQ,IAAI,mBAAmB,QAAW;AAC5C,YAAQ,IAAI,uBAAuB,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,EACrE;AAGA,SAAO;AACT;AAeO,IAAM,qBAAqB,CAAC,YAA+B;AAChE,MAAI,QAAQ,QAAQ,OAAO,QAAQ,QAAQ,eAAe;AACxD,YAAQ,MAAM;AACd,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,QAAQ,IAAI,MAAM,GAAG,EAAE,OAAO,OAAO;AACtD,QAAM,cAAc,SAAS,SAAS,SAAS,CAAC,KAAK;AACrD,QAAM,sBAAsB,YAAY,SAAS,GAAG;AAEpD,MAAI,qBAAqB;AACvB,WAAO;AAAA,EACT;AAEA,UAAQ,MAAM;AACd,SAAO;AACT;AAWO,IAAM,kBAAkB,CAAC,YAA+B;AAC7D,QAAM,aAAa,QAAQ,QAAQ,KAAK,CAAC,EAAE;AAC3C,UAAQ,MAAM,IAAI,UAAU,GAAG,QAAQ,GAAG;AAC1C,SAAO;AACT;","names":[]}
|
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"@swc/core": "^1.15.13",
|
|
14
14
|
"@swc/jest": "^0.2.39",
|
|
15
15
|
"@types/jest": "^30.0.0",
|
|
16
|
-
"@types/node": "^24.10.
|
|
16
|
+
"@types/node": "^24.10.14",
|
|
17
17
|
"@typescript-eslint/eslint-plugin": "^8",
|
|
18
18
|
"@typescript-eslint/parser": "^8",
|
|
19
19
|
"aws-cdk-lib": "2.240.0",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"constructs": "10.5.1"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@aws-sdk/client-dynamodb": "^3.
|
|
41
|
+
"@aws-sdk/client-dynamodb": "^3.998.0",
|
|
42
42
|
"@types/aws-lambda": "^8.10.160",
|
|
43
43
|
"change-case": "^4.0",
|
|
44
44
|
"esbuild": "^0.27.3",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"main": "lib/index.js",
|
|
48
48
|
"license": "UNLICENSED",
|
|
49
|
-
"version": "0.0.
|
|
49
|
+
"version": "0.0.19",
|
|
50
50
|
"types": "lib/index.d.ts",
|
|
51
51
|
"//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\".",
|
|
52
52
|
"scripts": {
|