cdk-nextjs 0.3.11 → 0.4.1
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/.jsii +1248 -748
- package/API.md +1033 -531
- package/README.md +1 -2
- package/assets/lambdas/assets-deployment/assets-deployment.lambda/assets-deployment.Dockerfile +12 -8
- package/assets/lambdas/assets-deployment/assets-deployment.lambda/index.js +67 -56
- package/assets/lambdas/post-deploy/post-deploy.lambda/index.js +246 -0
- package/docs/breaking-changes.md +9 -0
- package/docs/notes.md +170 -20
- package/lib/constants.d.ts +32 -0
- package/lib/constants.js +38 -0
- package/lib/generated-structs/OptionalCustomResourceProps.d.ts +104 -0
- package/lib/generated-structs/OptionalCustomResourceProps.js +3 -0
- package/lib/generated-structs/OptionalDockerImageAssetProps.d.ts +21 -0
- package/lib/generated-structs/OptionalDockerImageAssetProps.js +1 -1
- package/lib/generated-structs/OptionalNextjsAssetsDeploymentProps.d.ts +4 -3
- package/lib/generated-structs/OptionalNextjsAssetsDeploymentProps.js +1 -1
- package/lib/generated-structs/OptionalNextjsBuildProps.d.ts +1 -1
- package/lib/generated-structs/OptionalNextjsBuildProps.js +1 -1
- package/lib/generated-structs/OptionalNextjsContainersProps.d.ts +3 -3
- package/lib/generated-structs/OptionalNextjsContainersProps.js +1 -1
- package/lib/generated-structs/OptionalNextjsPostDeployProps.d.ts +42 -0
- package/lib/generated-structs/OptionalNextjsPostDeployProps.js +3 -0
- package/lib/generated-structs/OptionalPostDeployCustomResourceProperties.d.ts +41 -0
- package/lib/generated-structs/OptionalPostDeployCustomResourceProperties.js +4 -0
- package/lib/index.d.ts +6 -5
- package/lib/index.js +6 -8
- package/lib/lambdas/assets-deployment/assets-deployment.lambda.js +5 -23
- package/lib/lambdas/assets-deployment/fs-to-fs.js +18 -3
- package/lib/lambdas/assets-deployment/fs-to-s3.d.ts +8 -2
- package/lib/lambdas/assets-deployment/fs-to-s3.js +14 -8
- package/lib/lambdas/post-deploy/create-invalidation.d.ts +2 -0
- package/lib/lambdas/post-deploy/create-invalidation.js +19 -0
- package/lib/lambdas/post-deploy/post-deploy-function.d.ts +13 -0
- package/lib/lambdas/post-deploy/post-deploy-function.js +22 -0
- package/lib/lambdas/post-deploy/post-deploy.lambda.d.ts +9 -0
- package/lib/lambdas/post-deploy/post-deploy.lambda.js +56 -0
- package/lib/lambdas/post-deploy/prune-fs.d.ts +10 -0
- package/lib/lambdas/post-deploy/prune-fs.js +33 -0
- package/lib/lambdas/post-deploy/prune-s3.d.ts +15 -0
- package/lib/lambdas/post-deploy/prune-s3.js +101 -0
- package/lib/lambdas/utils.js +35 -0
- package/lib/nextjs-assets-deployment.d.ts +15 -51
- package/lib/nextjs-assets-deployment.js +27 -25
- package/lib/nextjs-build/builder.Dockerfile +12 -8
- package/lib/nextjs-build/cdk-nextjs-cache-handler.cjs +63 -0
- package/lib/nextjs-build/cdk-nextjs-cache-handler.d.ts +21 -0
- package/lib/nextjs-build/cdk-nextjs-cache-handler.js +49 -0
- package/lib/nextjs-build/global-containers.Dockerfile +29 -19
- package/lib/nextjs-build/global-functions.Dockerfile +30 -24
- package/lib/nextjs-build/nextjs-build.d.ts +51 -26
- package/lib/nextjs-build/nextjs-build.js +108 -88
- package/lib/nextjs-build/regional-containers.Dockerfile +30 -20
- package/lib/nextjs-compute/nextjs-compute-base-props.d.ts +0 -1
- package/lib/nextjs-compute/nextjs-compute-base-props.js +1 -1
- package/lib/nextjs-compute/nextjs-containers.d.ts +2 -1
- package/lib/nextjs-compute/nextjs-containers.js +7 -5
- package/lib/nextjs-compute/nextjs-functions.d.ts +1 -0
- package/lib/nextjs-compute/nextjs-functions.js +8 -11
- package/lib/nextjs-distribution.d.ts +1 -1
- package/lib/nextjs-distribution.js +4 -4
- package/lib/nextjs-file-system.js +1 -1
- package/lib/nextjs-post-deploy.d.ts +101 -0
- package/lib/nextjs-post-deploy.js +78 -0
- package/lib/nextjs-static-assets.js +1 -1
- package/lib/nextjs-vpc.d.ts +1 -1
- package/lib/nextjs-vpc.js +4 -4
- package/lib/root-constructs/nextjs-base-overrides.d.ts +4 -0
- package/lib/root-constructs/nextjs-base-overrides.js +1 -1
- package/lib/root-constructs/nextjs-base-props.d.ts +15 -7
- package/lib/root-constructs/nextjs-base-props.js +1 -1
- package/lib/root-constructs/nextjs-global-containers.d.ts +8 -8
- package/lib/root-constructs/nextjs-global-containers.js +25 -15
- package/lib/root-constructs/nextjs-global-functions.d.ts +8 -11
- package/lib/root-constructs/nextjs-global-functions.js +25 -25
- package/lib/root-constructs/nextjs-regional-containers.d.ts +8 -0
- package/lib/root-constructs/nextjs-regional-containers.js +26 -9
- package/lib/utils/get-architecture.d.ts +2 -0
- package/lib/utils/get-architecture.js +8 -0
- package/lib/utils/handle-deprecated-properties.d.ts +7 -0
- package/lib/utils/handle-deprecated-properties.js +14 -0
- package/package.json +17 -19
- package/assets/lambdas/revalidate/revalidate.lambda/index.js +0 -67
- package/lib/common.d.ts +0 -25
- package/lib/common.js +0 -30
- package/lib/generated-structs/OptionalNextjsInvalidationProps.d.ts +0 -11
- package/lib/generated-structs/OptionalNextjsInvalidationProps.js +0 -3
- package/lib/lambdas/assets-deployment/prune-s3.d.ts +0 -15
- package/lib/lambdas/assets-deployment/prune-s3.js +0 -42
- package/lib/lambdas/assets-deployment/s3.d.ts +0 -2
- package/lib/lambdas/assets-deployment/s3.js +0 -7
- package/lib/lambdas/assets-deployment/utils.js +0 -35
- package/lib/lambdas/revalidate/revalidate-function.d.ts +0 -13
- package/lib/lambdas/revalidate/revalidate-function.js +0 -22
- package/lib/lambdas/revalidate/revalidate.lambda.d.ts +0 -2
- package/lib/lambdas/revalidate/revalidate.lambda.js +0 -53
- package/lib/nextjs-build/add-cache-handler.d.ts +0 -1
- package/lib/nextjs-build/add-cache-handler.js +0 -23
- package/lib/nextjs-build/add-cache-handler.mjs +0 -18
- package/lib/nextjs-build/cache-handler.cjs +0 -21878
- package/lib/nextjs-build/cache-handler.d.ts +0 -6
- package/lib/nextjs-build/cache-handler.js +0 -26
- package/lib/nextjs-invalidation.d.ts +0 -19
- package/lib/nextjs-invalidation.js +0 -52
- package/lib/nextjs-revalidation.d.ts +0 -30
- package/lib/nextjs-revalidation.js +0 -65
- /package/lib/lambdas/{assets-deployment/utils.d.ts → utils.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -24,14 +24,13 @@ Deploy [Next.js](https://nextjs.org/) apps on [AWS](https://aws.amazon.com/) wit
|
|
|
24
24
|
|
|
25
25
|
## Features
|
|
26
26
|
|
|
27
|
-
- Supports all features of Next.js App and Pages Router for [Node.js Runtime](https://nextjs.org/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes#nodejs-runtime)
|
|
27
|
+
- Supports all features of Next.js App and Pages Router for [Node.js Runtime](https://nextjs.org/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes#nodejs-runtime). One exception is `NextjsGlobalFunctions` does not currently support [ISR](https://nextjs.org/docs/app/guides/incremental-static-regeneration) - waiting for [Official Next.js Deployment Adapters API](https://github.com/vercel/next.js/discussions/77740).
|
|
28
28
|
- Choose your AWS architecture for Next.js with the supported constructs: `NextjsGlobalFunctions`, `NextjsGlobalContainers`, `NextjsRegionalContainers`.
|
|
29
29
|
- Global Content Delivery Network (CDN) built with [Amazon CloudFront](https://aws.amazon.com/cloudfront/) to deliver content with low latency and high transfer speeds.
|
|
30
30
|
- Serverless functions powered by [AWS Lambda](https://aws.amazon.com/lambda/) or serverless containers powered by [AWS Fargate](https://aws.amazon.com/fargate/).
|
|
31
31
|
- Static assets (JS, CSS, public folder) are stored and served from [Amazon Simple Storage Service (S3)](https://aws.amazon.com/s3/) for global constructs to decrease latency and reduce compute costs.
|
|
32
32
|
- [Optimized images](https://nextjs.org/docs/pages/building-your-application/optimizing/images), [data cache](https://nextjs.org/docs/app/building-your-application/caching#data-cache), and [full route cache](https://nextjs.org/docs/app/building-your-application/caching#full-route-cache) are shared across compute with [Amazon Elastic File System (EFS)](https://aws.amazon.com/efs/).
|
|
33
33
|
- Customize every construct via `overrides`.
|
|
34
|
-
- WIP: When using AWS Lambda for compute, async revalidation is supported with [Amazon Simple Queue Service (SQS)](https://aws.amazon.com/sqs/).
|
|
35
34
|
- AWS security and operational best practices are utilized, guided by [cdk-nag](https://github.com/cdklabs/cdk-nag).
|
|
36
35
|
- First class support for [monorepos](https://monorepo.tools/).
|
|
37
36
|
- [AWS GovCloud (US)](https://aws.amazon.com/govcloud-us) compatible (with `NextjsRegionalContainers`).
|
package/assets/lambdas/assets-deployment/assets-deployment.lambda/assets-deployment.Dockerfile
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
#checkov:skip=CKV_DOCKER_2: healthcheck not needed for Custom Resource
|
|
2
2
|
#checkov:skip=CKV_DOCKER_3: lambda container creates user by default
|
|
3
3
|
#checkov:skip=CKV_DOCKER_7: latest tag is ok to use for local builder container
|
|
4
|
-
ARG
|
|
5
|
-
FROM $
|
|
6
|
-
FROM public.ecr.aws/lambda/nodejs:22
|
|
4
|
+
ARG BUILDER_IMAGE_ALIAS=cdk-nextjs/builder:latest
|
|
5
|
+
FROM $BUILDER_IMAGE_ALIAS AS builder
|
|
6
|
+
FROM public.ecr.aws/lambda/nodejs:22 AS runner
|
|
7
|
+
# do not set WORKDIR b/c it's configured by public.ecr.aws/lambda/nodejs:22 to be /var/task for lambda to run
|
|
7
8
|
|
|
8
|
-
ARG
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
#
|
|
12
|
-
|
|
9
|
+
ARG RELATIVE_PATH_TO_PACKAGE
|
|
10
|
+
ARG PUBLIC_PATH
|
|
11
|
+
COPY --from=builder --chown=nextjs:nodejs /app/$RELATIVE_PATH_TO_PACKAGE/$PUBLIC_PATH /app/$PUBLIC_PATH
|
|
12
|
+
# 1. see src/nextjs-assets-deployment.ts's #createCustomResource method to see what
|
|
13
|
+
# we copy from this resulting image to S3 and EFS
|
|
14
|
+
# 2. we don't copy from /app/$RELATIVE_PATH_TO_PACKAGE/.next/standalone like in
|
|
15
|
+
# compute images because we need .next/cache folder which doesn't exist within standalone
|
|
16
|
+
COPY --from=builder --chown=nextjs:nodejs /app/$RELATIVE_PATH_TO_PACKAGE/.next /app/.next
|
|
13
17
|
# create fetch-cache if not created by next build
|
|
14
18
|
RUN mkdir -p /app/.next/cache/fetch-cache
|
|
15
19
|
# copy bundled custom resource handler
|
|
@@ -8568,7 +8568,7 @@ var require_mime_types = __commonJS({
|
|
|
8568
8568
|
"node_modules/.pnpm/mime-types@2.1.35/node_modules/mime-types/index.js"(exports2) {
|
|
8569
8569
|
"use strict";
|
|
8570
8570
|
var db = require_mime_db();
|
|
8571
|
-
var
|
|
8571
|
+
var extname2 = require("path").extname;
|
|
8572
8572
|
var EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/;
|
|
8573
8573
|
var TEXT_TYPE_REGEXP = /^text\//i;
|
|
8574
8574
|
exports2.charset = charset;
|
|
@@ -8622,7 +8622,7 @@ var require_mime_types = __commonJS({
|
|
|
8622
8622
|
if (!path || typeof path !== "string") {
|
|
8623
8623
|
return false;
|
|
8624
8624
|
}
|
|
8625
|
-
var extension2 =
|
|
8625
|
+
var extension2 = extname2("x." + path).toLowerCase().substr(1);
|
|
8626
8626
|
if (!extension2) {
|
|
8627
8627
|
return false;
|
|
8628
8628
|
}
|
|
@@ -8663,25 +8663,60 @@ var import_node_fs4 = require("node:fs");
|
|
|
8663
8663
|
|
|
8664
8664
|
// src/lambdas/assets-deployment/fs-to-fs.ts
|
|
8665
8665
|
var import_node_fs = require("node:fs");
|
|
8666
|
+
var import_node_path = require("node:path");
|
|
8667
|
+
|
|
8668
|
+
// src/lambdas/utils.ts
|
|
8669
|
+
function cfnResponse(props) {
|
|
8670
|
+
const body = JSON.stringify({
|
|
8671
|
+
Status: props.responseStatus,
|
|
8672
|
+
Reason: "See the details in CloudWatch Log Stream: " + props.context.logStreamName,
|
|
8673
|
+
PhysicalResourceId: props.physicalResourceId || props.context.logStreamName,
|
|
8674
|
+
StackId: props.event.StackId,
|
|
8675
|
+
RequestId: props.event.RequestId,
|
|
8676
|
+
LogicalResourceId: props.event.LogicalResourceId,
|
|
8677
|
+
Data: props.responseData
|
|
8678
|
+
});
|
|
8679
|
+
return fetch(props.event.ResponseURL, {
|
|
8680
|
+
method: "PUT",
|
|
8681
|
+
body,
|
|
8682
|
+
headers: { "content-type": "", "content-length": body.length.toString() }
|
|
8683
|
+
});
|
|
8684
|
+
}
|
|
8685
|
+
function debug(value) {
|
|
8686
|
+
if (process.env.DEBUG) console.log(JSON.stringify(value, null, 2));
|
|
8687
|
+
}
|
|
8688
|
+
|
|
8689
|
+
// src/lambdas/assets-deployment/fs-to-fs.ts
|
|
8666
8690
|
function fsToFs(props) {
|
|
8667
|
-
const { sourcePath, destinationPath } = props;
|
|
8668
|
-
(
|
|
8691
|
+
const { sourcePath, destinationPath, includeExtensions } = props;
|
|
8692
|
+
debug(`Copying ${sourcePath} to ${destinationPath} recursively`);
|
|
8693
|
+
(0, import_node_fs.cpSync)(sourcePath, destinationPath, {
|
|
8694
|
+
recursive: true,
|
|
8695
|
+
filter: (_src, dest) => {
|
|
8696
|
+
const isDirectory = (0, import_node_path.extname)(dest) === "";
|
|
8697
|
+
if (isDirectory) {
|
|
8698
|
+
return true;
|
|
8699
|
+
}
|
|
8700
|
+
return includeExtensions ? includeExtensions.includes((0, import_node_path.extname)(dest)) : true;
|
|
8701
|
+
}
|
|
8702
|
+
});
|
|
8669
8703
|
}
|
|
8670
8704
|
|
|
8671
8705
|
// src/lambdas/assets-deployment/fs-to-s3.ts
|
|
8672
8706
|
var import_node_fs3 = require("node:fs");
|
|
8673
|
-
var
|
|
8707
|
+
var import_node_path3 = require("node:path");
|
|
8708
|
+
var import_client_s3 = require("@aws-sdk/client-s3");
|
|
8674
8709
|
var import_lib_storage = require("@aws-sdk/lib-storage");
|
|
8675
8710
|
var mime = __toESM(require_mime_types());
|
|
8676
8711
|
|
|
8677
8712
|
// src/lambdas/assets-deployment/common.ts
|
|
8678
8713
|
var import_node_fs2 = require("node:fs");
|
|
8679
|
-
var
|
|
8714
|
+
var import_node_path2 = require("node:path");
|
|
8680
8715
|
function listFilePaths(dirPath) {
|
|
8681
8716
|
const filePaths = [];
|
|
8682
8717
|
const directory = (0, import_node_fs2.readdirSync)(dirPath, { withFileTypes: true });
|
|
8683
8718
|
for (const d of directory) {
|
|
8684
|
-
const filePath = (0,
|
|
8719
|
+
const filePath = (0, import_node_path2.resolve)(dirPath, d.name);
|
|
8685
8720
|
if (d.isDirectory()) {
|
|
8686
8721
|
filePaths.push(...listFilePaths(filePath));
|
|
8687
8722
|
} else {
|
|
@@ -8696,34 +8731,23 @@ async function* chunkArray(array, chunkSize) {
|
|
|
8696
8731
|
}
|
|
8697
8732
|
}
|
|
8698
8733
|
|
|
8699
|
-
// src/
|
|
8700
|
-
var
|
|
8701
|
-
var
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
const body = JSON.stringify({
|
|
8706
|
-
Status: props.responseStatus,
|
|
8707
|
-
Reason: "See the details in CloudWatch Log Stream: " + props.context.logStreamName,
|
|
8708
|
-
PhysicalResourceId: props.physicalResourceId || props.context.logStreamName,
|
|
8709
|
-
StackId: props.event.StackId,
|
|
8710
|
-
RequestId: props.event.RequestId,
|
|
8711
|
-
LogicalResourceId: props.event.LogicalResourceId,
|
|
8712
|
-
Data: props.responseData
|
|
8713
|
-
});
|
|
8714
|
-
return fetch(props.event.ResponseURL, {
|
|
8715
|
-
method: "PUT",
|
|
8716
|
-
body,
|
|
8717
|
-
headers: { "content-type": "", "content-length": body.length.toString() }
|
|
8718
|
-
});
|
|
8719
|
-
}
|
|
8720
|
-
function debug(value) {
|
|
8721
|
-
if (process.env.DEBUG) console.log(JSON.stringify(value, null, 2));
|
|
8722
|
-
}
|
|
8734
|
+
// src/constants.ts
|
|
8735
|
+
var CACHE_PATH = ".next/cache";
|
|
8736
|
+
var DATA_CACHE_PATH = `${CACHE_PATH}/fetch-cache`;
|
|
8737
|
+
var IMAGE_CACHE_PATH = `${CACHE_PATH}/images`;
|
|
8738
|
+
var SERVER_DIST_PATH = ".next/server";
|
|
8739
|
+
var FULL_ROUTE_CACHE_PATH = `${SERVER_DIST_PATH}/app`;
|
|
8723
8740
|
|
|
8724
8741
|
// src/lambdas/assets-deployment/fs-to-s3.ts
|
|
8725
|
-
|
|
8726
|
-
|
|
8742
|
+
var s3 = new import_client_s3.S3Client({});
|
|
8743
|
+
async function fsToS3(props) {
|
|
8744
|
+
const {
|
|
8745
|
+
buildId,
|
|
8746
|
+
destinationBucketName,
|
|
8747
|
+
destinationKeyPrefix,
|
|
8748
|
+
nextjsType,
|
|
8749
|
+
sourcePath
|
|
8750
|
+
} = props;
|
|
8727
8751
|
const sourceFilePaths = listFilePaths(sourcePath);
|
|
8728
8752
|
for await (const filePathChunk of chunkArray(sourceFilePaths, 100)) {
|
|
8729
8753
|
const putObjectInputs = filePathChunk.map(
|
|
@@ -8738,7 +8762,7 @@ async function fsToS3(props, nextjsType) {
|
|
|
8738
8762
|
if (path.includes(".next/static/chunks/main-app-") && nextjsType === "GLOBAL_FUNCTIONS" /* GLOBAL_FUNCTIONS */) {
|
|
8739
8763
|
const mainAppFileContent = (0, import_node_fs3.readFileSync)(path);
|
|
8740
8764
|
const patchFetchContent = (0, import_node_fs3.readFileSync)(
|
|
8741
|
-
(0,
|
|
8765
|
+
(0, import_node_path3.join)(__dirname, "patch-fetch.js")
|
|
8742
8766
|
).toString().replace('"use strict";', "");
|
|
8743
8767
|
body = patchFetchContent + "\n" + mainAppFileContent;
|
|
8744
8768
|
} else {
|
|
@@ -8748,7 +8772,10 @@ async function fsToS3(props, nextjsType) {
|
|
|
8748
8772
|
Body: body,
|
|
8749
8773
|
Bucket: destinationBucketName,
|
|
8750
8774
|
ContentType: contentType,
|
|
8751
|
-
Key: key
|
|
8775
|
+
Key: key,
|
|
8776
|
+
Metadata: {
|
|
8777
|
+
"next-build-id": buildId
|
|
8778
|
+
}
|
|
8752
8779
|
};
|
|
8753
8780
|
}
|
|
8754
8781
|
);
|
|
@@ -8768,36 +8795,26 @@ async function fsToS3(props, nextjsType) {
|
|
|
8768
8795
|
function createS3Key({ keyPrefix, path, basePath }) {
|
|
8769
8796
|
const objectKeyParts = [];
|
|
8770
8797
|
if (keyPrefix) objectKeyParts.push(keyPrefix);
|
|
8771
|
-
objectKeyParts.push((0,
|
|
8772
|
-
return (0,
|
|
8773
|
-
}
|
|
8774
|
-
|
|
8775
|
-
// src/lambdas/assets-deployment/prune-s3.ts
|
|
8776
|
-
var import_client_s32 = require("@aws-sdk/client-s3");
|
|
8777
|
-
async function pruneS3(props) {
|
|
8778
|
-
props;
|
|
8798
|
+
objectKeyParts.push((0, import_node_path3.relative)(basePath, path));
|
|
8799
|
+
return (0, import_node_path3.join)(...objectKeyParts);
|
|
8779
8800
|
}
|
|
8780
8801
|
|
|
8781
8802
|
// src/lambdas/assets-deployment/assets-deployment.lambda.ts
|
|
8782
8803
|
var handler = async (event, context) => {
|
|
8783
8804
|
debug({ event });
|
|
8784
8805
|
let responseStatus = "FAILED" /* Failed */;
|
|
8785
|
-
let previewModeId = "";
|
|
8786
8806
|
try {
|
|
8787
8807
|
const props = event.ResourceProperties;
|
|
8788
8808
|
if (event.RequestType === "Create" || event.RequestType === "Update") {
|
|
8789
|
-
const { actions, nextjsType } = props;
|
|
8809
|
+
const { actions, buildId, nextjsType } = props;
|
|
8790
8810
|
for (const action of actions) {
|
|
8791
8811
|
if (action.type === "fs-to-fs") {
|
|
8792
8812
|
fsToFs(action);
|
|
8793
8813
|
} else if (action.type === "fs-to-s3") {
|
|
8794
|
-
await fsToS3(action, nextjsType);
|
|
8795
|
-
} else if (action.type === "prune-s3") {
|
|
8796
|
-
await pruneS3(action);
|
|
8814
|
+
await fsToS3({ ...action, nextjsType, buildId });
|
|
8797
8815
|
}
|
|
8798
8816
|
}
|
|
8799
8817
|
initImageCache(props.imageCachePath);
|
|
8800
|
-
previewModeId = getPreviewModeId(props.prerenderManifestPath);
|
|
8801
8818
|
responseStatus = "SUCCESS" /* Success */;
|
|
8802
8819
|
} else {
|
|
8803
8820
|
responseStatus = "SUCCESS" /* Success */;
|
|
@@ -8809,7 +8826,7 @@ var handler = async (event, context) => {
|
|
|
8809
8826
|
event,
|
|
8810
8827
|
context,
|
|
8811
8828
|
responseStatus,
|
|
8812
|
-
responseData: {
|
|
8829
|
+
responseData: {}
|
|
8813
8830
|
});
|
|
8814
8831
|
}
|
|
8815
8832
|
};
|
|
@@ -8818,12 +8835,6 @@ function initImageCache(imageCachePath) {
|
|
|
8818
8835
|
(0, import_node_fs4.mkdirSync)(imageCachePath);
|
|
8819
8836
|
}
|
|
8820
8837
|
}
|
|
8821
|
-
function getPreviewModeId(prerenderManifestPath) {
|
|
8822
|
-
const prerenderManifest = JSON.parse(
|
|
8823
|
-
(0, import_node_fs4.readFileSync)(prerenderManifestPath, { encoding: "utf-8" })
|
|
8824
|
-
);
|
|
8825
|
-
return prerenderManifest.preview.previewModeId;
|
|
8826
|
-
}
|
|
8827
8838
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8828
8839
|
0 && (module.exports = {
|
|
8829
8840
|
handler
|
|
@@ -0,0 +1,246 @@
|
|
|
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/lambdas/post-deploy/post-deploy.lambda.ts
|
|
21
|
+
var post_deploy_lambda_exports = {};
|
|
22
|
+
__export(post_deploy_lambda_exports, {
|
|
23
|
+
handler: () => handler
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(post_deploy_lambda_exports);
|
|
26
|
+
|
|
27
|
+
// src/lambdas/post-deploy/create-invalidation.ts
|
|
28
|
+
var import_client_cloudfront = require("@aws-sdk/client-cloudfront");
|
|
29
|
+
var cfClient = new import_client_cloudfront.CloudFrontClient();
|
|
30
|
+
async function createInvalidation(input) {
|
|
31
|
+
return cfClient.send(
|
|
32
|
+
new import_client_cloudfront.CreateInvalidationCommand({
|
|
33
|
+
DistributionId: input.distributionId,
|
|
34
|
+
InvalidationBatch: {
|
|
35
|
+
CallerReference: input.invalidationBatch.callerReference,
|
|
36
|
+
Paths: {
|
|
37
|
+
Quantity: input.invalidationBatch.paths.quantity,
|
|
38
|
+
Items: input.invalidationBatch.paths.items
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/lambdas/post-deploy/prune-fs.ts
|
|
46
|
+
var import_node_fs = require("node:fs");
|
|
47
|
+
var import_node_path = require("node:path");
|
|
48
|
+
|
|
49
|
+
// src/lambdas/utils.ts
|
|
50
|
+
function cfnResponse(props) {
|
|
51
|
+
const body = JSON.stringify({
|
|
52
|
+
Status: props.responseStatus,
|
|
53
|
+
Reason: "See the details in CloudWatch Log Stream: " + props.context.logStreamName,
|
|
54
|
+
PhysicalResourceId: props.physicalResourceId || props.context.logStreamName,
|
|
55
|
+
StackId: props.event.StackId,
|
|
56
|
+
RequestId: props.event.RequestId,
|
|
57
|
+
LogicalResourceId: props.event.LogicalResourceId,
|
|
58
|
+
Data: props.responseData
|
|
59
|
+
});
|
|
60
|
+
return fetch(props.event.ResponseURL, {
|
|
61
|
+
method: "PUT",
|
|
62
|
+
body,
|
|
63
|
+
headers: { "content-type": "", "content-length": body.length.toString() }
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function debug(value) {
|
|
67
|
+
if (process.env.DEBUG) console.log(JSON.stringify(value, null, 2));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/lambdas/post-deploy/prune-fs.ts
|
|
71
|
+
function pruneFs(props) {
|
|
72
|
+
const { mountPath, currentBuildId } = props;
|
|
73
|
+
if (!(0, import_node_fs.existsSync)(mountPath)) {
|
|
74
|
+
debug(`Mount path ${mountPath} does not exist, nothing to prune`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const entries = (0, import_node_fs.readdirSync)(mountPath);
|
|
78
|
+
const directoriesToDelete = entries.filter((entry) => {
|
|
79
|
+
const entryPath = (0, import_node_path.join)(mountPath, entry);
|
|
80
|
+
return (0, import_node_fs.statSync)(entryPath).isDirectory() && entry !== currentBuildId;
|
|
81
|
+
});
|
|
82
|
+
for (const dir of directoriesToDelete) {
|
|
83
|
+
const dirPath = (0, import_node_path.join)(mountPath, dir);
|
|
84
|
+
debug(`Pruning directory: ${dirPath}`);
|
|
85
|
+
(0, import_node_fs.rmSync)(dirPath, { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
debug(
|
|
88
|
+
`Pruned ${directoriesToDelete.length} directories, kept ${currentBuildId}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/lambdas/post-deploy/prune-s3.ts
|
|
93
|
+
var import_client_s3 = require("@aws-sdk/client-s3");
|
|
94
|
+
var s3Client = new import_client_s3.S3Client();
|
|
95
|
+
var MAX_CONCURRENT_OPERATIONS = 50;
|
|
96
|
+
async function pruneS3(props) {
|
|
97
|
+
const { bucketName, currentBuildId, msTtl } = props;
|
|
98
|
+
const cutoffDate = new Date(Date.now() - msTtl);
|
|
99
|
+
const objectsToDelete = [];
|
|
100
|
+
let continuationToken = void 0;
|
|
101
|
+
let listObjectsCount = 0;
|
|
102
|
+
do {
|
|
103
|
+
const listObjectsV2Input = {
|
|
104
|
+
Bucket: bucketName,
|
|
105
|
+
ContinuationToken: continuationToken
|
|
106
|
+
};
|
|
107
|
+
const listResponse = await s3Client.send(
|
|
108
|
+
new import_client_s3.ListObjectsV2Command(listObjectsV2Input)
|
|
109
|
+
);
|
|
110
|
+
if (!listResponse.Contents || listResponse.Contents.length === 0) {
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
const oldObjects = listResponse.Contents.filter((obj) => {
|
|
114
|
+
const lastModified = obj.LastModified || /* @__PURE__ */ new Date();
|
|
115
|
+
return obj.Key && lastModified < cutoffDate;
|
|
116
|
+
});
|
|
117
|
+
debug(
|
|
118
|
+
`Checking old objects metadata to determine pruning: ${oldObjects.map((o) => o.Key)}`
|
|
119
|
+
);
|
|
120
|
+
const checkResults = await processBatch(
|
|
121
|
+
oldObjects,
|
|
122
|
+
MAX_CONCURRENT_OPERATIONS,
|
|
123
|
+
async (object) => {
|
|
124
|
+
if (!object.Key) return null;
|
|
125
|
+
try {
|
|
126
|
+
const headResponse = await s3Client.send(
|
|
127
|
+
new import_client_s3.HeadObjectCommand({
|
|
128
|
+
Bucket: bucketName,
|
|
129
|
+
Key: object.Key
|
|
130
|
+
})
|
|
131
|
+
);
|
|
132
|
+
const objectBuildId = headResponse.Metadata?.["next-build-id"];
|
|
133
|
+
if (objectBuildId !== currentBuildId) {
|
|
134
|
+
return { Key: object.Key };
|
|
135
|
+
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error(`Error checking object ${object.Key}:`, error);
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
objectsToDelete.push(
|
|
143
|
+
...checkResults.filter(Boolean)
|
|
144
|
+
);
|
|
145
|
+
if (listResponse.NextContinuationToken) {
|
|
146
|
+
continuationToken = listResponse.NextContinuationToken;
|
|
147
|
+
}
|
|
148
|
+
listObjectsCount++;
|
|
149
|
+
} while (continuationToken && listObjectsCount <= 100);
|
|
150
|
+
if (objectsToDelete.length > 0) {
|
|
151
|
+
const deleteBatches = [];
|
|
152
|
+
for (let i = 0; i < objectsToDelete.length; i += 1e3) {
|
|
153
|
+
const batch = objectsToDelete.slice(i, i + 1e3);
|
|
154
|
+
deleteBatches.push(batch);
|
|
155
|
+
}
|
|
156
|
+
await processBatch(
|
|
157
|
+
deleteBatches,
|
|
158
|
+
5,
|
|
159
|
+
// Process up to 5 delete batches in parallel
|
|
160
|
+
async (batch) => {
|
|
161
|
+
try {
|
|
162
|
+
debug(
|
|
163
|
+
`Deleting objects: ${batch.map((b) => b.Key)} from ${bucketName}`
|
|
164
|
+
);
|
|
165
|
+
await s3Client.send(
|
|
166
|
+
new import_client_s3.DeleteObjectsCommand({
|
|
167
|
+
Bucket: bucketName,
|
|
168
|
+
Delete: { Objects: batch }
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
debug(`Deleted ${batch.length} objects from ${bucketName}`);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error("Error deleting objects:", error);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
debug(
|
|
179
|
+
`Pruning complete. Deleted ${objectsToDelete.length} objects from ${bucketName}`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
async function processBatch(items, batchSize, processFn) {
|
|
183
|
+
const results = [];
|
|
184
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
185
|
+
const batch = items.slice(i, i + batchSize);
|
|
186
|
+
const batchResults = await Promise.all(batch.map(processFn));
|
|
187
|
+
results.push(...batchResults);
|
|
188
|
+
}
|
|
189
|
+
return results;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/constants.ts
|
|
193
|
+
var MOUNT_PATH = "/mnt/cdk-nextjs";
|
|
194
|
+
var CACHE_PATH = ".next/cache";
|
|
195
|
+
var DATA_CACHE_PATH = `${CACHE_PATH}/fetch-cache`;
|
|
196
|
+
var IMAGE_CACHE_PATH = `${CACHE_PATH}/images`;
|
|
197
|
+
var SERVER_DIST_PATH = ".next/server";
|
|
198
|
+
var FULL_ROUTE_CACHE_PATH = `${SERVER_DIST_PATH}/app`;
|
|
199
|
+
|
|
200
|
+
// src/lambdas/post-deploy/post-deploy.lambda.ts
|
|
201
|
+
var handler = async (event, context) => {
|
|
202
|
+
debug({ event });
|
|
203
|
+
let responseStatus = "FAILED" /* Failed */;
|
|
204
|
+
try {
|
|
205
|
+
const props = event.ResourceProperties;
|
|
206
|
+
if (event.RequestType === "Create" || event.RequestType === "Update") {
|
|
207
|
+
const {
|
|
208
|
+
buildId,
|
|
209
|
+
createInvalidationCommandInput,
|
|
210
|
+
msTtl,
|
|
211
|
+
staticAssetsBucketName
|
|
212
|
+
} = props;
|
|
213
|
+
const promises = [];
|
|
214
|
+
if (createInvalidationCommandInput) {
|
|
215
|
+
promises.push(createInvalidation(createInvalidationCommandInput));
|
|
216
|
+
}
|
|
217
|
+
pruneFs({ currentBuildId: buildId, mountPath: MOUNT_PATH });
|
|
218
|
+
if (staticAssetsBucketName) {
|
|
219
|
+
promises.push(
|
|
220
|
+
pruneS3({
|
|
221
|
+
bucketName: staticAssetsBucketName,
|
|
222
|
+
currentBuildId: buildId,
|
|
223
|
+
msTtl: parseInt(msTtl)
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
await Promise.all(promises);
|
|
228
|
+
responseStatus = "SUCCESS" /* Success */;
|
|
229
|
+
} else {
|
|
230
|
+
responseStatus = "SUCCESS" /* Success */;
|
|
231
|
+
}
|
|
232
|
+
} catch (err) {
|
|
233
|
+
console.error(err);
|
|
234
|
+
} finally {
|
|
235
|
+
await cfnResponse({
|
|
236
|
+
event,
|
|
237
|
+
context,
|
|
238
|
+
responseStatus,
|
|
239
|
+
responseData: {}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
244
|
+
0 && (module.exports = {
|
|
245
|
+
handler
|
|
246
|
+
});
|
package/docs/breaking-changes.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Breaking Changes
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
- Change `Nextjs{...}Props.relativePathToWorkspace` -> `Nextjs{...}Props.relativePathToPackage` because workspace references entire monorepo not a package in the monorepo.
|
|
6
|
+
- [NextjsGlobalFunctions] Remove `NextjsRevalidation`. `NextjsRevalidation` was an initial effor to support ISR. This wasn't working as intended. With [RFC: Deployment Adapters API](https://github.com/vercel/next.js/discussions/77740) specifically addressing the pain point, "No signal for when background work like revalidating is complete", cdk-nextjs will support ISR once the `waitFor` callback is officially supported by Next.js instead of using internal workaround. This is inline with the design principles of cdk-nextjs. Removing this construct simplifies cdk-nextjs and makes it more maintainable long term.
|
|
7
|
+
- [NextjsGlobal...] Refactor `NextjsInvalidation` to `NextjsPostDeploy` which now does not use `AwsCustomResource` but is `CustomResource` which calls `CreateInvalidation` API within, in addition to pruning EFS old BUILD_ID folder and old S3 static assets
|
|
8
|
+
- Relevant but not user facing change is that EFS Mounts are now segmented by BUILD_ID and S3 objects are now tagged with "next-build-id" metadata which are a step towards Blue/Green deployments.
|
|
9
|
+
- Change BUILDER_IMAGE_TAG to BUILDER_IMAGE_ALIAS in `NextjsBuild` and Dockerfiles. This change is mostly internal and will only affect you if you used overrides. Reason for change was to resolve Docker warning, `InvalidDefaultArgInFrom`, and to improve semantics. A tag in Dockerfile reference is what comes after the :. Alias is repo:tag. Now repository (cdk-nextjs/builder) and tag (CDK's `node.addr.slice(30)`) so we can properly default to `cdk-nextjs/builder:latest` in Dockerfiles.
|
|
10
|
+
- Remove `overrides.{nextjs...}.nextjsBuildProps.customDockerfilePath` in favor of `buildContext` + `overrides.{nextjs...}.nextjsBuildProps.file`. Simplies from 2 ways to 1. Default builder image Dockerfile name is now "builder.Dockerfile" too.
|
|
11
|
+
|
|
3
12
|
## 0.3.0
|
|
4
13
|
|
|
5
14
|
- Change `NextjsDistributionProps.loadBalancer` type from `ILoadBalancerV2` to `ApplicationLoadBalancer`
|