cdk-nextjs 0.6.1 → 0.7.0
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 +2467 -2714
- package/.jsii.tabl.json +1 -1
- package/API.md +1725 -1909
- package/CLAUDE.md +2 -1
- package/README.md +429 -130
- package/assets/lambdas/post-deploy/post-deploy.lambda/index.js +152 -170
- package/assets/lambdas/redeploy-stage/redeploy-stage.lambda/index.js +875 -0
- package/docs/adapter-runtime.md +185 -0
- package/docs/breaking-changes.md +687 -4
- package/docs/caching-guide.md +156 -112
- package/docs/cdk-nextjs-cfn-exec-policy.json +2 -1
- package/docs/development-guide.md +1 -1
- package/docs/harness-coverage.md +598 -0
- package/docs/init-cache-deployment.md +32 -1
- package/docs/next-build-output-guide.md +30 -20
- package/examples/README.md +5 -1
- package/lib/adapter/__fixtures__/app-playground-base-path.json +4926 -0
- package/lib/adapter/__fixtures__/app-playground.json +4917 -0
- package/lib/adapter/__fixtures__/pages-i18n.json +1728 -0
- package/lib/adapter/adapter.mjs +1548 -185
- package/lib/adapter/adapter.test.js +106 -0
- package/lib/adapter/aws-cache-store.d.ts +489 -0
- package/lib/adapter/aws-cache-store.js +1004 -0
- package/lib/adapter/aws-cache-store.test.d.ts +1 -0
- package/lib/adapter/aws-cache-store.test.js +361 -0
- package/lib/adapter/build-outputs.d.ts +80 -0
- package/lib/adapter/build-outputs.js +852 -0
- package/lib/adapter/build-outputs.test.d.ts +1 -0
- package/lib/adapter/build-outputs.test.js +861 -0
- package/lib/adapter/cache-handler.d.ts +9 -2
- package/lib/adapter/cache-handler.js +72 -96
- package/lib/adapter/cache-handler.mjs +18956 -17774
- package/lib/adapter/cache-handler.test.js +263 -31
- package/lib/adapter/cache-kinds.d.ts +32 -0
- package/lib/adapter/cache-kinds.js +35 -0
- package/lib/adapter/cache-kinds.test.d.ts +1 -0
- package/lib/adapter/cache-kinds.test.js +69 -0
- package/lib/adapter/cache-utils.d.ts +90 -0
- package/lib/adapter/cache-utils.js +199 -4
- package/lib/adapter/cache-utils.test.d.ts +1 -0
- package/lib/adapter/cache-utils.test.js +179 -0
- package/lib/adapter/function-groups.d.ts +215 -0
- package/lib/adapter/function-groups.js +1099 -0
- package/lib/adapter/function-groups.test.d.ts +1 -0
- package/lib/adapter/function-groups.test.js +743 -0
- package/lib/adapter/init-cache.d.ts +6 -0
- package/lib/adapter/init-cache.js +245 -0
- package/lib/adapter/init-cache.test.d.ts +1 -0
- package/lib/adapter/init-cache.test.js +137 -0
- package/lib/adapter/local-file-cache-handler.d.ts +23 -0
- package/lib/adapter/local-file-cache-handler.js +52 -1
- package/lib/adapter/memory-cache-handler.d.ts +26 -17
- package/lib/adapter/memory-cache-handler.js +32 -50
- package/lib/adapter/memory-cache-handler.test.js +105 -85
- package/lib/adapter/s3-cache-handler.d.ts +179 -40
- package/lib/adapter/s3-cache-handler.js +807 -277
- package/lib/adapter/s3-cache-handler.test.js +1186 -31
- package/lib/adapter/use-cache-common.d.ts +90 -0
- package/lib/adapter/use-cache-common.js +250 -0
- package/lib/adapter/use-cache-common.test.d.ts +1 -0
- package/lib/adapter/use-cache-common.test.js +646 -0
- package/lib/adapter/use-cache-default-handler.d.ts +24 -0
- package/lib/adapter/use-cache-default-handler.js +90 -0
- package/lib/adapter/use-cache-default-handler.mjs +38645 -0
- package/lib/adapter/use-cache-handlers.test.d.ts +1 -0
- package/lib/adapter/use-cache-handlers.test.js +349 -0
- package/lib/adapter/use-cache-remote-handler.d.ts +26 -0
- package/lib/adapter/use-cache-remote-handler.js +186 -0
- package/lib/{image-optimization/handler.mjs → adapter/use-cache-remote-handler.mjs} +33572 -38798
- package/lib/generated-structs/OptionalNextjsApiProps.d.ts +59 -0
- package/lib/generated-structs/OptionalNextjsApiProps.js +3 -0
- package/lib/generated-structs/OptionalNextjsCacheProps.d.ts +15 -6
- package/lib/generated-structs/OptionalNextjsCacheProps.js +1 -1
- package/lib/generated-structs/OptionalNextjsContainersProps.d.ts +25 -11
- package/lib/generated-structs/OptionalNextjsContainersProps.js +1 -1
- package/lib/generated-structs/OptionalNextjsDistributionProps.d.ts +31 -9
- package/lib/generated-structs/OptionalNextjsDistributionProps.js +1 -1
- package/lib/generated-structs/OptionalNextjsPostDeployProps.d.ts +13 -0
- package/lib/generated-structs/OptionalNextjsPostDeployProps.js +1 -1
- package/lib/generated-structs/OptionalPostDeployCustomResourceProperties.d.ts +11 -2
- package/lib/generated-structs/OptionalPostDeployCustomResourceProperties.js +1 -1
- package/lib/index.d.ts +6 -7
- package/lib/index.js +2 -4
- package/lib/lambdas/post-deploy/post-deploy.lambda.js +3 -2
- package/lib/lambdas/post-deploy/prune-cache-bucket.d.ts +7 -3
- package/lib/lambdas/post-deploy/prune-cache-bucket.js +57 -71
- package/lib/lambdas/post-deploy/prune-cache-bucket.test.d.ts +1 -0
- package/lib/lambdas/post-deploy/prune-cache-bucket.test.js +114 -0
- package/lib/lambdas/post-deploy/prune-revalidation-table.d.ts +1 -0
- package/lib/lambdas/post-deploy/prune-revalidation-table.js +23 -31
- package/lib/lambdas/post-deploy/prune-revalidation-table.test.d.ts +1 -0
- package/lib/lambdas/post-deploy/prune-revalidation-table.test.js +71 -0
- package/lib/lambdas/post-deploy/prune-s3.d.ts +19 -3
- package/lib/lambdas/post-deploy/prune-s3.js +54 -15
- package/lib/lambdas/post-deploy/prune-s3.test.d.ts +1 -0
- package/lib/lambdas/post-deploy/prune-s3.test.js +255 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage-function.d.ts +13 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage-function.js +55 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage.d.ts +24 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage.js +68 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage.lambda.d.ts +20 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage.lambda.js +24 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage.test.d.ts +1 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage.test.js +93 -0
- package/lib/nextjs-api.d.ts +107 -18
- package/lib/nextjs-api.js +349 -39
- package/lib/nextjs-api.test.js +582 -1
- package/lib/nextjs-build/global-containers.Dockerfile +15 -10
- package/lib/nextjs-build/nextjs-build.d.ts +339 -63
- package/lib/nextjs-build/nextjs-build.js +684 -327
- package/lib/nextjs-build/nextjs-build.test.d.ts +1 -0
- package/lib/nextjs-build/nextjs-build.test.js +412 -0
- package/lib/nextjs-build/patch-fetch.js +1 -1
- package/lib/nextjs-build/patch-fetch.test.js +166 -11
- package/lib/nextjs-build/regional-containers.Dockerfile +17 -10
- package/lib/nextjs-cache.d.ts +82 -4
- package/lib/nextjs-cache.js +186 -10
- package/lib/nextjs-cache.test.js +176 -1
- package/lib/nextjs-compute/nextjs-compute-base-props.d.ts +21 -8
- package/lib/nextjs-compute/nextjs-compute-base-props.js +41 -1
- package/lib/nextjs-compute/nextjs-compute-base-props.test.d.ts +1 -0
- package/lib/nextjs-compute/nextjs-compute-base-props.test.js +44 -0
- package/lib/nextjs-compute/nextjs-containers.d.ts +19 -0
- package/lib/nextjs-compute/nextjs-containers.js +57 -20
- package/lib/nextjs-compute/nextjs-functions.d.ts +122 -7
- package/lib/nextjs-compute/nextjs-functions.js +113 -67
- package/lib/nextjs-compute/nextjs-functions.test.d.ts +1 -0
- package/lib/nextjs-compute/nextjs-functions.test.js +99 -0
- package/lib/nextjs-distribution.d.ts +194 -23
- package/lib/nextjs-distribution.js +535 -62
- package/lib/nextjs-distribution.test.d.ts +1 -0
- package/lib/nextjs-distribution.test.js +697 -0
- package/lib/nextjs-post-deploy.d.ts +23 -3
- package/lib/nextjs-post-deploy.js +14 -10
- package/lib/nextjs-post-deploy.test.d.ts +1 -0
- package/lib/nextjs-post-deploy.test.js +34 -0
- package/lib/nextjs-static-assets.d.ts +23 -4
- package/lib/nextjs-static-assets.js +29 -6
- package/lib/nextjs-static-assets.test.js +20 -1
- package/lib/root-constructs/nextjs-base-construct.d.ts +105 -45
- package/lib/root-constructs/nextjs-base-construct.js +144 -34
- package/lib/root-constructs/nextjs-global-containers.d.ts +18 -20
- package/lib/root-constructs/nextjs-global-containers.js +20 -70
- package/lib/root-constructs/nextjs-global-functions.d.ts +26 -28
- package/lib/root-constructs/nextjs-global-functions.js +52 -69
- package/lib/root-constructs/nextjs-regional-containers.d.ts +25 -5
- package/lib/root-constructs/nextjs-regional-containers.js +17 -15
- package/lib/root-constructs/nextjs-regional-functions.d.ts +22 -18
- package/lib/root-constructs/nextjs-regional-functions.js +8 -25
- package/lib/runtime/api-gateway-path.d.ts +34 -0
- package/lib/runtime/api-gateway-path.js +71 -0
- package/lib/runtime/api-gateway-path.test.d.ts +1 -0
- package/lib/runtime/api-gateway-path.test.js +65 -0
- package/lib/runtime/case-redirect.d.ts +22 -0
- package/lib/runtime/case-redirect.js +63 -0
- package/lib/runtime/case-redirect.test.d.ts +1 -0
- package/lib/runtime/case-redirect.test.js +32 -0
- package/lib/runtime/core.d.ts +234 -0
- package/lib/runtime/core.js +1213 -0
- package/lib/runtime/core.test.d.ts +1 -0
- package/lib/runtime/core.test.js +1678 -0
- package/lib/runtime/deployment-root.d.ts +13 -0
- package/lib/runtime/deployment-root.js +29 -0
- package/lib/runtime/dispatch.d.ts +331 -0
- package/lib/runtime/dispatch.js +751 -0
- package/lib/runtime/dispatch.test.d.ts +1 -0
- package/lib/runtime/dispatch.test.js +853 -0
- package/lib/runtime/entrypoints.d.ts +57 -0
- package/lib/runtime/entrypoints.js +115 -0
- package/lib/runtime/http/abort-signal.d.ts +6 -0
- package/lib/runtime/http/abort-signal.js +19 -0
- package/lib/runtime/http/abort-signal.test.d.ts +1 -0
- package/lib/runtime/http/abort-signal.test.js +49 -0
- package/lib/runtime/http/node-server.d.ts +38 -0
- package/lib/runtime/http/node-server.js +99 -0
- package/lib/runtime/http/node-server.test.d.ts +1 -0
- package/lib/runtime/http/node-server.test.js +96 -0
- package/lib/runtime/http/node-sink.d.ts +16 -0
- package/lib/runtime/http/node-sink.js +48 -0
- package/lib/runtime/http/node-sink.test.d.ts +1 -0
- package/lib/runtime/http/node-sink.test.js +66 -0
- package/lib/runtime/http/request.d.ts +62 -0
- package/lib/runtime/http/request.js +134 -0
- package/lib/runtime/http/request.test.d.ts +1 -0
- package/lib/runtime/http/request.test.js +103 -0
- package/lib/runtime/http/response.d.ts +77 -0
- package/lib/runtime/http/response.js +229 -0
- package/lib/runtime/http/response.test.d.ts +1 -0
- package/lib/runtime/http/response.test.js +178 -0
- package/lib/runtime/http/sink.d.ts +73 -0
- package/lib/runtime/http/sink.js +188 -0
- package/lib/runtime/http/sink.test.d.ts +1 -0
- package/lib/runtime/http/sink.test.js +257 -0
- package/lib/runtime/image-utils.d.ts +78 -0
- package/lib/runtime/image-utils.js +129 -0
- package/lib/runtime/image-utils.test.d.ts +1 -0
- package/lib/runtime/image-utils.test.js +237 -0
- package/lib/runtime/image.d.ts +76 -0
- package/lib/runtime/image.js +296 -0
- package/lib/runtime/image.test.d.ts +1 -0
- package/lib/runtime/image.test.js +405 -0
- package/lib/runtime/lambda-event.d.ts +4 -0
- package/lib/runtime/lambda-event.js +113 -0
- package/lib/runtime/lambda-event.test.d.ts +1 -0
- package/lib/runtime/lambda-event.test.js +119 -0
- package/lib/runtime/lambda.mjs +41595 -0
- package/lib/runtime/load-module.d.ts +8 -0
- package/lib/runtime/load-module.js +35 -0
- package/lib/runtime/manifest.d.ts +215 -0
- package/lib/runtime/manifest.js +118 -0
- package/lib/runtime/middleware.d.ts +43 -0
- package/lib/runtime/middleware.js +113 -0
- package/lib/runtime/middleware.test.d.ts +1 -0
- package/lib/runtime/middleware.test.js +259 -0
- package/lib/runtime/next-modules.d.ts +55 -0
- package/lib/runtime/next-modules.js +124 -0
- package/lib/runtime/next-modules.test.d.ts +1 -0
- package/lib/runtime/next-modules.test.js +34 -0
- package/lib/runtime/public-files.d.ts +32 -0
- package/lib/runtime/public-files.js +117 -0
- package/lib/runtime/public-files.test.d.ts +1 -0
- package/lib/runtime/public-files.test.js +97 -0
- package/lib/runtime/server.mjs +41588 -0
- package/lib/runtime/static-files.d.ts +50 -0
- package/lib/runtime/static-files.js +266 -0
- package/lib/runtime/static-files.test.d.ts +1 -0
- package/lib/runtime/static-files.test.js +118 -0
- package/lib/runtime/util.d.ts +28 -0
- package/lib/runtime/util.js +79 -0
- package/lib/utils/base-path.d.ts +123 -11
- package/lib/utils/base-path.js +276 -39
- package/lib/utils/base-path.test.js +275 -50
- package/lib/utils/get-architecture.d.ts +7 -8
- package/lib/utils/get-architecture.js +12 -13
- package/lib/utils/zip-directory.d.ts +14 -0
- package/lib/utils/zip-directory.js +121 -0
- package/lib/utils/zip-directory.test.d.ts +1 -0
- package/lib/utils/zip-directory.test.js +99 -0
- package/package.json +22 -3
- package/scripts/capture-adapter-fixture.mjs +325 -0
- package/scripts/e2e-deploy.sh +398 -0
- package/scripts/e2e-harness/README.md +707 -0
- package/scripts/e2e-harness/app.js +370 -0
- package/scripts/e2e-harness/common.sh +249 -0
- package/scripts/e2e-harness/screen.mjs +302 -0
- package/scripts/e2e-harness/stage-proxy.mjs +145 -0
- package/scripts/e2e-harness/type-filters.mjs +47 -0
- package/scripts/e2e-logs.sh +75 -0
- package/scripts/e2e-offline.sh +91 -0
- package/scripts/e2e-sweep.sh +370 -0
- package/scripts/e2e-warm.sh +114 -0
- package/scripts/zero-config-build.mjs +418 -0
- package/docs/1k-concurrent-users-executions.png +0 -0
- package/docs/plans/adapter-runtime-release.md +0 -362
- package/docs/plans/phase-0-image-function-opt-in.md +0 -295
- package/lib/generated-structs/OptionalDockerImageFunctionProps.d.ts +0 -425
- package/lib/generated-structs/OptionalDockerImageFunctionProps.js +0 -3
- package/lib/image-optimization/handler-utils.d.ts +0 -34
- package/lib/image-optimization/handler-utils.js +0 -83
- package/lib/image-optimization/handler-utils.test.js +0 -178
- package/lib/nextjs-build/functions.Dockerfile +0 -31
- package/lib/nextjs-compute/nextjs-image-function.d.ts +0 -53
- package/lib/nextjs-compute/nextjs-image-function.js +0 -59
- package/lib/utils/experimental-flags.d.ts +0 -24
- package/lib/utils/experimental-flags.js +0 -30
- /package/lib/{image-optimization/handler-utils.test.d.ts → adapter/adapter.test.d.ts} +0 -0
package/lib/adapter/adapter.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createRequire } from 'node:module'; const require =
|
|
1
|
+
import { createRequire as __cdkNextjsCreateRequire } from 'node:module'; const require = __cdkNextjsCreateRequire(import.meta.url);
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -183,11 +183,11 @@ var require_common = __commonJS({
|
|
|
183
183
|
let enableOverride = null;
|
|
184
184
|
let namespacesCache;
|
|
185
185
|
let enabledCache;
|
|
186
|
-
function
|
|
187
|
-
if (!
|
|
186
|
+
function debug3(...args) {
|
|
187
|
+
if (!debug3.enabled) {
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
190
|
-
const self =
|
|
190
|
+
const self = debug3;
|
|
191
191
|
const curr = Number(/* @__PURE__ */ new Date());
|
|
192
192
|
const ms = curr - (prevTime || curr);
|
|
193
193
|
self.diff = ms;
|
|
@@ -217,12 +217,12 @@ var require_common = __commonJS({
|
|
|
217
217
|
const logFn = self.log || createDebug.log;
|
|
218
218
|
logFn.apply(self, args);
|
|
219
219
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
Object.defineProperty(
|
|
220
|
+
debug3.namespace = namespace;
|
|
221
|
+
debug3.useColors = createDebug.useColors();
|
|
222
|
+
debug3.color = createDebug.selectColor(namespace);
|
|
223
|
+
debug3.extend = extend;
|
|
224
|
+
debug3.destroy = createDebug.destroy;
|
|
225
|
+
Object.defineProperty(debug3, "enabled", {
|
|
226
226
|
enumerable: true,
|
|
227
227
|
configurable: false,
|
|
228
228
|
get: () => {
|
|
@@ -240,9 +240,9 @@ var require_common = __commonJS({
|
|
|
240
240
|
}
|
|
241
241
|
});
|
|
242
242
|
if (typeof createDebug.init === "function") {
|
|
243
|
-
createDebug.init(
|
|
243
|
+
createDebug.init(debug3);
|
|
244
244
|
}
|
|
245
|
-
return
|
|
245
|
+
return debug3;
|
|
246
246
|
}
|
|
247
247
|
function extend(namespace, delimiter) {
|
|
248
248
|
const newDebug = createDebug(this.namespace + (typeof delimiter === "undefined" ? ":" : delimiter) + namespace);
|
|
@@ -761,11 +761,11 @@ var require_node = __commonJS({
|
|
|
761
761
|
function load() {
|
|
762
762
|
return process.env.DEBUG;
|
|
763
763
|
}
|
|
764
|
-
function init(
|
|
765
|
-
|
|
764
|
+
function init(debug3) {
|
|
765
|
+
debug3.inspectOpts = {};
|
|
766
766
|
const keys = Object.keys(exports.inspectOpts);
|
|
767
767
|
for (let i = 0; i < keys.length; i++) {
|
|
768
|
-
|
|
768
|
+
debug3.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
|
|
769
769
|
}
|
|
770
770
|
}
|
|
771
771
|
module.exports = require_common()(exports);
|
|
@@ -793,11 +793,1285 @@ var require_src = __commonJS({
|
|
|
793
793
|
});
|
|
794
794
|
|
|
795
795
|
// src/adapter/adapter.mts
|
|
796
|
-
import {
|
|
797
|
-
import { join } from "node:path";
|
|
796
|
+
import { join as join3 } from "node:path";
|
|
798
797
|
import { fileURLToPath } from "node:url";
|
|
799
|
-
|
|
800
|
-
|
|
798
|
+
|
|
799
|
+
// src/adapter/build-outputs.ts
|
|
800
|
+
import { existsSync, lstatSync, realpathSync } from "node:fs";
|
|
801
|
+
import {
|
|
802
|
+
copyFile,
|
|
803
|
+
cp,
|
|
804
|
+
mkdir,
|
|
805
|
+
readlink,
|
|
806
|
+
rm,
|
|
807
|
+
symlink,
|
|
808
|
+
writeFile
|
|
809
|
+
} from "node:fs/promises";
|
|
810
|
+
import { createRequire } from "node:module";
|
|
811
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
812
|
+
|
|
813
|
+
// src/constants.ts
|
|
814
|
+
var LOG_PREFIX = "[cdk-nextjs]";
|
|
815
|
+
|
|
816
|
+
// src/runtime/manifest.ts
|
|
817
|
+
var ADAPTER_MANIFEST_VERSION = 1;
|
|
818
|
+
var ADAPTER_DIR_NAME = "cdk-nextjs-adapter";
|
|
819
|
+
var STAGING_DIR_NAME = "app";
|
|
820
|
+
var GROUPS_DIR_NAME = "groups";
|
|
821
|
+
function groupStagingDirName(group) {
|
|
822
|
+
return group === void 0 ? STAGING_DIR_NAME : `${GROUPS_DIR_NAME}/${group}`;
|
|
823
|
+
}
|
|
824
|
+
var MANIFEST_FILE_NAME = "manifest.json";
|
|
825
|
+
var STATUS_PAGE_SUFFIXES = {
|
|
826
|
+
404: ["/_not-found", "/404"],
|
|
827
|
+
500: ["/500"],
|
|
828
|
+
fallback: "/_error"
|
|
829
|
+
};
|
|
830
|
+
var ERROR_PAGE_SUFFIXES = [
|
|
831
|
+
...STATUS_PAGE_SUFFIXES[404],
|
|
832
|
+
...STATUS_PAGE_SUFFIXES[500],
|
|
833
|
+
STATUS_PAGE_SUFFIXES.fallback
|
|
834
|
+
];
|
|
835
|
+
var RUNTIME_DIR_NAME = "cdk-nextjs-runtime";
|
|
836
|
+
|
|
837
|
+
// src/utils/base-path.ts
|
|
838
|
+
function normalizeBasePath(basePath) {
|
|
839
|
+
return (basePath || "").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
840
|
+
}
|
|
841
|
+
function hasPathPrefix(path, prefix) {
|
|
842
|
+
return !!prefix && (path === prefix || path.startsWith(`${prefix}/`));
|
|
843
|
+
}
|
|
844
|
+
function basePathPrefix(basePath) {
|
|
845
|
+
const bare = normalizeBasePath(basePath);
|
|
846
|
+
return bare ? `/${bare}` : "";
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/adapter/function-groups.ts
|
|
850
|
+
var DEFAULT_FUNCTION_GROUP = "default";
|
|
851
|
+
var FUNCTION_GROUPS_ENV_VAR = "CDK_NEXTJS_FUNCTION_GROUPS";
|
|
852
|
+
var ERROR_PREFIX = `${LOG_PREFIX} \`functionGroups\`: `;
|
|
853
|
+
var NAME_PATTERN = /^[a-zA-Z0-9-]+$/;
|
|
854
|
+
var SUBTREE_SUFFIX = "/**";
|
|
855
|
+
var PATH_PATTERN_LITERAL = /[a-zA-Z0-9_\-.$/~"'@:+&]/;
|
|
856
|
+
function parseFunctionGroupsEnv(value) {
|
|
857
|
+
return value ? JSON.parse(value) : void 0;
|
|
858
|
+
}
|
|
859
|
+
function assertNoI18nSplitting(i18n) {
|
|
860
|
+
if (i18n === null || i18n === void 0) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
throw new Error(
|
|
864
|
+
`${ERROR_PREFIX}\`functionGroups\` cannot be combined with \`i18n\`. With i18n every route is locale-prefixed, so routing a group at the edge would need one CloudFront behavior per locale per pattern, against a quota of 75 for the whole distribution. Deploy one function for every route (omit \`functionGroups\`), or drop \`i18n\`.`
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
function validateFunctionGroups(groups) {
|
|
868
|
+
if (groups.length === 0) {
|
|
869
|
+
throw new Error(
|
|
870
|
+
`${ERROR_PREFIX}\`functionGroups\` is an empty array. Omit the prop entirely to deploy one function for every route, which is the default.`
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
874
|
+
const patternOwner = /* @__PURE__ */ new Map();
|
|
875
|
+
for (const group of groups) {
|
|
876
|
+
if (group.name === DEFAULT_FUNCTION_GROUP) {
|
|
877
|
+
throw new Error(
|
|
878
|
+
`${ERROR_PREFIX}"${DEFAULT_FUNCTION_GROUP}" is a reserved group name: it is the implicit group every unassigned route falls into.`
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
if (!NAME_PATTERN.test(group.name)) {
|
|
882
|
+
throw new Error(
|
|
883
|
+
`${ERROR_PREFIX}Group name "${group.name}" must match ${NAME_PATTERN} \u2014 it becomes a construct id and part of a Lambda function name.`
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
if (seenNames.has(group.name)) {
|
|
887
|
+
throw new Error(
|
|
888
|
+
`${ERROR_PREFIX}Two groups are both named "${group.name}".`
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
seenNames.add(group.name);
|
|
892
|
+
if (group.routes.length === 0) {
|
|
893
|
+
throw new Error(
|
|
894
|
+
`${ERROR_PREFIX}Group "${group.name}" owns no routes. Every group is a Lambda function, so an empty one would deploy and never be reached.`
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
for (const route of group.routes) {
|
|
898
|
+
validateRoutePattern(route, group.name);
|
|
899
|
+
const owner = patternOwner.get(route);
|
|
900
|
+
if (owner !== void 0) {
|
|
901
|
+
throw new Error(
|
|
902
|
+
`${ERROR_PREFIX}Pattern "${route}" appears in both group "${owner}" and group "${group.name}". A route can only be packaged into one function. (Overlapping-but-different patterns are fine: "/api/**" in one group and "/api/reports/**" in another resolves longest-first.)`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
patternOwner.set(route, group.name);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
function validateRoutePattern(route, groupName) {
|
|
910
|
+
const where = `Group "${groupName}" pattern "${route}"`;
|
|
911
|
+
if (!route.startsWith("/")) {
|
|
912
|
+
throw new Error(
|
|
913
|
+
`${ERROR_PREFIX}${where} must start with "/". Patterns are URL paths as the browser requests them.`
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
if (route === "/") {
|
|
917
|
+
throw new Error(
|
|
918
|
+
`${ERROR_PREFIX}${where} cannot be routed. CloudFront has no path pattern that matches only "/" \u2014 the default behavior serves it \u2014 so the home page always belongs to the "${DEFAULT_FUNCTION_GROUP}" group. Group the routes around it instead.`
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
if (route === "/_next" || route.startsWith("/_next/")) {
|
|
922
|
+
throw new Error(
|
|
923
|
+
`${ERROR_PREFIX}${where} is under "/_next", which Next.js reserves for build assets, image optimization and data routes. A Pages Router page's "/_next/data/\u2026" route follows the page into its group, so group the page itself instead.`
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
if (route === SUBTREE_SUFFIX) {
|
|
927
|
+
throw new Error(
|
|
928
|
+
`${ERROR_PREFIX}${where} would own every route, leaving the default group empty. Splitting exists to divide routes between functions; omit \`functionGroups\` to deploy them all in one.`
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
if (/[[\]]/.test(route)) {
|
|
932
|
+
throw new Error(
|
|
933
|
+
`${ERROR_PREFIX}${where} contains a dynamic segment. Patterns become CloudFront behavior path patterns, which support only "*" and "?", so "${route}" could only deploy as a wildcard that also captures its siblings. Use a subtree instead: "/blog/**" owns "/blog/[slug]".`
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
if (/\((.*)\)/.test(route)) {
|
|
937
|
+
throw new Error(
|
|
938
|
+
`${ERROR_PREFIX}${where} contains a route group segment. Route groups like "(marketing)" organize files and never appear in a URL, so they cannot be routed on. Use the URL path the route is served at.`
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
const withoutSubtree = route.endsWith(SUBTREE_SUFFIX) ? route.slice(0, -SUBTREE_SUFFIX.length) : route;
|
|
942
|
+
if (/[*?]/.test(withoutSubtree)) {
|
|
943
|
+
throw new Error(
|
|
944
|
+
`${ERROR_PREFIX}${where} contains a wildcard somewhere other than a trailing "/**". The only two forms are an exact path ("/pricing") and a subtree ("/api/reports/**").`
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
if (route.includes("//")) {
|
|
948
|
+
throw new Error(`${ERROR_PREFIX}${where} contains an empty path segment.`);
|
|
949
|
+
}
|
|
950
|
+
const invalid = [...new Set(withoutSubtree)].filter(
|
|
951
|
+
(char) => !PATH_PATTERN_LITERAL.test(char)
|
|
952
|
+
);
|
|
953
|
+
if (invalid.length > 0) {
|
|
954
|
+
throw new Error(
|
|
955
|
+
`${ERROR_PREFIX}${where} contains ${invalid.map((char) => JSON.stringify(char)).join(", ")}, which a CloudFront behavior path pattern cannot contain (allowed: A-Z a-z 0-9 _ - . $ / ~ " ' @ : + &). Escaping it with "?" would also match other routes of the same length. Use a subtree on a parent segment whose name is plain ASCII instead: "/intl/**" owns "/intl/\xFCber".`
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
function assignRoutesToGroups(groups, entries, options) {
|
|
960
|
+
validateFunctionGroups(groups);
|
|
961
|
+
const basePath = basePathPrefix(options.basePath);
|
|
962
|
+
const edgeBase = {
|
|
963
|
+
basePath,
|
|
964
|
+
buildId: options.buildId,
|
|
965
|
+
trailingSlash: options.trailingSlash ?? false
|
|
966
|
+
};
|
|
967
|
+
const patterns = edgeBehaviors(
|
|
968
|
+
groups,
|
|
969
|
+
{},
|
|
970
|
+
{ ...edgeBase, dataRouteGroups: /* @__PURE__ */ new Set() }
|
|
971
|
+
);
|
|
972
|
+
const matchedPatterns = /* @__PURE__ */ new Set();
|
|
973
|
+
const groupOfEntrypoint = /* @__PURE__ */ new Map();
|
|
974
|
+
const winningIndexOfEntrypoint = /* @__PURE__ */ new Map();
|
|
975
|
+
for (const entry of entries) {
|
|
976
|
+
let winner;
|
|
977
|
+
for (const [index, pattern] of patterns.entries()) {
|
|
978
|
+
if (!pattern.regex.test(entry.template)) {
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
matchedPatterns.add(`${pattern.group}\0${pattern.route}`);
|
|
982
|
+
winner ??= index;
|
|
983
|
+
}
|
|
984
|
+
if (winner === void 0 || isInterceptionTemplate(entry.template)) {
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
const incumbent = winningIndexOfEntrypoint.get(entry.entrypointId);
|
|
988
|
+
if (incumbent === void 0 || winner < incumbent) {
|
|
989
|
+
winningIndexOfEntrypoint.set(entry.entrypointId, winner);
|
|
990
|
+
groupOfEntrypoint.set(entry.entrypointId, patterns[winner].group);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
for (const group of groups) {
|
|
994
|
+
for (const route of group.routes) {
|
|
995
|
+
if (!matchedPatterns.has(`${group.name}\0${route}`)) {
|
|
996
|
+
throw new Error(
|
|
997
|
+
`${ERROR_PREFIX}Group "${group.name}" pattern "${route}" matches no route in this build. ` + optionalCatchAllParentHint(route, entries, basePath) + `Patterns match route templates as Next.js declares them (e.g. "/blog/[slug]" is matched by "/blog/**"), and prerendered pages and static assets are served from S3 and need no group.`
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
const edge = {
|
|
1003
|
+
...edgeBase,
|
|
1004
|
+
// Only a group that owns a Pages Router page has data URLs to route. Read
|
|
1005
|
+
// before interception moves files: those are App Router, never pages.
|
|
1006
|
+
dataRouteGroups: new Set(
|
|
1007
|
+
entries.filter((entry) => entry.type === "page").map(
|
|
1008
|
+
(entry) => groupOfEntrypoint.get(entry.entrypointId) ?? DEFAULT_FUNCTION_GROUP
|
|
1009
|
+
)
|
|
1010
|
+
)
|
|
1011
|
+
};
|
|
1012
|
+
reassignInterceptingFiles(
|
|
1013
|
+
entries,
|
|
1014
|
+
groupOfEntrypoint,
|
|
1015
|
+
edgeBehaviors(
|
|
1016
|
+
groups,
|
|
1017
|
+
templatesByGroup(groups, entries, groupOfEntrypoint),
|
|
1018
|
+
edge
|
|
1019
|
+
),
|
|
1020
|
+
edge
|
|
1021
|
+
);
|
|
1022
|
+
const assigned = templatesByGroup(groups, entries, groupOfEntrypoint);
|
|
1023
|
+
const behaviors = edgeBehaviors(groups, assigned, edge);
|
|
1024
|
+
const router = {
|
|
1025
|
+
fileOfTemplate: fileOfTemplate(entries),
|
|
1026
|
+
routing: options.routing
|
|
1027
|
+
};
|
|
1028
|
+
assertEdgeReachesEveryFile(
|
|
1029
|
+
entries,
|
|
1030
|
+
groupOfEntrypoint,
|
|
1031
|
+
behaviors,
|
|
1032
|
+
edge,
|
|
1033
|
+
router
|
|
1034
|
+
);
|
|
1035
|
+
assertRewritesStayInGroup(groupOfEntrypoint, behaviors, router);
|
|
1036
|
+
for (const key of Object.keys(assigned)) {
|
|
1037
|
+
assigned[key] = [...assigned[key]].sort();
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
templates: assigned,
|
|
1041
|
+
behaviors: behaviors.map(({ group, route, pattern }) => ({
|
|
1042
|
+
group,
|
|
1043
|
+
route,
|
|
1044
|
+
pattern
|
|
1045
|
+
}))
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
function routedPatterns(routes, ownedTemplates, basePath) {
|
|
1049
|
+
const result = [...routes];
|
|
1050
|
+
for (const route of routes) {
|
|
1051
|
+
if (!route.endsWith(SUBTREE_SUFFIX)) {
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
const parent = route.slice(0, -SUBTREE_SUFFIX.length);
|
|
1055
|
+
const prefix = `${prefixBasePath(parent, basePath)}/`;
|
|
1056
|
+
const ownsOptionalCatchAll = ownedTemplates.some(
|
|
1057
|
+
(template) => template.startsWith(prefix) && OPTIONAL_CATCH_ALL_SEGMENT.test(template.slice(prefix.length))
|
|
1058
|
+
);
|
|
1059
|
+
if (ownsOptionalCatchAll && !result.includes(parent)) {
|
|
1060
|
+
result.push(parent);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
return result;
|
|
1064
|
+
}
|
|
1065
|
+
function pathPatternsFor(route, options) {
|
|
1066
|
+
const isSubtree = route.endsWith(SUBTREE_SUFFIX);
|
|
1067
|
+
const base = isSubtree ? route.slice(0, -SUBTREE_SUFFIX.length) : route;
|
|
1068
|
+
const bare = base.replace(/^\//, "");
|
|
1069
|
+
const patterns = [isSubtree ? `${bare}/*` : bare];
|
|
1070
|
+
if (!isSubtree && options.trailingSlash && bare) {
|
|
1071
|
+
patterns.push(`${bare}/`);
|
|
1072
|
+
}
|
|
1073
|
+
if (options.hasDataRoutes) {
|
|
1074
|
+
patterns.push(
|
|
1075
|
+
isSubtree ? `_next/data/${dataBuildId(options.buildId)}/${bare}/*` : `_next/data/${dataBuildId(options.buildId)}/${bare}.json`
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
return patterns;
|
|
1079
|
+
}
|
|
1080
|
+
function behaviorSpecificity(pattern) {
|
|
1081
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
1082
|
+
const firstWildcard = segments.findIndex((segment) => segment.includes("*"));
|
|
1083
|
+
const literalDepth = firstWildcard === -1 ? segments.length : firstWildcard;
|
|
1084
|
+
const exact = firstWildcard === -1 ? 1 : 0;
|
|
1085
|
+
return (literalDepth * 2 + exact) * 1e6 + segments.length * 1e4 + pattern.length;
|
|
1086
|
+
}
|
|
1087
|
+
function dataBuildId(buildId) {
|
|
1088
|
+
if (!buildId) {
|
|
1089
|
+
throw new Error(
|
|
1090
|
+
`${ERROR_PREFIX}Routing a Pages Router app's "/_next/data/<buildId>/\u2026" URLs needs its build ID, and none was passed. This is a cdk-nextjs bug: the build ID comes from the adapter manifest.`
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
const invalid = [...new Set(buildId)].filter(
|
|
1094
|
+
(char) => !PATH_PATTERN_LITERAL.test(char) || char === "/"
|
|
1095
|
+
);
|
|
1096
|
+
if (invalid.length > 0) {
|
|
1097
|
+
throw new Error(
|
|
1098
|
+
`${ERROR_PREFIX}The build ID "${buildId}" contains ${invalid.map((char) => JSON.stringify(char)).join(", ")}, which a CloudFront path pattern cannot hold, so its "/_next/data" URLs cannot be routed to a group. Make \`generateBuildId\` return only A-Z a-z 0-9 _ - . characters.`
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
return buildId;
|
|
1102
|
+
}
|
|
1103
|
+
function optionalCatchAllParentHint(route, entries, basePath) {
|
|
1104
|
+
if (route.endsWith(SUBTREE_SUFFIX)) {
|
|
1105
|
+
return "";
|
|
1106
|
+
}
|
|
1107
|
+
const prefix = `${prefixBasePath(route, basePath)}/`;
|
|
1108
|
+
const catchAll = entries.find(
|
|
1109
|
+
(entry) => entry.template.startsWith(prefix) && OPTIONAL_CATCH_ALL_SEGMENT.test(entry.template.slice(prefix.length))
|
|
1110
|
+
);
|
|
1111
|
+
if (!catchAll) {
|
|
1112
|
+
return "";
|
|
1113
|
+
}
|
|
1114
|
+
return `"${route}" is served by the optional catch-all "${catchAll.template}", whose subtree pattern "${route}${SUBTREE_SUFFIX}" routes "${route}" as well \u2014 use that instead. `;
|
|
1115
|
+
}
|
|
1116
|
+
var OPTIONAL_CATCH_ALL_SEGMENT = /^\[\[\.\.\.[^\]/]+\]\]$/;
|
|
1117
|
+
var INTERCEPTION_MARKERS = ["(..)(..)", "(.)", "(..)", "(...)"];
|
|
1118
|
+
function interceptionMarkerOf(path) {
|
|
1119
|
+
for (const segment of path.split("/")) {
|
|
1120
|
+
const marker = INTERCEPTION_MARKERS.find((it) => segment.startsWith(it));
|
|
1121
|
+
if (marker) {
|
|
1122
|
+
return marker;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
return void 0;
|
|
1126
|
+
}
|
|
1127
|
+
function isInterceptionTemplate(template) {
|
|
1128
|
+
return interceptionMarkerOf(template) !== void 0;
|
|
1129
|
+
}
|
|
1130
|
+
function interceptedRoute(path) {
|
|
1131
|
+
const marker = interceptionMarkerOf(path);
|
|
1132
|
+
if (marker === void 0) {
|
|
1133
|
+
return void 0;
|
|
1134
|
+
}
|
|
1135
|
+
const at = path.indexOf(marker);
|
|
1136
|
+
const intercepting = path.slice(0, at).split("/").filter(Boolean);
|
|
1137
|
+
const rest = path.slice(at + marker.length).split("/").filter(Boolean);
|
|
1138
|
+
const parent = marker === "(.)" ? intercepting : marker === "(..)" ? intercepting.slice(0, -1) : marker === "(..)(..)" ? intercepting.slice(0, -2) : [];
|
|
1139
|
+
return `/${[...parent, ...rest].join("/")}`;
|
|
1140
|
+
}
|
|
1141
|
+
function isInternalTemplate(path) {
|
|
1142
|
+
return path.endsWith(".rsc") || path.includes(".segments/");
|
|
1143
|
+
}
|
|
1144
|
+
function edgeBehaviors(groups, assigned, edge) {
|
|
1145
|
+
return groups.flatMap(
|
|
1146
|
+
(group) => routedPatterns(
|
|
1147
|
+
group.routes,
|
|
1148
|
+
assigned[group.name] ?? [],
|
|
1149
|
+
edge.basePath
|
|
1150
|
+
).flatMap(
|
|
1151
|
+
(route) => pathPatternsFor(route, {
|
|
1152
|
+
trailingSlash: edge.trailingSlash,
|
|
1153
|
+
buildId: edge.buildId,
|
|
1154
|
+
hasDataRoutes: edge.dataRouteGroups.has(group.name)
|
|
1155
|
+
}).map((pattern) => ({
|
|
1156
|
+
group: group.name,
|
|
1157
|
+
route,
|
|
1158
|
+
pattern,
|
|
1159
|
+
regex: cloudFrontPatternRegex(`${edge.basePath}/${pattern}`)
|
|
1160
|
+
}))
|
|
1161
|
+
)
|
|
1162
|
+
).sort(
|
|
1163
|
+
(a, b) => behaviorSpecificity(b.pattern) - behaviorSpecificity(a.pattern)
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
function cloudFrontPatternRegex(pattern) {
|
|
1167
|
+
const source = [...pattern].map(
|
|
1168
|
+
(char) => char === "*" ? ".*" : char === "?" ? "." : char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
1169
|
+
).join("");
|
|
1170
|
+
return new RegExp(`^${source}$`);
|
|
1171
|
+
}
|
|
1172
|
+
function edgeBehaviorFor(behaviors, url) {
|
|
1173
|
+
return behaviors.find((behavior) => behavior.regex.test(url));
|
|
1174
|
+
}
|
|
1175
|
+
function templatesByGroup(groups, entries, groupOfEntrypoint) {
|
|
1176
|
+
const assigned = {
|
|
1177
|
+
[DEFAULT_FUNCTION_GROUP]: []
|
|
1178
|
+
};
|
|
1179
|
+
for (const group of groups) {
|
|
1180
|
+
assigned[group.name] = [];
|
|
1181
|
+
}
|
|
1182
|
+
for (const entry of entries) {
|
|
1183
|
+
const group = groupOfEntrypoint.get(entry.entrypointId) ?? DEFAULT_FUNCTION_GROUP;
|
|
1184
|
+
assigned[group].push(entry.template);
|
|
1185
|
+
}
|
|
1186
|
+
return assigned;
|
|
1187
|
+
}
|
|
1188
|
+
function reassignInterceptingFiles(entries, groupOfEntrypoint, behaviors, edge) {
|
|
1189
|
+
for (const entry of entries) {
|
|
1190
|
+
const path = stripBasePath(entry.template, edge.basePath);
|
|
1191
|
+
if (path === void 0 || isInternalTemplate(path)) {
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
const target = interceptedRoute(path);
|
|
1195
|
+
if (target === void 0) {
|
|
1196
|
+
continue;
|
|
1197
|
+
}
|
|
1198
|
+
const url = withBasePath(target, edge.basePath);
|
|
1199
|
+
const group = edgeBehaviorFor(behaviors, url)?.group ?? DEFAULT_FUNCTION_GROUP;
|
|
1200
|
+
const intercepted = templateRegex(url);
|
|
1201
|
+
const split = behaviors.find(
|
|
1202
|
+
(behavior) => behavior.group !== group && !behavior.pattern.startsWith("_next/data/") && sampleUrl(behavior.route, edge.basePath).some(
|
|
1203
|
+
(it) => intercepted.test(it)
|
|
1204
|
+
)
|
|
1205
|
+
);
|
|
1206
|
+
if (split) {
|
|
1207
|
+
throw new Error(
|
|
1208
|
+
`${ERROR_PREFIX}"${entry.entrypointId}" is the interception route "${entry.template}", which Next.js reaches by rewriting a request for "${url}" inside whichever function CloudFront sent it to \u2014 so it is packaged with the group that owns "${url}". That URL space is split, though: most of it reaches ${groupLabel(group)}, but group "${split.group}"'s pattern "${split.route}" claims part of it, and one file cannot be in both. Route all of "${target}" to one group.`
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
groupOfEntrypoint.set(entry.entrypointId, group);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
function sampleUrl(route, basePath) {
|
|
1215
|
+
return route.endsWith(SUBTREE_SUFFIX) ? [`${basePath}${route.slice(0, -SUBTREE_SUFFIX.length)}/_`] : [`${basePath}${route}`];
|
|
1216
|
+
}
|
|
1217
|
+
function templateRegex(template) {
|
|
1218
|
+
const source = template.split("/").filter(Boolean).map(
|
|
1219
|
+
(segment) => [
|
|
1220
|
+
`/${segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`,
|
|
1221
|
+
"/[^/]+",
|
|
1222
|
+
"/.+",
|
|
1223
|
+
"(?:/.+)?"
|
|
1224
|
+
][segmentKind(segment)]
|
|
1225
|
+
).join("");
|
|
1226
|
+
return new RegExp(`^${source || "/"}$`);
|
|
1227
|
+
}
|
|
1228
|
+
function assertEdgeReachesEveryFile(entries, groupOfEntrypoint, behaviors, edge, router) {
|
|
1229
|
+
const files = /* @__PURE__ */ new Map();
|
|
1230
|
+
for (const entry of entries) {
|
|
1231
|
+
const list = files.get(entry.entrypointId) ?? [];
|
|
1232
|
+
list.push(entry);
|
|
1233
|
+
files.set(entry.entrypointId, list);
|
|
1234
|
+
}
|
|
1235
|
+
const deepest = Math.max(
|
|
1236
|
+
0,
|
|
1237
|
+
...entries.map((entry) => {
|
|
1238
|
+
const path = stripBasePath(entry.template, edge.basePath);
|
|
1239
|
+
return path !== void 0 && isRoutablePath(path) ? path.split("/").filter(Boolean).length : 0;
|
|
1240
|
+
})
|
|
1241
|
+
) + 1;
|
|
1242
|
+
for (const [file, fileEntries] of files) {
|
|
1243
|
+
const owner = groupOfEntrypoint.get(file) ?? DEFAULT_FUNCTION_GROUP;
|
|
1244
|
+
const misrouted = [];
|
|
1245
|
+
for (const entry of fileEntries) {
|
|
1246
|
+
for (const url of servedUrls(entry.template, entry.type, edge)) {
|
|
1247
|
+
const behavior = edgeBehaviorFor(behaviors, url);
|
|
1248
|
+
const group = behavior?.group ?? DEFAULT_FUNCTION_GROUP;
|
|
1249
|
+
if (group !== owner && !misrouted.some((it) => it.url === url)) {
|
|
1250
|
+
misrouted.push({ url, group, route: behavior?.route });
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
for (const it of capturedDynamicUrls(
|
|
1254
|
+
entry,
|
|
1255
|
+
owner,
|
|
1256
|
+
deepest,
|
|
1257
|
+
behaviors,
|
|
1258
|
+
edge,
|
|
1259
|
+
router
|
|
1260
|
+
)) {
|
|
1261
|
+
if (!misrouted.some((known) => known.url === it.url)) {
|
|
1262
|
+
misrouted.push(it);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
if (misrouted.length > 0) {
|
|
1267
|
+
throw new Error(misroutedFileMessage(file, owner, misrouted, edge));
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
var ANY_SEGMENT = "_";
|
|
1272
|
+
function isRoutablePath(path) {
|
|
1273
|
+
return !isInternalTemplate(path) && !isInterceptionTemplate(path) && !path.startsWith("/_next/data/");
|
|
1274
|
+
}
|
|
1275
|
+
function segmentKind(segment) {
|
|
1276
|
+
if (OPTIONAL_CATCH_ALL_SEGMENT.test(segment)) {
|
|
1277
|
+
return 3;
|
|
1278
|
+
}
|
|
1279
|
+
if (/^\[\.\.\.[^\]]+\]$/.test(segment)) {
|
|
1280
|
+
return 2;
|
|
1281
|
+
}
|
|
1282
|
+
return /^\[[^\]]+\]$/.test(segment) ? 1 : 0;
|
|
1283
|
+
}
|
|
1284
|
+
function capturedDynamicUrls(entry, owner, deepest, behaviors, edge, router) {
|
|
1285
|
+
const path = stripBasePath(entry.template, edge.basePath);
|
|
1286
|
+
if (path === void 0 || !path.includes("[") || ERROR_PAGE_SUFFIXES.includes(path) || !isRoutablePath(path)) {
|
|
1287
|
+
return [];
|
|
1288
|
+
}
|
|
1289
|
+
const segments = path.split("/").filter(Boolean);
|
|
1290
|
+
const samples = /* @__PURE__ */ new Set();
|
|
1291
|
+
for (const behavior of behaviors) {
|
|
1292
|
+
if (behavior.group === owner || behavior.pattern.startsWith("_next/data/")) {
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
const bare = behavior.pattern.replace(/\/$/, "");
|
|
1296
|
+
const isSubtree = bare.endsWith("/*");
|
|
1297
|
+
const prefix = (isSubtree ? bare.slice(0, -2) : bare).split("/").filter(Boolean);
|
|
1298
|
+
for (const sample of sampleTemplatePaths(
|
|
1299
|
+
segments,
|
|
1300
|
+
prefix,
|
|
1301
|
+
isSubtree,
|
|
1302
|
+
Math.max(deepest, prefix.length + 1)
|
|
1303
|
+
)) {
|
|
1304
|
+
samples.add(sample);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
const misrouted = [];
|
|
1308
|
+
const check = (url, resolved) => {
|
|
1309
|
+
const behavior = edgeBehaviorFor(behaviors, url);
|
|
1310
|
+
const group = behavior?.group ?? DEFAULT_FUNCTION_GROUP;
|
|
1311
|
+
if (group === owner || misrouted.some((it) => it.url === url)) {
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
if (resolveFile(resolved, router) === entry.entrypointId) {
|
|
1315
|
+
misrouted.push({
|
|
1316
|
+
url,
|
|
1317
|
+
group,
|
|
1318
|
+
route: behavior?.route,
|
|
1319
|
+
servedAs: entry.template
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
for (const sample of samples) {
|
|
1324
|
+
for (const { url, resolved } of urlVariants(sample, entry.type, edge)) {
|
|
1325
|
+
check(url, resolved);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return misrouted;
|
|
1329
|
+
}
|
|
1330
|
+
function sampleTemplatePaths(template, prefix, isSubtree, maxDepth) {
|
|
1331
|
+
if (!isSubtree) {
|
|
1332
|
+
const path = `/${prefix.join("/")}`;
|
|
1333
|
+
return templateRegex(`/${template.join("/")}`).test(path) ? [path] : [];
|
|
1334
|
+
}
|
|
1335
|
+
const valueAt = (at) => at < prefix.length ? prefix[at] : ANY_SEGMENT;
|
|
1336
|
+
const results = [];
|
|
1337
|
+
const walk = (index, acc) => {
|
|
1338
|
+
if (index === template.length) {
|
|
1339
|
+
if (acc.length >= prefix.length) {
|
|
1340
|
+
results.push(`/${acc.join("/")}`);
|
|
1341
|
+
}
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
const segment = template[index];
|
|
1345
|
+
const at = acc.length;
|
|
1346
|
+
const kind = segmentKind(segment);
|
|
1347
|
+
if (kind === 0) {
|
|
1348
|
+
if (at >= prefix.length || prefix[at] === segment) {
|
|
1349
|
+
walk(index + 1, [...acc, segment]);
|
|
1350
|
+
}
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
if (kind === 1) {
|
|
1354
|
+
walk(index + 1, [...acc, valueAt(at)]);
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
for (let count = kind === 2 ? 1 : 0; at + count <= maxDepth; count++) {
|
|
1358
|
+
const taken = Array.from({ length: count }, (_, i) => valueAt(at + i));
|
|
1359
|
+
walk(index + 1, [...acc, ...taken]);
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
walk(0, []);
|
|
1363
|
+
return results;
|
|
1364
|
+
}
|
|
1365
|
+
function servedUrls(template, type, edge) {
|
|
1366
|
+
const path = stripBasePath(template, edge.basePath);
|
|
1367
|
+
if (path === void 0 || ERROR_PAGE_SUFFIXES.includes(path) || isInternalTemplate(path) || isInterceptionTemplate(path)) {
|
|
1368
|
+
return [];
|
|
1369
|
+
}
|
|
1370
|
+
if (path.startsWith("/_next/data/")) {
|
|
1371
|
+
return [template];
|
|
1372
|
+
}
|
|
1373
|
+
const paths = [path];
|
|
1374
|
+
const optional = /^(.*)\/(\[\[\.\.\.[^\]/]+\]\])$/.exec(path);
|
|
1375
|
+
if (optional) {
|
|
1376
|
+
paths.push(optional[1] || "/");
|
|
1377
|
+
}
|
|
1378
|
+
return paths.flatMap(
|
|
1379
|
+
(it) => urlVariants(it, type, edge).map(({ url }) => url)
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
function urlVariants(path, type, edge) {
|
|
1383
|
+
const url = withBasePath(path, edge.basePath);
|
|
1384
|
+
const variants = [{ url, resolved: url }];
|
|
1385
|
+
if (edge.trailingSlash && path !== "/") {
|
|
1386
|
+
variants.push({ url: `${url}/`, resolved: url });
|
|
1387
|
+
}
|
|
1388
|
+
if (type === "page" && edge.buildId) {
|
|
1389
|
+
const data = `${edge.basePath}/_next/data/${edge.buildId}${path === "/" ? "/index" : path}.json`;
|
|
1390
|
+
variants.push({ url: data, resolved: data });
|
|
1391
|
+
}
|
|
1392
|
+
return variants;
|
|
1393
|
+
}
|
|
1394
|
+
function misroutedFileMessage(file, owner, misrouted, edge) {
|
|
1395
|
+
const where = misrouted.map(
|
|
1396
|
+
(it) => `"${it.url}" to ${groupLabel(it.group)}` + (it.route ? ` (its pattern "${it.route}")` : "")
|
|
1397
|
+
).join(", ");
|
|
1398
|
+
const head = `${ERROR_PREFIX}"${file}" is packaged into ${groupLabel(owner)}, but CloudFront would send ${where}, whose function does not have it: a 404 on every such request. Every URL one file serves has to reach the group that holds the file.`;
|
|
1399
|
+
const captured = misrouted.filter((it) => it.servedAs !== void 0);
|
|
1400
|
+
if (captured.length > 0) {
|
|
1401
|
+
const templates = [...new Set(captured.map((it) => `"${it.servedAs}"`))];
|
|
1402
|
+
return `${head} Those URLs are part of the dynamic route ${templates.join(", ")} ("${ANY_SEGMENT}" stands for any segment value): the pattern captures them at the edge, and no route packaged into the capturing group is more specific, so Next.js still resolves them to this file. Give that group a route that answers them (a catch-all under its pattern, say), or narrow the pattern.`;
|
|
1403
|
+
}
|
|
1404
|
+
if (owner === DEFAULT_FUNCTION_GROUP) {
|
|
1405
|
+
return `${head} A pattern claims a URL of a file that no pattern moved: narrow it, or give the group the file's other routes too.`;
|
|
1406
|
+
}
|
|
1407
|
+
const unroutable = misrouted.filter(
|
|
1408
|
+
(it) => suggestedPattern(it.url, edge) === void 0
|
|
1409
|
+
);
|
|
1410
|
+
if (unroutable.length > 0) {
|
|
1411
|
+
return `${head} ${unroutable.map((it) => `"${it.url}"`).join(", ")} cannot be routed to any group \u2014 CloudFront has no pattern for the home page alone, and a dynamic first segment would need "/**" \u2014 so this file can only be served by the "${DEFAULT_FUNCTION_GROUP}" group. Remove the pattern that moved it. (A Pages Router home page is also "/index": one file.)`;
|
|
1412
|
+
}
|
|
1413
|
+
const suggestions = [
|
|
1414
|
+
...new Set(misrouted.map((it) => suggestedPattern(it.url, edge)))
|
|
1415
|
+
];
|
|
1416
|
+
return `${head} Add ${suggestions.map((it) => `"${it}"`).join(", ")} to group "${owner}"'s routes.`;
|
|
1417
|
+
}
|
|
1418
|
+
function suggestedPattern(url, edge) {
|
|
1419
|
+
let path = stripBasePath(url, edge.basePath) ?? url;
|
|
1420
|
+
const dataPrefix = `/_next/data/${edge.buildId}/`;
|
|
1421
|
+
if (path.startsWith(dataPrefix) && path.endsWith(".json")) {
|
|
1422
|
+
path = `/${path.slice(dataPrefix.length, -".json".length)}`;
|
|
1423
|
+
path = path === "/index" ? "/" : path;
|
|
1424
|
+
}
|
|
1425
|
+
if (path.length > 1 && path.endsWith("/")) {
|
|
1426
|
+
path = path.slice(0, -1);
|
|
1427
|
+
}
|
|
1428
|
+
const segments = path.split("/").filter(Boolean);
|
|
1429
|
+
const firstDynamic = segments.findIndex((segment) => segment.startsWith("["));
|
|
1430
|
+
if (segments.length === 0 || firstDynamic === 0) {
|
|
1431
|
+
return void 0;
|
|
1432
|
+
}
|
|
1433
|
+
return firstDynamic === -1 ? path : `/${segments.slice(0, firstDynamic).join("/")}${SUBTREE_SUFFIX}`;
|
|
1434
|
+
}
|
|
1435
|
+
function assertRewritesStayInGroup(groupOfEntrypoint, behaviors, router) {
|
|
1436
|
+
const { routing } = router;
|
|
1437
|
+
const phases = [
|
|
1438
|
+
[routing.beforeFiles, false],
|
|
1439
|
+
// `afterFiles` and `fallback` only apply when no file matched the URL, so a
|
|
1440
|
+
// source that is itself a route never reaches the destination.
|
|
1441
|
+
[routing.afterFiles, true],
|
|
1442
|
+
[routing.fallback, true]
|
|
1443
|
+
];
|
|
1444
|
+
for (const [rules, yieldsToFiles] of phases) {
|
|
1445
|
+
for (const rule of rules ?? []) {
|
|
1446
|
+
const destination = literalDestination(rule.destination);
|
|
1447
|
+
if (destination === void 0 || isInterceptionTemplate(destination)) {
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
const file = resolveFile(destination, router);
|
|
1451
|
+
if (file === void 0) {
|
|
1452
|
+
continue;
|
|
1453
|
+
}
|
|
1454
|
+
const target = groupOfEntrypoint.get(file) ?? DEFAULT_FUNCTION_GROUP;
|
|
1455
|
+
for (const url of rewriteSourceUrls(rule.source)) {
|
|
1456
|
+
if (yieldsToFiles && router.fileOfTemplate.has(url)) {
|
|
1457
|
+
continue;
|
|
1458
|
+
}
|
|
1459
|
+
const behavior = edgeBehaviorFor(behaviors, url);
|
|
1460
|
+
const group = behavior?.group ?? DEFAULT_FUNCTION_GROUP;
|
|
1461
|
+
if (group === target) {
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
throw new Error(
|
|
1465
|
+
`${ERROR_PREFIX}The next.config rewrite from "${rule.source}" to "${rule.destination}" crosses groups: CloudFront sends "${url}" to ${groupLabel(group)}` + (behavior ? ` (its pattern "${behavior.route}")` : "") + `, and Next.js then serves it with "${file}", which is packaged into ${groupLabel(target)}. A rewrite is applied inside the function that received the request, so its source and its destination have to be routed to the same group.`
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
function literalDestination(destination) {
|
|
1472
|
+
if (!destination?.startsWith("/")) {
|
|
1473
|
+
return void 0;
|
|
1474
|
+
}
|
|
1475
|
+
const pathname = destination.split(/[?#]/)[0];
|
|
1476
|
+
if (/[$:]/.test(pathname)) {
|
|
1477
|
+
return void 0;
|
|
1478
|
+
}
|
|
1479
|
+
return pathname.length > 1 ? pathname.replace(/\/$/, "") : pathname;
|
|
1480
|
+
}
|
|
1481
|
+
function fileOfTemplate(entries) {
|
|
1482
|
+
return new Map(
|
|
1483
|
+
entries.filter((entry) => !isInternalTemplate(entry.template)).map((entry) => [entry.template, entry.entrypointId])
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
function resolveFile(pathname, { fileOfTemplate: files, routing }) {
|
|
1487
|
+
const exact = files.get(pathname);
|
|
1488
|
+
if (exact !== void 0) {
|
|
1489
|
+
return exact;
|
|
1490
|
+
}
|
|
1491
|
+
for (const route of routing.dynamicRoutes ?? []) {
|
|
1492
|
+
if (new RegExp(route.sourceRegex, "i").test(pathname)) {
|
|
1493
|
+
const template = route.destination?.split("?")[0];
|
|
1494
|
+
return template === void 0 ? void 0 : files.get(template);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
return void 0;
|
|
1498
|
+
}
|
|
1499
|
+
function rewriteSourceUrls(source) {
|
|
1500
|
+
if (!source?.startsWith("/")) {
|
|
1501
|
+
return [];
|
|
1502
|
+
}
|
|
1503
|
+
const segments = [];
|
|
1504
|
+
let optionalFrom;
|
|
1505
|
+
for (const segment of source.split("/").slice(1)) {
|
|
1506
|
+
if (!/[:()*?+{}[\]]/.test(segment)) {
|
|
1507
|
+
segments.push(segment);
|
|
1508
|
+
continue;
|
|
1509
|
+
}
|
|
1510
|
+
optionalFrom = /^:\w+[*?]$/.test(segment) ? segments.length : void 0;
|
|
1511
|
+
segments.push(`[${/:(\w+)/.exec(segment)?.[1] ?? "param"}]`);
|
|
1512
|
+
}
|
|
1513
|
+
const urls = [`/${segments.join("/")}`];
|
|
1514
|
+
if (optionalFrom !== void 0) {
|
|
1515
|
+
urls.push(`/${segments.slice(0, optionalFrom).join("/")}`);
|
|
1516
|
+
}
|
|
1517
|
+
return urls;
|
|
1518
|
+
}
|
|
1519
|
+
function groupLabel(group) {
|
|
1520
|
+
return group === DEFAULT_FUNCTION_GROUP ? `the "${DEFAULT_FUNCTION_GROUP}" group` : `group "${group}"`;
|
|
1521
|
+
}
|
|
1522
|
+
function prefixBasePath(route, basePath) {
|
|
1523
|
+
return `${basePathPrefix(basePath)}${route}`;
|
|
1524
|
+
}
|
|
1525
|
+
function stripBasePath(template, basePath) {
|
|
1526
|
+
if (!basePath) {
|
|
1527
|
+
return template;
|
|
1528
|
+
}
|
|
1529
|
+
return hasPathPrefix(template, basePath) ? template.slice(basePath.length) || "/" : void 0;
|
|
1530
|
+
}
|
|
1531
|
+
function withBasePath(path, basePath) {
|
|
1532
|
+
return path === "/" ? basePath || "/" : `${basePath}${path}`;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
// src/adapter/build-outputs.ts
|
|
1536
|
+
var ENV_FILES = [".env", ".env.production"];
|
|
1537
|
+
var RUNTIME_NEXT_MODULES = ["next/dist/server/serve-static.js"];
|
|
1538
|
+
var IMAGE_NEXT_MODULES = [
|
|
1539
|
+
"next/dist/server/config-shared.js",
|
|
1540
|
+
"next/dist/shared/lib/image-config.js",
|
|
1541
|
+
"next/dist/server/image-optimizer.js"
|
|
1542
|
+
];
|
|
1543
|
+
var NEXT_FILE_TRACER = "next/dist/compiled/@vercel/nft";
|
|
1544
|
+
async function writeBuildOutputs(ctx, options = {}) {
|
|
1545
|
+
const adapterDir = join(ctx.distDir, ADAPTER_DIR_NAME);
|
|
1546
|
+
const manifestPath = join(adapterDir, MANIFEST_FILE_NAME);
|
|
1547
|
+
const { manifest, groups } = buildAdapterManifest(ctx, options);
|
|
1548
|
+
const runtimeClosure = /* @__PURE__ */ new Map();
|
|
1549
|
+
const imageClosure = /* @__PURE__ */ new Map();
|
|
1550
|
+
await addRuntimeNextClosure(ctx, runtimeClosure, imageClosure);
|
|
1551
|
+
addRequiredServerFiles(ctx, runtimeClosure);
|
|
1552
|
+
await rm(adapterDir, { recursive: true, force: true });
|
|
1553
|
+
const stagedGroups = [];
|
|
1554
|
+
for (const group of groups) {
|
|
1555
|
+
const groupStaging = merged(
|
|
1556
|
+
merged(group.staging, runtimeClosure),
|
|
1557
|
+
group.name === DEFAULT_FUNCTION_GROUP ? imageClosure : /* @__PURE__ */ new Map()
|
|
1558
|
+
);
|
|
1559
|
+
const path = join(adapterDir, ...group.dirName.split("/"));
|
|
1560
|
+
await mkdir(path, { recursive: true });
|
|
1561
|
+
await stageFiles(groupStaging, path);
|
|
1562
|
+
stagedGroups.push({ name: group.name, path, fileCount: groupStaging.size });
|
|
1563
|
+
}
|
|
1564
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
1565
|
+
return {
|
|
1566
|
+
manifest,
|
|
1567
|
+
groups,
|
|
1568
|
+
adapterDir,
|
|
1569
|
+
manifestPath,
|
|
1570
|
+
stagedGroups
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
function merged(base, additions) {
|
|
1574
|
+
const result = new Map(base);
|
|
1575
|
+
for (const [key, source] of additions) {
|
|
1576
|
+
if (!result.has(key)) {
|
|
1577
|
+
result.set(key, source);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
return result;
|
|
1581
|
+
}
|
|
1582
|
+
function buildAdapterManifest(ctx, options = {}) {
|
|
1583
|
+
const { outputs, repoRoot } = ctx;
|
|
1584
|
+
const basePath = ctx.config.basePath || "";
|
|
1585
|
+
assertBuildCwd(ctx, options.buildCwd ?? process.cwd());
|
|
1586
|
+
const invocable = [
|
|
1587
|
+
...outputs.pages,
|
|
1588
|
+
...outputs.pagesApi,
|
|
1589
|
+
...outputs.appPages,
|
|
1590
|
+
...outputs.appRoutes,
|
|
1591
|
+
...outputs.middleware ? [outputs.middleware] : []
|
|
1592
|
+
];
|
|
1593
|
+
assertNodeRuntimes(invocable, outputs.middleware);
|
|
1594
|
+
warnOnDroppedRouteConfig(invocable);
|
|
1595
|
+
const staticFiles = collectStaticFiles(ctx);
|
|
1596
|
+
const entrypoints = {};
|
|
1597
|
+
for (const { outputs: group, type } of [
|
|
1598
|
+
{ outputs: outputs.pages, type: "page" },
|
|
1599
|
+
{ outputs: outputs.pagesApi, type: "page-api" },
|
|
1600
|
+
{ outputs: outputs.appPages, type: "app-page" },
|
|
1601
|
+
{ outputs: outputs.appRoutes, type: "app-route" }
|
|
1602
|
+
]) {
|
|
1603
|
+
for (const output of group) {
|
|
1604
|
+
addEntrypoint(entrypoints, repoRoot, output, type, basePath);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
addPrerenderPathnames(
|
|
1608
|
+
entrypoints,
|
|
1609
|
+
outputs.prerenders,
|
|
1610
|
+
ctx.routing.dynamicRoutes,
|
|
1611
|
+
basePath,
|
|
1612
|
+
`${basePath}/_next/data/${ctx.buildId}/`
|
|
1613
|
+
);
|
|
1614
|
+
const pathnames = sortedUnique([
|
|
1615
|
+
...Object.keys(entrypoints),
|
|
1616
|
+
...Object.keys(staticFiles)
|
|
1617
|
+
]);
|
|
1618
|
+
const functionGroups = options.functionGroups ?? parseFunctionGroupsEnv(process.env[FUNCTION_GROUPS_ENV_VAR]);
|
|
1619
|
+
if (functionGroups) {
|
|
1620
|
+
assertNoI18nSplitting(ctx.config.i18n ?? null);
|
|
1621
|
+
}
|
|
1622
|
+
const assignment = functionGroups ? assignRoutesToGroups(
|
|
1623
|
+
functionGroups,
|
|
1624
|
+
// Keyed on the file, not the output `id`: a Pages Router page and its
|
|
1625
|
+
// `/_next/data/<buildId>/<page>.json` route are separate outputs with
|
|
1626
|
+
// separate ids backed by one file, and only the page's template matches
|
|
1627
|
+
// a group pattern.
|
|
1628
|
+
Object.entries(entrypoints).map(([template, entrypoint]) => ({
|
|
1629
|
+
template,
|
|
1630
|
+
entrypointId: entrypoint.filePath,
|
|
1631
|
+
type: entrypoint.type
|
|
1632
|
+
})),
|
|
1633
|
+
{
|
|
1634
|
+
basePath: ctx.config.basePath || "",
|
|
1635
|
+
buildId: ctx.buildId,
|
|
1636
|
+
trailingSlash: ctx.config.trailingSlash === true,
|
|
1637
|
+
routing: ctx.routing
|
|
1638
|
+
}
|
|
1639
|
+
) : void 0;
|
|
1640
|
+
const manifest = {
|
|
1641
|
+
version: ADAPTER_MANIFEST_VERSION,
|
|
1642
|
+
buildId: ctx.buildId,
|
|
1643
|
+
relativeProjectDir: toPosix(relative(repoRoot, ctx.projectDir)),
|
|
1644
|
+
config: {
|
|
1645
|
+
basePath: ctx.config.basePath || "",
|
|
1646
|
+
trailingSlash: ctx.config.trailingSlash === true,
|
|
1647
|
+
assetPrefix: ctx.config.assetPrefix || "",
|
|
1648
|
+
distDir: toPosix(relative(ctx.projectDir, ctx.distDir)),
|
|
1649
|
+
compress: ctx.config.compress !== false,
|
|
1650
|
+
generateEtags: ctx.config.generateEtags !== false,
|
|
1651
|
+
i18n: ctx.config.i18n ?? null,
|
|
1652
|
+
deploymentId: (ctx.config.deploymentId ?? "").replace(
|
|
1653
|
+
/[^A-Za-z0-9_-]/g,
|
|
1654
|
+
"-"
|
|
1655
|
+
)
|
|
1656
|
+
},
|
|
1657
|
+
routing: ctx.routing,
|
|
1658
|
+
pathnames,
|
|
1659
|
+
entrypoints,
|
|
1660
|
+
middleware: buildMiddleware(repoRoot, outputs.middleware),
|
|
1661
|
+
staticFiles,
|
|
1662
|
+
...assignment ? {
|
|
1663
|
+
groups: assignment.templates,
|
|
1664
|
+
behaviors: assignment.behaviors,
|
|
1665
|
+
functionGroups: functionGroups.map(({ name, routes }) => ({
|
|
1666
|
+
name,
|
|
1667
|
+
routes
|
|
1668
|
+
}))
|
|
1669
|
+
} : {}
|
|
1670
|
+
};
|
|
1671
|
+
const groups = assignment ? Object.entries(assignment.templates).map(([name, templates]) => ({
|
|
1672
|
+
name,
|
|
1673
|
+
dirName: groupStagingDirName(name),
|
|
1674
|
+
staging: collectGroupStagingPlan(
|
|
1675
|
+
ctx,
|
|
1676
|
+
invocable,
|
|
1677
|
+
entrypoints,
|
|
1678
|
+
templates
|
|
1679
|
+
)
|
|
1680
|
+
})) : [
|
|
1681
|
+
{
|
|
1682
|
+
name: DEFAULT_FUNCTION_GROUP,
|
|
1683
|
+
dirName: groupStagingDirName(),
|
|
1684
|
+
staging: servedStagingPlan(ctx, invocable)
|
|
1685
|
+
}
|
|
1686
|
+
];
|
|
1687
|
+
return { manifest, groups };
|
|
1688
|
+
}
|
|
1689
|
+
function collectGroupStagingPlan(ctx, invocable, entrypoints, templates) {
|
|
1690
|
+
const basePath = ctx.config.basePath || "";
|
|
1691
|
+
const ownedEntrypoints = [
|
|
1692
|
+
...templates,
|
|
1693
|
+
...ERROR_PAGE_SUFFIXES.map((suffix) => `${basePath}${suffix}`)
|
|
1694
|
+
].map((template) => entrypoints[template]).filter((entry) => entry !== void 0);
|
|
1695
|
+
const ownedFiles = new Set(ownedEntrypoints.map((entry) => entry.filePath));
|
|
1696
|
+
const middleware = ctx.outputs.middleware;
|
|
1697
|
+
const owned = invocable.filter(
|
|
1698
|
+
(output) => ownedFiles.has(toPosix(relative(ctx.repoRoot, output.filePath))) || output === middleware
|
|
1699
|
+
);
|
|
1700
|
+
return servedStagingPlan(ctx, owned);
|
|
1701
|
+
}
|
|
1702
|
+
function servedStagingPlan(ctx, outputs) {
|
|
1703
|
+
const staging = collectStagingPlan(ctx, outputs);
|
|
1704
|
+
stageServedStaticFiles(ctx, staging);
|
|
1705
|
+
return staging;
|
|
1706
|
+
}
|
|
1707
|
+
function assertNodeRuntimes(invocable, middleware) {
|
|
1708
|
+
const offending = invocable.filter((output) => output.runtime !== "nodejs");
|
|
1709
|
+
if (offending.length === 0) {
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
const offendingMiddleware = middleware && offending.includes(middleware);
|
|
1713
|
+
const routes = sortedUnique(
|
|
1714
|
+
offending.filter((output) => output !== middleware).map((output) => `${output.sourcePage} (runtime: "${output.runtime}")`)
|
|
1715
|
+
);
|
|
1716
|
+
const reasons = [];
|
|
1717
|
+
if (routes.length > 0) {
|
|
1718
|
+
reasons.push(
|
|
1719
|
+
`cdk-nextjs cannot deploy routes built for the edge runtime. Remove \`export const runtime = "edge"\` from:
|
|
1720
|
+
` + routes.map((route) => ` - ${route}`).join("\n")
|
|
1721
|
+
);
|
|
1722
|
+
}
|
|
1723
|
+
if (offendingMiddleware) {
|
|
1724
|
+
reasons.push(
|
|
1725
|
+
`cdk-nextjs cannot deploy middleware built for the edge runtime (${middleware.filePath}, runtime: "${middleware.runtime}"). Rename it to \`proxy.ts\` (Next.js 16 runs proxy on the Node runtime), or keep \`middleware.ts\` and add \`export const config = { runtime: "nodejs" }\` to it \u2014 \`middleware.ts\` defaults to the edge runtime, but does not require it.`
|
|
1726
|
+
);
|
|
1727
|
+
}
|
|
1728
|
+
throw new Error(
|
|
1729
|
+
`${LOG_PREFIX} ${reasons.join("\n")}
|
|
1730
|
+
The edge runtime is deprecated in Next.js: https://nextjs.org/docs/messages/edge-runtime-deprecated`
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
function warnOnDroppedRouteConfig(invocable) {
|
|
1734
|
+
const dropped = {
|
|
1735
|
+
maxDuration: [],
|
|
1736
|
+
preferredRegion: []
|
|
1737
|
+
};
|
|
1738
|
+
for (const output of invocable) {
|
|
1739
|
+
if (output.config?.maxDuration !== void 0) {
|
|
1740
|
+
dropped.maxDuration.push(output.sourcePage);
|
|
1741
|
+
}
|
|
1742
|
+
if (output.config?.preferredRegion !== void 0) {
|
|
1743
|
+
dropped.preferredRegion.push(output.sourcePage);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
const remedy = {
|
|
1747
|
+
maxDuration: "Set the function timeout with the construct's `overrides` prop instead (e.g. `overrides: { nextjsFunctions: { functionProps: { timeout: Duration.seconds(30) } } }`); a Lambda timeout is per-function while `maxDuration` is per-route.",
|
|
1748
|
+
preferredRegion: "Honoring it would require a multi-region deployment, which this construct does not model."
|
|
1749
|
+
};
|
|
1750
|
+
for (const key of ["maxDuration", "preferredRegion"]) {
|
|
1751
|
+
const routes = sortedUnique(dropped[key]);
|
|
1752
|
+
if (routes.length === 0) {
|
|
1753
|
+
continue;
|
|
1754
|
+
}
|
|
1755
|
+
console.warn(
|
|
1756
|
+
`${LOG_PREFIX} \`${key}\` is not honored by cdk-nextjs. ${routes.length} route(s) set it, including: ${routes.slice(0, 5).join(", ")}${routes.length > 5 ? `, and ${routes.length - 5} more` : ""}. ` + remedy[key]
|
|
1757
|
+
);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
function collectStagingPlan(ctx, invocable) {
|
|
1761
|
+
const { repoRoot } = ctx;
|
|
1762
|
+
const staging = /* @__PURE__ */ new Map();
|
|
1763
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
1764
|
+
const add = (key, source, hash) => {
|
|
1765
|
+
assertStagingKey(key, source);
|
|
1766
|
+
const existingHash = hashes.get(key);
|
|
1767
|
+
if (hash !== void 0 && existingHash !== void 0 && existingHash !== hash) {
|
|
1768
|
+
throw new Error(
|
|
1769
|
+
`${LOG_PREFIX} Two build outputs map "${key}" to different content (hashes ${existingHash} and ${hash}). One deployment package cannot hold both. This usually means two Next.js projects in the same repo write the same repo-root-relative path.`
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
if (hash !== void 0) {
|
|
1773
|
+
hashes.set(key, hash);
|
|
1774
|
+
}
|
|
1775
|
+
if (!staging.has(key)) {
|
|
1776
|
+
staging.set(key, source);
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
for (const output of invocable) {
|
|
1780
|
+
for (const [key, source] of Object.entries(output.assets)) {
|
|
1781
|
+
add(key, source, output.assetsHashes[key]);
|
|
1782
|
+
}
|
|
1783
|
+
const entryKey = toPosix(relative(repoRoot, output.filePath));
|
|
1784
|
+
add(entryKey, output.filePath, output.assetsHashes[entryKey]);
|
|
1785
|
+
}
|
|
1786
|
+
for (const envFile of ENV_FILES) {
|
|
1787
|
+
const source = join(ctx.projectDir, envFile);
|
|
1788
|
+
if (existsSync(source)) {
|
|
1789
|
+
add(toPosix(relative(repoRoot, source)), source);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
return staging;
|
|
1793
|
+
}
|
|
1794
|
+
function collectStaticFiles(ctx) {
|
|
1795
|
+
const { repoRoot } = ctx;
|
|
1796
|
+
const basePath = ctx.config.basePath || "";
|
|
1797
|
+
const staticFiles = /* @__PURE__ */ new Map();
|
|
1798
|
+
const pagesDir = join(ctx.distDir, "server", "pages") + sep;
|
|
1799
|
+
for (const output of ctx.outputs.staticFiles) {
|
|
1800
|
+
const key = toPosix(relative(repoRoot, output.filePath));
|
|
1801
|
+
const pathnames = routablePathnames(
|
|
1802
|
+
output.pathname,
|
|
1803
|
+
basePath,
|
|
1804
|
+
output.filePath.startsWith(pagesDir)
|
|
1805
|
+
);
|
|
1806
|
+
for (const pathname of pathnames) {
|
|
1807
|
+
const existing = staticFiles.get(pathname);
|
|
1808
|
+
if (existing !== void 0 && existing !== key) {
|
|
1809
|
+
throw new Error(
|
|
1810
|
+
`${LOG_PREFIX} Two static files claim the pathname "${pathname}" ("${existing}" and "${key}"). Dispatch cannot choose between them.`
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
staticFiles.set(pathname, key);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
return Object.fromEntries(
|
|
1817
|
+
[...staticFiles].sort(([a], [b]) => a < b ? -1 : 1)
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1820
|
+
function routablePathnames(pathname, basePath, fromPagesRouter) {
|
|
1821
|
+
if (!fromPagesRouter || pathname !== `${basePath}/index`) {
|
|
1822
|
+
return [pathname];
|
|
1823
|
+
}
|
|
1824
|
+
return [basePath || "/", pathname];
|
|
1825
|
+
}
|
|
1826
|
+
function stageServedStaticFiles(ctx, staging) {
|
|
1827
|
+
const { repoRoot, distDir } = ctx;
|
|
1828
|
+
const clientStaticDir = join(distDir, "static") + sep;
|
|
1829
|
+
for (const output of ctx.outputs.staticFiles) {
|
|
1830
|
+
const servedByS3 = output.filePath.startsWith(clientStaticDir) || !output.filePath.startsWith(distDir + sep);
|
|
1831
|
+
if (servedByS3) {
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
const key = toPosix(relative(repoRoot, output.filePath));
|
|
1835
|
+
assertStagingKey(key, output.filePath);
|
|
1836
|
+
staging.set(key, output.filePath);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
function assertBuildCwd(ctx, buildCwd) {
|
|
1840
|
+
const cwd = resolve(buildCwd);
|
|
1841
|
+
if (cwd === resolve(ctx.projectDir)) {
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
throw new Error(
|
|
1845
|
+
`${LOG_PREFIX} \`next build\` must run from the Next.js project directory. It ran from "${cwd}" with the project at "${ctx.projectDir}". Next.js bakes the relative path between those two into every built entrypoint, and cdk-nextjs cannot reproduce that layout in the deployment package. Change directory first (\`cd ${relative(cwd, ctx.projectDir) || "."} && next build\`) instead of passing the directory as an argument; in a monorepo, run the app's own build script from its directory (e.g. \`pnpm --filter <app> build\`, which runs in the package directory).`
|
|
1846
|
+
);
|
|
1847
|
+
}
|
|
1848
|
+
function addEntrypoint(entrypoints, repoRoot, output, type, basePath) {
|
|
1849
|
+
const filePath = toPosix(relative(repoRoot, output.filePath));
|
|
1850
|
+
const pathnames = routablePathnames(
|
|
1851
|
+
output.pathname,
|
|
1852
|
+
basePath,
|
|
1853
|
+
type === "page"
|
|
1854
|
+
);
|
|
1855
|
+
for (const pathname of pathnames) {
|
|
1856
|
+
const existing = entrypoints[pathname];
|
|
1857
|
+
if (existing && existing.filePath !== filePath) {
|
|
1858
|
+
throw new Error(
|
|
1859
|
+
`${LOG_PREFIX} Two build outputs claim the pathname "${pathname}" ("${existing.filePath}" and "${filePath}"). Dispatch cannot choose between them.`
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1862
|
+
entrypoints[pathname] = { id: output.id, filePath, type };
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
function addPrerenderPathnames(entrypoints, prerenders, dynamicRoutes, basePath, dataRoutePrefix) {
|
|
1866
|
+
const matchers = dynamicRoutes.map((route) => ({
|
|
1867
|
+
regex: new RegExp(route.sourceRegex, "i"),
|
|
1868
|
+
gated: Boolean(route.has?.length || route.missing?.length)
|
|
1869
|
+
}));
|
|
1870
|
+
const matchingRules = (pathname) => matchers.filter(({ regex }) => regex.test(pathname));
|
|
1871
|
+
const onlyGatedRulesMatch = (pathname) => {
|
|
1872
|
+
const matched = matchingRules(pathname);
|
|
1873
|
+
return matched.length > 0 && matched.every(({ gated }) => gated);
|
|
1874
|
+
};
|
|
1875
|
+
const nothingUngatedMatches = (pathname) => matchingRules(pathname).every(({ gated }) => gated);
|
|
1876
|
+
const orphans = [];
|
|
1877
|
+
for (const prerender of prerenders) {
|
|
1878
|
+
const { pathname } = prerender;
|
|
1879
|
+
if (entrypoints[pathname]) {
|
|
1880
|
+
continue;
|
|
1881
|
+
}
|
|
1882
|
+
const isTemplate = pathname.includes("[");
|
|
1883
|
+
const isDataRoute = pathname.startsWith(dataRoutePrefix) && pathname.endsWith(".json");
|
|
1884
|
+
const reachable = isDataRoute ? nothingUngatedMatches(pathname) : onlyGatedRulesMatch(pathname);
|
|
1885
|
+
if (!isTemplate && !reachable) {
|
|
1886
|
+
continue;
|
|
1887
|
+
}
|
|
1888
|
+
const suffixes = pathname.endsWith(".rsc") ? [".rsc", ""] : [""];
|
|
1889
|
+
const owner = suffixes.flatMap((suffix) => [
|
|
1890
|
+
`${basePath}${prerender.route}${suffix}`,
|
|
1891
|
+
`${prerender.route}${suffix}`
|
|
1892
|
+
]).map((key) => entrypoints[key]).find(Boolean);
|
|
1893
|
+
if (!owner) {
|
|
1894
|
+
orphans.push(`${pathname} (route: "${prerender.route}")`);
|
|
1895
|
+
continue;
|
|
1896
|
+
}
|
|
1897
|
+
entrypoints[pathname] = {
|
|
1898
|
+
id: prerender.id,
|
|
1899
|
+
filePath: owner.filePath,
|
|
1900
|
+
type: owner.type
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
if (orphans.length > 0) {
|
|
1904
|
+
console.warn(
|
|
1905
|
+
`${LOG_PREFIX} ${orphans.length} prerender pathname(s) have no matching route entrypoint and will 404: ${orphans.slice(0, 5).join(", ")}${orphans.length > 5 ? `, and ${orphans.length - 5} more` : ""}.`
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
function buildMiddleware(repoRoot, middleware) {
|
|
1910
|
+
if (!middleware) {
|
|
1911
|
+
return null;
|
|
1912
|
+
}
|
|
1913
|
+
return {
|
|
1914
|
+
id: middleware.id,
|
|
1915
|
+
filePath: toPosix(relative(repoRoot, middleware.filePath))
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
var STAGING_CONCURRENCY = 32;
|
|
1919
|
+
async function stageFiles(staging, stagingDir) {
|
|
1920
|
+
const planned = [...staging];
|
|
1921
|
+
const links = new Array(
|
|
1922
|
+
planned.length
|
|
1923
|
+
);
|
|
1924
|
+
let nextEntry = 0;
|
|
1925
|
+
const worker = async () => {
|
|
1926
|
+
while (nextEntry < planned.length) {
|
|
1927
|
+
const index = nextEntry++;
|
|
1928
|
+
const [key, source] = planned[index];
|
|
1929
|
+
const linkTarget = await readlink(source).catch(() => null);
|
|
1930
|
+
if (linkTarget !== null) {
|
|
1931
|
+
links[index] = [key, source, linkTarget];
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
const dest = join(stagingDir, key);
|
|
1935
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
1936
|
+
await copyFile(source, dest);
|
|
1937
|
+
}
|
|
1938
|
+
};
|
|
1939
|
+
await Promise.all(
|
|
1940
|
+
Array.from(
|
|
1941
|
+
{ length: Math.min(STAGING_CONCURRENCY, planned.length) },
|
|
1942
|
+
worker
|
|
1943
|
+
)
|
|
1944
|
+
);
|
|
1945
|
+
for (const link of links) {
|
|
1946
|
+
if (link === void 0) {
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
const [key, source, linkTarget] = link;
|
|
1950
|
+
const dest = join(stagingDir, key);
|
|
1951
|
+
if (existsSync(dest)) {
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
1955
|
+
const resolved = resolve(dirname(dest), linkTarget);
|
|
1956
|
+
if (resolved === stagingDir || resolved.startsWith(stagingDir + sep)) {
|
|
1957
|
+
await symlink(linkTarget, dest);
|
|
1958
|
+
} else {
|
|
1959
|
+
await cp(source, dest, { recursive: true, dereference: true });
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
async function addRuntimeNextClosure(ctx, staging, imageStaging) {
|
|
1964
|
+
const nextRequire = createRequire(
|
|
1965
|
+
join(ctx.projectDir, "cdk-nextjs-next-resolver.cjs")
|
|
1966
|
+
);
|
|
1967
|
+
let entries;
|
|
1968
|
+
let imageEntries;
|
|
1969
|
+
try {
|
|
1970
|
+
entries = RUNTIME_NEXT_MODULES.map(
|
|
1971
|
+
(specifier) => nextRequire.resolve(specifier)
|
|
1972
|
+
);
|
|
1973
|
+
imageEntries = IMAGE_NEXT_MODULES.map(
|
|
1974
|
+
(specifier) => nextRequire.resolve(specifier)
|
|
1975
|
+
);
|
|
1976
|
+
} catch (cause) {
|
|
1977
|
+
console.warn(
|
|
1978
|
+
`${LOG_PREFIX} Could not resolve next's image optimizer from "${ctx.projectDir}" (${cause.message}). Requests to /_next/image will fail at runtime.`
|
|
1979
|
+
);
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
const nextPackage = nextRequire.resolve("next/package.json");
|
|
1983
|
+
try {
|
|
1984
|
+
entries.push(createRequire(nextPackage).resolve("@next/env"));
|
|
1985
|
+
} catch (cause) {
|
|
1986
|
+
throw new Error(
|
|
1987
|
+
`${LOG_PREFIX} Could not resolve "@next/env" from the app's \`next\`, which the cdk-nextjs runtime uses to load .env files.`,
|
|
1988
|
+
{ cause }
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
let nodeFileTrace;
|
|
1992
|
+
try {
|
|
1993
|
+
({ nodeFileTrace } = nextRequire(NEXT_FILE_TRACER));
|
|
1994
|
+
} catch (cause) {
|
|
1995
|
+
throw new Error(
|
|
1996
|
+
`${LOG_PREFIX} Could not load "${NEXT_FILE_TRACER}", which cdk-nextjs uses to stage the \`next\` files its image optimizer requires. This Next.js version may have moved it; please open an issue.`,
|
|
1997
|
+
{ cause }
|
|
1998
|
+
);
|
|
1999
|
+
}
|
|
2000
|
+
const add = (fileList2, target) => {
|
|
2001
|
+
for (const traced of fileList2) {
|
|
2002
|
+
const key = toPosix(traced);
|
|
2003
|
+
if (target.has(key)) {
|
|
2004
|
+
continue;
|
|
2005
|
+
}
|
|
2006
|
+
const source = join(ctx.repoRoot, traced);
|
|
2007
|
+
assertStagingKey(key, source);
|
|
2008
|
+
target.set(key, source);
|
|
2009
|
+
}
|
|
2010
|
+
};
|
|
2011
|
+
add(
|
|
2012
|
+
(await nodeFileTrace(imageEntries, { base: ctx.repoRoot })).fileList,
|
|
2013
|
+
imageStaging
|
|
2014
|
+
);
|
|
2015
|
+
const { fileList } = await nodeFileTrace(entries, { base: ctx.repoRoot });
|
|
2016
|
+
const nextEnvLink = join(dirname(nextPackage), "..", "@next", "env");
|
|
2017
|
+
if (lstatSync(nextEnvLink, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
2018
|
+
fileList.add(relative(realpathSync(ctx.repoRoot), nextEnvLink));
|
|
2019
|
+
}
|
|
2020
|
+
add(fileList, staging);
|
|
2021
|
+
}
|
|
2022
|
+
function addRequiredServerFiles(ctx, staging) {
|
|
2023
|
+
const source = join(ctx.distDir, "required-server-files.json");
|
|
2024
|
+
const key = toPosix(relative(ctx.repoRoot, source));
|
|
2025
|
+
if (staging.has(key) || !existsSync(source)) {
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
assertStagingKey(key, source);
|
|
2029
|
+
staging.set(key, source);
|
|
2030
|
+
}
|
|
2031
|
+
function assertStagingKey(key, source) {
|
|
2032
|
+
if (key === "" || key.startsWith("/") || key.startsWith("..")) {
|
|
2033
|
+
throw new Error(
|
|
2034
|
+
`${LOG_PREFIX} Build output asset "${source}" resolves to the staging key "${key}", which is outside the deployment root. Set \`outputFileTracingRoot\` so all traced files live under one root.`
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
2037
|
+
if (key === RUNTIME_DIR_NAME || key.startsWith(`${RUNTIME_DIR_NAME}/`)) {
|
|
2038
|
+
throw new Error(
|
|
2039
|
+
`${LOG_PREFIX} Build output asset "${source}" resolves to the staging key "${key}", but "${RUNTIME_DIR_NAME}/" is reserved for cdk-nextjs's own runtime files.`
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
function toPosix(path) {
|
|
2044
|
+
return sep === "/" ? path : path.split(sep).join("/");
|
|
2045
|
+
}
|
|
2046
|
+
function sortedUnique(values) {
|
|
2047
|
+
return [...new Set(values)].sort();
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
// src/adapter/init-cache.ts
|
|
2051
|
+
var import_debug = __toESM(require_src());
|
|
2052
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
2053
|
+
import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "node:fs/promises";
|
|
2054
|
+
import { join as join2 } from "node:path";
|
|
2055
|
+
import { CachedRouteKind as CachedRouteKind2 } from "next/dist/server/response-cache/index.js";
|
|
2056
|
+
|
|
2057
|
+
// src/adapter/cache-kinds.ts
|
|
2058
|
+
import { CachedRouteKind } from "next/dist/server/response-cache/index.js";
|
|
2059
|
+
function cacheKindResolver(outputs) {
|
|
2060
|
+
const kindById = /* @__PURE__ */ new Map();
|
|
2061
|
+
for (const { id } of outputs.pages) {
|
|
2062
|
+
kindById.set(id, CachedRouteKind.PAGES);
|
|
2063
|
+
}
|
|
2064
|
+
for (const { id } of outputs.appPages) {
|
|
2065
|
+
kindById.set(id, CachedRouteKind.APP_PAGE);
|
|
2066
|
+
}
|
|
2067
|
+
for (const { id } of outputs.appRoutes) {
|
|
2068
|
+
kindById.set(id, CachedRouteKind.APP_ROUTE);
|
|
2069
|
+
}
|
|
2070
|
+
return (variants) => {
|
|
2071
|
+
const source = variants.html ?? variants.rsc ?? variants.data ?? variants.segments[0];
|
|
2072
|
+
return source ? kindById.get(source.parentOutputId) : void 0;
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
801
2075
|
|
|
802
2076
|
// src/adapter/cache-utils.ts
|
|
803
2077
|
function preprocessValue(value) {
|
|
@@ -807,7 +2081,7 @@ function preprocessValue(value) {
|
|
|
807
2081
|
if (Buffer.isBuffer(value)) {
|
|
808
2082
|
return {
|
|
809
2083
|
__type: "Buffer",
|
|
810
|
-
|
|
2084
|
+
base64: value.toString("base64")
|
|
811
2085
|
};
|
|
812
2086
|
}
|
|
813
2087
|
if (value instanceof Map) {
|
|
@@ -836,140 +2110,222 @@ function serializeCacheValue(value) {
|
|
|
836
2110
|
const processed = preprocessValue(value);
|
|
837
2111
|
return JSON.stringify(processed);
|
|
838
2112
|
}
|
|
2113
|
+
function prerenderPathToCacheKey(pathname, basePath) {
|
|
2114
|
+
const route = (hasPathPrefix(pathname, basePath) ? pathname.slice(basePath.length) : pathname).replace(/^\/+/, "");
|
|
2115
|
+
return route === "" ? "index" : route;
|
|
2116
|
+
}
|
|
2117
|
+
var INDEX_SUFFIX = "/index";
|
|
2118
|
+
var PAGES_DATA_PATHNAME = /^(.*)\/_next\/data\/[^/]+\/(.+)\.json$/;
|
|
2119
|
+
function groupPrerenders(prerenders) {
|
|
2120
|
+
const htmlPathnames = new Set(
|
|
2121
|
+
prerenders.map((p) => p.pathname).filter(
|
|
2122
|
+
(p) => !p.endsWith(".rsc") && !p.includes(".segments/") && !PAGES_DATA_PATHNAME.test(p)
|
|
2123
|
+
)
|
|
2124
|
+
);
|
|
2125
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2126
|
+
const variantsFor = (base) => {
|
|
2127
|
+
let variants = groups.get(base);
|
|
2128
|
+
if (!variants) {
|
|
2129
|
+
variants = { segments: [] };
|
|
2130
|
+
groups.set(base, variants);
|
|
2131
|
+
}
|
|
2132
|
+
return variants;
|
|
2133
|
+
};
|
|
2134
|
+
const routeOf = (base) => {
|
|
2135
|
+
if (base.endsWith(INDEX_SUFFIX) && !htmlPathnames.has(base)) {
|
|
2136
|
+
const parent = base.slice(0, -INDEX_SUFFIX.length) || "/";
|
|
2137
|
+
if (htmlPathnames.has(parent)) {
|
|
2138
|
+
return parent;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
return base;
|
|
2142
|
+
};
|
|
2143
|
+
for (const prerender of prerenders) {
|
|
2144
|
+
const { pathname } = prerender;
|
|
2145
|
+
const dataRoute = PAGES_DATA_PATHNAME.exec(pathname);
|
|
2146
|
+
if (dataRoute) {
|
|
2147
|
+
variantsFor(routeOf(`${dataRoute[1]}/${dataRoute[2]}`)).data = prerender;
|
|
2148
|
+
} else if (pathname.includes(".segments/")) {
|
|
2149
|
+
variantsFor(routeOf(pathname.split(".segments/")[0])).segments.push(
|
|
2150
|
+
prerender
|
|
2151
|
+
);
|
|
2152
|
+
} else if (pathname.endsWith(".rsc")) {
|
|
2153
|
+
variantsFor(routeOf(pathname.slice(0, -".rsc".length))).rsc = prerender;
|
|
2154
|
+
} else {
|
|
2155
|
+
variantsFor(pathname).html = prerender;
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
return groups;
|
|
2159
|
+
}
|
|
2160
|
+
var NON_CACHEABLE_APP_PAGE_HEADERS = /* @__PURE__ */ new Set([
|
|
2161
|
+
"content-type",
|
|
2162
|
+
"vary",
|
|
2163
|
+
"x-nextjs-prerender",
|
|
2164
|
+
"x-nextjs-postponed"
|
|
2165
|
+
]);
|
|
2166
|
+
function appPageCacheHeaders(initialHeaders) {
|
|
2167
|
+
const headers = {};
|
|
2168
|
+
for (const [name, value] of Object.entries(initialHeaders)) {
|
|
2169
|
+
if (!NON_CACHEABLE_APP_PAGE_HEADERS.has(name.toLowerCase())) {
|
|
2170
|
+
headers[name] = value;
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
return headers;
|
|
2174
|
+
}
|
|
2175
|
+
var NEXT_CACHE_TAGS_HEADER = "x-next-cache-tags";
|
|
2176
|
+
function headerTags(value) {
|
|
2177
|
+
const headers = value?.headers;
|
|
2178
|
+
const header = headers?.[NEXT_CACHE_TAGS_HEADER];
|
|
2179
|
+
return typeof header === "string" ? header.split(",").filter(Boolean) : [];
|
|
2180
|
+
}
|
|
2181
|
+
var INIT_CACHE_TAG_MANIFEST = "_cdk-nextjs-tag-manifest.json";
|
|
839
2182
|
|
|
840
|
-
// src/adapter/
|
|
841
|
-
var import_debug = __toESM(require_src(), 1);
|
|
2183
|
+
// src/adapter/init-cache.ts
|
|
842
2184
|
var debug = (0, import_debug.default)("cdk-nextjs:adapter");
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
try {
|
|
874
|
-
if (basePath.includes("[")) {
|
|
875
|
-
debug(`SKIP: Dynamic route template ${basePath}`);
|
|
876
|
-
continue;
|
|
877
|
-
}
|
|
878
|
-
const kind = routeToCacheKind.get(basePath);
|
|
879
|
-
if (!kind) {
|
|
880
|
-
debug(`SKIP: No route kind found for ${basePath}`);
|
|
2185
|
+
async function writeInitCache(ctx, cacheDir) {
|
|
2186
|
+
await mkdir2(cacheDir, { recursive: true });
|
|
2187
|
+
debug(`Init cache directory: ${cacheDir}`);
|
|
2188
|
+
const prerenderGroups = groupPrerenders(ctx.outputs.prerenders);
|
|
2189
|
+
debug(`Prerender groups: ${prerenderGroups.size} groups`);
|
|
2190
|
+
const cacheKindOf = cacheKindResolver(ctx.outputs);
|
|
2191
|
+
const tagManifest = {};
|
|
2192
|
+
for (const [basePath, variants] of prerenderGroups) {
|
|
2193
|
+
try {
|
|
2194
|
+
const kind = cacheKindOf(variants);
|
|
2195
|
+
if (!kind) {
|
|
2196
|
+
debug(`SKIP: No route kind found for ${basePath}`);
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2199
|
+
debug(`Processing ${basePath} (${kind})`);
|
|
2200
|
+
const {
|
|
2201
|
+
html: htmlPrerender,
|
|
2202
|
+
rsc: rscPrerender,
|
|
2203
|
+
segments: segmentPrerenders,
|
|
2204
|
+
data: dataPrerender
|
|
2205
|
+
} = variants;
|
|
2206
|
+
if (!htmlPrerender && !rscPrerender && !dataPrerender) {
|
|
2207
|
+
debug(`SKIP: No prerender files found for ${basePath}`);
|
|
2208
|
+
continue;
|
|
2209
|
+
}
|
|
2210
|
+
let cacheEntry;
|
|
2211
|
+
if (kind === CachedRouteKind2.APP_PAGE) {
|
|
2212
|
+
const html = await readPrerenderAsText(htmlPrerender);
|
|
2213
|
+
if (!html) {
|
|
2214
|
+
debug(`SKIP: No HTML content for APP_PAGE ${basePath}`);
|
|
881
2215
|
continue;
|
|
882
2216
|
}
|
|
883
|
-
|
|
884
|
-
const
|
|
885
|
-
const
|
|
886
|
-
|
|
2217
|
+
const rscData = await readPrerenderAsBuffer(rscPrerender);
|
|
2218
|
+
const segmentData = await getSegmentData(segmentPrerenders);
|
|
2219
|
+
const headers = appPageCacheHeaders(
|
|
2220
|
+
htmlPrerender?.fallback?.initialHeaders || rscPrerender?.fallback?.initialHeaders || {}
|
|
887
2221
|
);
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
2222
|
+
cacheEntry = {
|
|
2223
|
+
lastModified: Date.now(),
|
|
2224
|
+
value: {
|
|
2225
|
+
kind: CachedRouteKind2.APP_PAGE,
|
|
2226
|
+
html,
|
|
2227
|
+
rscData,
|
|
2228
|
+
headers,
|
|
2229
|
+
segmentData,
|
|
2230
|
+
// postponedState from fallback appears to be the React postponed state
|
|
2231
|
+
// which matches what the cache expects for postponed
|
|
2232
|
+
postponed: htmlPrerender?.fallback?.postponedState,
|
|
2233
|
+
// initialStatus from prerender is the HTTP status of the prerendered page
|
|
2234
|
+
status: htmlPrerender?.fallback?.initialStatus
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
} else if (kind === CachedRouteKind2.APP_ROUTE) {
|
|
2238
|
+
const body = await readPrerenderAsBuffer(htmlPrerender);
|
|
2239
|
+
if (!body) {
|
|
2240
|
+
debug(`SKIP: No body content for APP_ROUTE ${basePath}`);
|
|
893
2241
|
continue;
|
|
894
2242
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
const segmentData = await getSegmentData(segmentPrerenders);
|
|
904
|
-
const headers = htmlPrerender?.fallback?.initialHeaders || rscPrerender?.fallback?.initialHeaders || {};
|
|
905
|
-
cacheEntry = {
|
|
906
|
-
lastModified: Date.now(),
|
|
907
|
-
value: {
|
|
908
|
-
kind: CachedRouteKind.APP_PAGE,
|
|
909
|
-
html,
|
|
910
|
-
rscData,
|
|
911
|
-
headers,
|
|
912
|
-
segmentData,
|
|
913
|
-
// postponedState from fallback appears to be the React postponed state
|
|
914
|
-
// which matches what the cache expects for postponed
|
|
915
|
-
postponed: htmlPrerender?.fallback?.postponedState,
|
|
916
|
-
// initialStatus from prerender is the HTTP status of the prerendered page
|
|
917
|
-
status: htmlPrerender?.fallback?.initialStatus
|
|
918
|
-
}
|
|
919
|
-
};
|
|
920
|
-
} else if (kind === CachedRouteKind.APP_ROUTE) {
|
|
921
|
-
const body = await readPrerenderAsBuffer(htmlPrerender);
|
|
922
|
-
if (!body) {
|
|
923
|
-
debug(`SKIP: No body content for APP_ROUTE ${basePath}`);
|
|
924
|
-
continue;
|
|
2243
|
+
const headers = htmlPrerender?.fallback?.initialHeaders || {};
|
|
2244
|
+
cacheEntry = {
|
|
2245
|
+
lastModified: Date.now(),
|
|
2246
|
+
value: {
|
|
2247
|
+
kind: CachedRouteKind2.APP_ROUTE,
|
|
2248
|
+
body,
|
|
2249
|
+
headers,
|
|
2250
|
+
status: htmlPrerender?.fallback?.initialStatus || 200
|
|
925
2251
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
status: htmlPrerender?.fallback?.initialStatus || 200
|
|
934
|
-
}
|
|
935
|
-
};
|
|
936
|
-
} else {
|
|
937
|
-
debug(`SKIP: Unsupported route kind ${kind} for ${basePath}`);
|
|
2252
|
+
};
|
|
2253
|
+
} else if (kind === CachedRouteKind2.PAGES) {
|
|
2254
|
+
const initialStatus = htmlPrerender?.fallback?.initialStatus;
|
|
2255
|
+
if (initialStatus !== void 0 && initialStatus !== 200) {
|
|
2256
|
+
debug(
|
|
2257
|
+
`SKIP: PAGES ${basePath} prerendered with status ${initialStatus} (build-time notFound)`
|
|
2258
|
+
);
|
|
938
2259
|
continue;
|
|
939
2260
|
}
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
2261
|
+
const html = await readPrerenderAsText(htmlPrerender);
|
|
2262
|
+
if (!html) {
|
|
2263
|
+
debug(`SKIP: No HTML content for PAGES ${basePath}`);
|
|
2264
|
+
continue;
|
|
2265
|
+
}
|
|
2266
|
+
const pageDataJson = await readPrerenderAsText(dataPrerender);
|
|
2267
|
+
cacheEntry = {
|
|
2268
|
+
lastModified: Date.now(),
|
|
2269
|
+
value: {
|
|
2270
|
+
kind: CachedRouteKind2.PAGES,
|
|
2271
|
+
html,
|
|
2272
|
+
pageData: pageDataJson ? JSON.parse(pageDataJson) : {},
|
|
2273
|
+
// Both `undefined`, which is what `FileSystemCache` hands back for
|
|
2274
|
+
// a `PAGES` entry: it reads a `.meta` sidecar for the App Router
|
|
2275
|
+
// kinds only, and `sendRenderResult` supplies the content type.
|
|
2276
|
+
// Seeding `fallback.initialHeaders` instead would put
|
|
2277
|
+
// `content-type: text/html` on the JSON data responses served from
|
|
2278
|
+
// this same entry.
|
|
2279
|
+
headers: void 0,
|
|
2280
|
+
status: void 0
|
|
946
2281
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
debug(`
|
|
950
|
-
|
|
951
|
-
console.error(`Error processing prerender group ${basePath}:`, error);
|
|
2282
|
+
};
|
|
2283
|
+
} else {
|
|
2284
|
+
debug(`SKIP: Unsupported route kind ${kind} for ${basePath}`);
|
|
2285
|
+
continue;
|
|
952
2286
|
}
|
|
2287
|
+
const cacheKey = prerenderPathToCacheKey(
|
|
2288
|
+
basePath,
|
|
2289
|
+
ctx.config.basePath || ""
|
|
2290
|
+
);
|
|
2291
|
+
const cacheFilePath = join2(cacheDir, `${cacheKey}.json`);
|
|
2292
|
+
await mkdir2(join2(cacheDir, cacheKey.split("/").slice(0, -1).join("/")), {
|
|
2293
|
+
recursive: true
|
|
2294
|
+
});
|
|
2295
|
+
await writeFile2(cacheFilePath, serializeCacheValue(cacheEntry));
|
|
2296
|
+
for (const tag of headerTags(cacheEntry.value)) {
|
|
2297
|
+
(tagManifest[tag] ??= []).push(cacheKey);
|
|
2298
|
+
}
|
|
2299
|
+
debug(`Created cache entry: ${cacheFilePath}`);
|
|
2300
|
+
} catch (error) {
|
|
2301
|
+
console.error(`Error processing prerender group ${basePath}:`, error);
|
|
953
2302
|
}
|
|
954
2303
|
}
|
|
955
|
-
|
|
956
|
-
|
|
2304
|
+
const taggedKeys = Object.keys(tagManifest).length;
|
|
2305
|
+
if (taggedKeys > 0) {
|
|
2306
|
+
await writeFile2(
|
|
2307
|
+
join2(cacheDir, INIT_CACHE_TAG_MANIFEST),
|
|
2308
|
+
JSON.stringify(tagManifest)
|
|
2309
|
+
);
|
|
2310
|
+
debug(`Wrote ${INIT_CACHE_TAG_MANIFEST} with ${taggedKeys} tags`);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
957
2313
|
async function readPrerenderAsText(prerender) {
|
|
958
|
-
if (prerender?.fallback && "filePath" in prerender.fallback && prerender.fallback.filePath &&
|
|
959
|
-
return
|
|
2314
|
+
if (prerender?.fallback && "filePath" in prerender.fallback && prerender.fallback.filePath && existsSync2(prerender.fallback.filePath)) {
|
|
2315
|
+
return readFile(prerender.fallback.filePath, "utf-8");
|
|
960
2316
|
}
|
|
961
2317
|
return void 0;
|
|
962
2318
|
}
|
|
963
2319
|
async function readPrerenderAsBuffer(prerender) {
|
|
964
|
-
if (prerender?.fallback && "filePath" in prerender.fallback && prerender.fallback.filePath &&
|
|
965
|
-
return
|
|
2320
|
+
if (prerender?.fallback && "filePath" in prerender.fallback && prerender.fallback.filePath && existsSync2(prerender.fallback.filePath)) {
|
|
2321
|
+
return readFile(prerender.fallback.filePath);
|
|
966
2322
|
}
|
|
967
2323
|
return void 0;
|
|
968
2324
|
}
|
|
969
2325
|
async function getSegmentData(segmentPrerenders) {
|
|
970
2326
|
const segmentData = /* @__PURE__ */ new Map();
|
|
971
2327
|
for (const segmentPrerender of segmentPrerenders) {
|
|
972
|
-
if (segmentPrerender.fallback && "filePath" in segmentPrerender.fallback && segmentPrerender.fallback.filePath &&
|
|
2328
|
+
if (segmentPrerender.fallback && "filePath" in segmentPrerender.fallback && segmentPrerender.fallback.filePath && existsSync2(segmentPrerender.fallback.filePath)) {
|
|
973
2329
|
const segmentContent = await readFile(segmentPrerender.fallback.filePath);
|
|
974
2330
|
const segmentName = "/" + segmentPrerender.pathname.split(".segments/")[1].replace(/\.segment\.rsc$/, "");
|
|
975
2331
|
segmentData.set(segmentName, segmentContent);
|
|
@@ -977,68 +2333,75 @@ async function getSegmentData(segmentPrerenders) {
|
|
|
977
2333
|
}
|
|
978
2334
|
return segmentData;
|
|
979
2335
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
2336
|
+
|
|
2337
|
+
// src/adapter/adapter.mts
|
|
2338
|
+
var import_debug2 = __toESM(require_src(), 1);
|
|
2339
|
+
var debug2 = (0, import_debug2.default)("cdk-nextjs:adapter");
|
|
2340
|
+
var adapter = {
|
|
2341
|
+
name: "cdk-nextjs-adapter",
|
|
2342
|
+
async modifyConfig(config, { phase, nextVersion }) {
|
|
2343
|
+
if (phase === "phase-production-build") {
|
|
2344
|
+
assertSupportedNextVersion(nextVersion);
|
|
2345
|
+
return {
|
|
2346
|
+
...config,
|
|
2347
|
+
// No `output: "standalone"`. `onBuildComplete` stages the deployment
|
|
2348
|
+
// root from the same NFT traces `writeStandaloneDirectory` would have
|
|
2349
|
+
// used, so the two are alternatives rather than layers — `next build`
|
|
2350
|
+
// says as much itself, immediately above the `onBuildComplete` call:
|
|
2351
|
+
// "in the future `output: standalone` might not be allowed if an adapter
|
|
2352
|
+
// with `onBuildComplete` is configured."
|
|
2353
|
+
cacheHandler: config.cacheHandler ? config.cacheHandler : fileURLToPath(import.meta.resolve("cdk-nextjs/cache-handler")),
|
|
2354
|
+
cacheHandlers: useCacheHandlers(config.cacheHandlers),
|
|
2355
|
+
images: {
|
|
2356
|
+
...config.images,
|
|
2357
|
+
customCacheHandler: config.images.customCacheHandler ? config.images.customCacheHandler : true
|
|
2358
|
+
// TODO: remove in Next.js 17
|
|
2359
|
+
}
|
|
2360
|
+
};
|
|
985
2361
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
2362
|
+
if (phase === "phase-production-server") {
|
|
2363
|
+
return {
|
|
2364
|
+
...config,
|
|
2365
|
+
cacheHandlers: useCacheHandlers(config.cacheHandlers)
|
|
2366
|
+
};
|
|
990
2367
|
}
|
|
991
|
-
return
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
for (const prerenderPath of prerenderPaths) {
|
|
1002
|
-
if (matchesDynamicRoute(prerenderPath, appPage.pathname)) {
|
|
1003
|
-
routeToCacheKind.set(prerenderPath, CachedRouteKind.APP_PAGE);
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
2368
|
+
return config;
|
|
2369
|
+
},
|
|
2370
|
+
async onBuildComplete(ctx) {
|
|
2371
|
+
const { manifest, adapterDir, stagedGroups } = await writeBuildOutputs(ctx);
|
|
2372
|
+
const entrypointCount = Object.keys(manifest.entrypoints).length;
|
|
2373
|
+
for (const group of stagedGroups) {
|
|
2374
|
+
const routes = manifest.groups?.[group.name];
|
|
2375
|
+
console.log(
|
|
2376
|
+
`${LOG_PREFIX} Staged ${group.fileCount} files for ${routes ? `${routes.length} of ${entrypointCount}` : entrypointCount} entrypoints in ${group.path}`
|
|
2377
|
+
);
|
|
1006
2378
|
}
|
|
2379
|
+
debug2(`Adapter output directory: ${adapterDir}`);
|
|
2380
|
+
const cacheDir = process.env.CDK_NEXTJS_INIT_CACHE_DIR || join3(ctx.distDir, "cdk-nextjs-init-cache");
|
|
2381
|
+
await writeInitCache(ctx, cacheDir);
|
|
1007
2382
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
2383
|
+
};
|
|
2384
|
+
var adapter_default = adapter;
|
|
2385
|
+
var MIN_NEXT_VERSION = [16, 3];
|
|
2386
|
+
function assertSupportedNextVersion(nextVersion) {
|
|
2387
|
+
const match = /^(\d+)\.(\d+)\./.exec(nextVersion ?? "");
|
|
2388
|
+
if (!match) {
|
|
2389
|
+
return;
|
|
1017
2390
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
if (basePath.endsWith(".rsc")) {
|
|
1025
|
-
basePath = basePath.replace(/\.rsc$/, "");
|
|
1026
|
-
}
|
|
1027
|
-
if (basePath.includes(".segments/")) {
|
|
1028
|
-
basePath = basePath.split(".segments/")[0];
|
|
1029
|
-
}
|
|
1030
|
-
if (basePath === "/index") {
|
|
1031
|
-
basePath = "/";
|
|
1032
|
-
}
|
|
1033
|
-
if (!prerenderGroups.has(basePath)) {
|
|
1034
|
-
prerenderGroups.set(basePath, []);
|
|
1035
|
-
}
|
|
1036
|
-
const group = prerenderGroups.get(basePath);
|
|
1037
|
-
if (group) {
|
|
1038
|
-
group.push(prerender);
|
|
1039
|
-
}
|
|
2391
|
+
const [major, minor] = [Number(match[1]), Number(match[2])];
|
|
2392
|
+
const [minMajor, minMinor] = MIN_NEXT_VERSION;
|
|
2393
|
+
if (major < minMajor || major === minMajor && minor < minMinor) {
|
|
2394
|
+
throw new Error(
|
|
2395
|
+
`${LOG_PREFIX} Next.js ${nextVersion} is not supported: cdk-nextjs needs Next.js ${minMajor}.${minMinor} or higher.`
|
|
2396
|
+
);
|
|
1040
2397
|
}
|
|
1041
|
-
|
|
2398
|
+
}
|
|
2399
|
+
function useCacheHandlers(configured) {
|
|
2400
|
+
return {
|
|
2401
|
+
...configured,
|
|
2402
|
+
default: configured?.default || fileURLToPath(import.meta.resolve("cdk-nextjs/cache-handlers/default")),
|
|
2403
|
+
remote: configured?.remote || fileURLToPath(import.meta.resolve("cdk-nextjs/cache-handlers/remote"))
|
|
2404
|
+
};
|
|
1042
2405
|
}
|
|
1043
2406
|
export {
|
|
1044
2407
|
adapter_default as default
|