cdk-nextjs 0.6.2 → 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.
Files changed (263) hide show
  1. package/.jsii +2404 -2723
  2. package/.jsii.tabl.json +1 -1
  3. package/API.md +1681 -1930
  4. package/CLAUDE.md +2 -1
  5. package/README.md +429 -130
  6. package/assets/lambdas/post-deploy/post-deploy.lambda/index.js +148 -169
  7. package/assets/lambdas/redeploy-stage/redeploy-stage.lambda/index.js +875 -0
  8. package/docs/adapter-runtime.md +185 -0
  9. package/docs/breaking-changes.md +667 -4
  10. package/docs/caching-guide.md +156 -112
  11. package/docs/cdk-nextjs-cfn-exec-policy.json +2 -1
  12. package/docs/development-guide.md +1 -1
  13. package/docs/harness-coverage.md +598 -0
  14. package/docs/init-cache-deployment.md +32 -1
  15. package/docs/next-build-output-guide.md +30 -20
  16. package/examples/README.md +5 -1
  17. package/lib/adapter/__fixtures__/app-playground-base-path.json +4926 -0
  18. package/lib/adapter/__fixtures__/app-playground.json +4917 -0
  19. package/lib/adapter/__fixtures__/pages-i18n.json +1728 -0
  20. package/lib/adapter/adapter.mjs +1548 -185
  21. package/lib/adapter/adapter.test.js +106 -0
  22. package/lib/adapter/aws-cache-store.d.ts +489 -0
  23. package/lib/adapter/aws-cache-store.js +1004 -0
  24. package/lib/adapter/aws-cache-store.test.d.ts +1 -0
  25. package/lib/adapter/aws-cache-store.test.js +361 -0
  26. package/lib/adapter/build-outputs.d.ts +80 -0
  27. package/lib/adapter/build-outputs.js +852 -0
  28. package/lib/adapter/build-outputs.test.d.ts +1 -0
  29. package/lib/adapter/build-outputs.test.js +861 -0
  30. package/lib/adapter/cache-handler.d.ts +9 -2
  31. package/lib/adapter/cache-handler.js +72 -96
  32. package/lib/adapter/cache-handler.mjs +18956 -17774
  33. package/lib/adapter/cache-handler.test.js +263 -31
  34. package/lib/adapter/cache-kinds.d.ts +32 -0
  35. package/lib/adapter/cache-kinds.js +35 -0
  36. package/lib/adapter/cache-kinds.test.d.ts +1 -0
  37. package/lib/adapter/cache-kinds.test.js +69 -0
  38. package/lib/adapter/cache-utils.d.ts +90 -0
  39. package/lib/adapter/cache-utils.js +199 -4
  40. package/lib/adapter/cache-utils.test.d.ts +1 -0
  41. package/lib/adapter/cache-utils.test.js +179 -0
  42. package/lib/adapter/function-groups.d.ts +215 -0
  43. package/lib/adapter/function-groups.js +1099 -0
  44. package/lib/adapter/function-groups.test.d.ts +1 -0
  45. package/lib/adapter/function-groups.test.js +743 -0
  46. package/lib/adapter/init-cache.d.ts +6 -0
  47. package/lib/adapter/init-cache.js +245 -0
  48. package/lib/adapter/init-cache.test.d.ts +1 -0
  49. package/lib/adapter/init-cache.test.js +137 -0
  50. package/lib/adapter/local-file-cache-handler.d.ts +23 -0
  51. package/lib/adapter/local-file-cache-handler.js +52 -1
  52. package/lib/adapter/memory-cache-handler.d.ts +26 -17
  53. package/lib/adapter/memory-cache-handler.js +32 -50
  54. package/lib/adapter/memory-cache-handler.test.js +105 -85
  55. package/lib/adapter/s3-cache-handler.d.ts +179 -40
  56. package/lib/adapter/s3-cache-handler.js +807 -277
  57. package/lib/adapter/s3-cache-handler.test.js +1186 -31
  58. package/lib/adapter/use-cache-common.d.ts +90 -0
  59. package/lib/adapter/use-cache-common.js +250 -0
  60. package/lib/adapter/use-cache-common.test.d.ts +1 -0
  61. package/lib/adapter/use-cache-common.test.js +646 -0
  62. package/lib/adapter/use-cache-default-handler.d.ts +24 -0
  63. package/lib/adapter/use-cache-default-handler.js +90 -0
  64. package/lib/adapter/use-cache-default-handler.mjs +38645 -0
  65. package/lib/adapter/use-cache-handlers.test.d.ts +1 -0
  66. package/lib/adapter/use-cache-handlers.test.js +349 -0
  67. package/lib/adapter/use-cache-remote-handler.d.ts +26 -0
  68. package/lib/adapter/use-cache-remote-handler.js +186 -0
  69. package/lib/{image-optimization/handler.mjs → adapter/use-cache-remote-handler.mjs} +33572 -38798
  70. package/lib/generated-structs/OptionalNextjsApiProps.d.ts +59 -0
  71. package/lib/generated-structs/OptionalNextjsApiProps.js +3 -0
  72. package/lib/generated-structs/OptionalNextjsCacheProps.d.ts +15 -6
  73. package/lib/generated-structs/OptionalNextjsCacheProps.js +1 -1
  74. package/lib/generated-structs/OptionalNextjsContainersProps.d.ts +25 -11
  75. package/lib/generated-structs/OptionalNextjsContainersProps.js +1 -1
  76. package/lib/generated-structs/OptionalNextjsDistributionProps.d.ts +31 -9
  77. package/lib/generated-structs/OptionalNextjsDistributionProps.js +1 -1
  78. package/lib/generated-structs/OptionalNextjsPostDeployProps.d.ts +8 -0
  79. package/lib/generated-structs/OptionalNextjsPostDeployProps.js +1 -1
  80. package/lib/generated-structs/OptionalPostDeployCustomResourceProperties.d.ts +6 -4
  81. package/lib/generated-structs/OptionalPostDeployCustomResourceProperties.js +1 -1
  82. package/lib/index.d.ts +6 -7
  83. package/lib/index.js +2 -4
  84. package/lib/lambdas/post-deploy/prune-cache-bucket.d.ts +7 -3
  85. package/lib/lambdas/post-deploy/prune-cache-bucket.js +57 -71
  86. package/lib/lambdas/post-deploy/prune-cache-bucket.test.d.ts +1 -0
  87. package/lib/lambdas/post-deploy/prune-cache-bucket.test.js +114 -0
  88. package/lib/lambdas/post-deploy/prune-revalidation-table.d.ts +1 -0
  89. package/lib/lambdas/post-deploy/prune-revalidation-table.js +23 -31
  90. package/lib/lambdas/post-deploy/prune-revalidation-table.test.d.ts +1 -0
  91. package/lib/lambdas/post-deploy/prune-revalidation-table.test.js +71 -0
  92. package/lib/lambdas/post-deploy/prune-s3.d.ts +14 -5
  93. package/lib/lambdas/post-deploy/prune-s3.js +41 -12
  94. package/lib/lambdas/post-deploy/prune-s3.test.js +97 -18
  95. package/lib/lambdas/redeploy-stage/redeploy-stage-function.d.ts +13 -0
  96. package/lib/lambdas/redeploy-stage/redeploy-stage-function.js +55 -0
  97. package/lib/lambdas/redeploy-stage/redeploy-stage.d.ts +24 -0
  98. package/lib/lambdas/redeploy-stage/redeploy-stage.js +68 -0
  99. package/lib/lambdas/redeploy-stage/redeploy-stage.lambda.d.ts +20 -0
  100. package/lib/lambdas/redeploy-stage/redeploy-stage.lambda.js +24 -0
  101. package/lib/lambdas/redeploy-stage/redeploy-stage.test.d.ts +1 -0
  102. package/lib/lambdas/redeploy-stage/redeploy-stage.test.js +93 -0
  103. package/lib/nextjs-api.d.ts +102 -18
  104. package/lib/nextjs-api.js +338 -38
  105. package/lib/nextjs-api.test.js +537 -1
  106. package/lib/nextjs-build/global-containers.Dockerfile +15 -10
  107. package/lib/nextjs-build/nextjs-build.d.ts +339 -63
  108. package/lib/nextjs-build/nextjs-build.js +684 -327
  109. package/lib/nextjs-build/nextjs-build.test.d.ts +1 -0
  110. package/lib/nextjs-build/nextjs-build.test.js +412 -0
  111. package/lib/nextjs-build/patch-fetch.js +1 -1
  112. package/lib/nextjs-build/patch-fetch.test.js +166 -11
  113. package/lib/nextjs-build/regional-containers.Dockerfile +17 -10
  114. package/lib/nextjs-cache.d.ts +82 -4
  115. package/lib/nextjs-cache.js +186 -10
  116. package/lib/nextjs-cache.test.js +176 -1
  117. package/lib/nextjs-compute/nextjs-compute-base-props.d.ts +21 -8
  118. package/lib/nextjs-compute/nextjs-compute-base-props.js +41 -1
  119. package/lib/nextjs-compute/nextjs-compute-base-props.test.d.ts +1 -0
  120. package/lib/nextjs-compute/nextjs-compute-base-props.test.js +44 -0
  121. package/lib/nextjs-compute/nextjs-containers.d.ts +19 -0
  122. package/lib/nextjs-compute/nextjs-containers.js +57 -20
  123. package/lib/nextjs-compute/nextjs-functions.d.ts +122 -7
  124. package/lib/nextjs-compute/nextjs-functions.js +113 -67
  125. package/lib/nextjs-compute/nextjs-functions.test.d.ts +1 -0
  126. package/lib/nextjs-compute/nextjs-functions.test.js +99 -0
  127. package/lib/nextjs-distribution.d.ts +194 -23
  128. package/lib/nextjs-distribution.js +535 -62
  129. package/lib/nextjs-distribution.test.d.ts +1 -0
  130. package/lib/nextjs-distribution.test.js +697 -0
  131. package/lib/nextjs-post-deploy.d.ts +13 -5
  132. package/lib/nextjs-post-deploy.js +13 -10
  133. package/lib/nextjs-post-deploy.test.d.ts +1 -0
  134. package/lib/nextjs-post-deploy.test.js +34 -0
  135. package/lib/nextjs-static-assets.d.ts +17 -4
  136. package/lib/nextjs-static-assets.js +22 -5
  137. package/lib/root-constructs/nextjs-base-construct.d.ts +102 -58
  138. package/lib/root-constructs/nextjs-base-construct.js +139 -47
  139. package/lib/root-constructs/nextjs-global-containers.d.ts +18 -20
  140. package/lib/root-constructs/nextjs-global-containers.js +20 -71
  141. package/lib/root-constructs/nextjs-global-functions.d.ts +26 -28
  142. package/lib/root-constructs/nextjs-global-functions.js +52 -70
  143. package/lib/root-constructs/nextjs-regional-containers.d.ts +18 -5
  144. package/lib/root-constructs/nextjs-regional-containers.js +8 -15
  145. package/lib/root-constructs/nextjs-regional-functions.d.ts +22 -18
  146. package/lib/root-constructs/nextjs-regional-functions.js +8 -26
  147. package/lib/runtime/api-gateway-path.d.ts +34 -0
  148. package/lib/runtime/api-gateway-path.js +71 -0
  149. package/lib/runtime/api-gateway-path.test.d.ts +1 -0
  150. package/lib/runtime/api-gateway-path.test.js +65 -0
  151. package/lib/runtime/case-redirect.d.ts +22 -0
  152. package/lib/runtime/case-redirect.js +63 -0
  153. package/lib/runtime/case-redirect.test.d.ts +1 -0
  154. package/lib/runtime/case-redirect.test.js +32 -0
  155. package/lib/runtime/core.d.ts +234 -0
  156. package/lib/runtime/core.js +1213 -0
  157. package/lib/runtime/core.test.d.ts +1 -0
  158. package/lib/runtime/core.test.js +1678 -0
  159. package/lib/runtime/deployment-root.d.ts +13 -0
  160. package/lib/runtime/deployment-root.js +29 -0
  161. package/lib/runtime/dispatch.d.ts +331 -0
  162. package/lib/runtime/dispatch.js +751 -0
  163. package/lib/runtime/dispatch.test.d.ts +1 -0
  164. package/lib/runtime/dispatch.test.js +853 -0
  165. package/lib/runtime/entrypoints.d.ts +57 -0
  166. package/lib/runtime/entrypoints.js +115 -0
  167. package/lib/runtime/http/abort-signal.d.ts +6 -0
  168. package/lib/runtime/http/abort-signal.js +19 -0
  169. package/lib/runtime/http/abort-signal.test.d.ts +1 -0
  170. package/lib/runtime/http/abort-signal.test.js +49 -0
  171. package/lib/runtime/http/node-server.d.ts +38 -0
  172. package/lib/runtime/http/node-server.js +99 -0
  173. package/lib/runtime/http/node-server.test.d.ts +1 -0
  174. package/lib/runtime/http/node-server.test.js +96 -0
  175. package/lib/runtime/http/node-sink.d.ts +16 -0
  176. package/lib/runtime/http/node-sink.js +48 -0
  177. package/lib/runtime/http/node-sink.test.d.ts +1 -0
  178. package/lib/runtime/http/node-sink.test.js +66 -0
  179. package/lib/runtime/http/request.d.ts +62 -0
  180. package/lib/runtime/http/request.js +134 -0
  181. package/lib/runtime/http/request.test.d.ts +1 -0
  182. package/lib/runtime/http/request.test.js +103 -0
  183. package/lib/runtime/http/response.d.ts +77 -0
  184. package/lib/runtime/http/response.js +229 -0
  185. package/lib/runtime/http/response.test.d.ts +1 -0
  186. package/lib/runtime/http/response.test.js +178 -0
  187. package/lib/runtime/http/sink.d.ts +73 -0
  188. package/lib/runtime/http/sink.js +188 -0
  189. package/lib/runtime/http/sink.test.d.ts +1 -0
  190. package/lib/runtime/http/sink.test.js +257 -0
  191. package/lib/runtime/image-utils.d.ts +78 -0
  192. package/lib/runtime/image-utils.js +129 -0
  193. package/lib/runtime/image-utils.test.d.ts +1 -0
  194. package/lib/runtime/image-utils.test.js +237 -0
  195. package/lib/runtime/image.d.ts +76 -0
  196. package/lib/runtime/image.js +296 -0
  197. package/lib/runtime/image.test.d.ts +1 -0
  198. package/lib/runtime/image.test.js +405 -0
  199. package/lib/runtime/lambda-event.d.ts +4 -0
  200. package/lib/runtime/lambda-event.js +113 -0
  201. package/lib/runtime/lambda-event.test.d.ts +1 -0
  202. package/lib/runtime/lambda-event.test.js +119 -0
  203. package/lib/runtime/lambda.mjs +41595 -0
  204. package/lib/runtime/load-module.d.ts +8 -0
  205. package/lib/runtime/load-module.js +35 -0
  206. package/lib/runtime/manifest.d.ts +215 -0
  207. package/lib/runtime/manifest.js +118 -0
  208. package/lib/runtime/middleware.d.ts +43 -0
  209. package/lib/runtime/middleware.js +113 -0
  210. package/lib/runtime/middleware.test.d.ts +1 -0
  211. package/lib/runtime/middleware.test.js +259 -0
  212. package/lib/runtime/next-modules.d.ts +55 -0
  213. package/lib/runtime/next-modules.js +124 -0
  214. package/lib/runtime/next-modules.test.d.ts +1 -0
  215. package/lib/runtime/next-modules.test.js +34 -0
  216. package/lib/runtime/public-files.d.ts +32 -0
  217. package/lib/runtime/public-files.js +117 -0
  218. package/lib/runtime/public-files.test.d.ts +1 -0
  219. package/lib/runtime/public-files.test.js +97 -0
  220. package/lib/runtime/server.mjs +41588 -0
  221. package/lib/runtime/static-files.d.ts +50 -0
  222. package/lib/runtime/static-files.js +266 -0
  223. package/lib/runtime/static-files.test.d.ts +1 -0
  224. package/lib/runtime/static-files.test.js +118 -0
  225. package/lib/runtime/util.d.ts +28 -0
  226. package/lib/runtime/util.js +79 -0
  227. package/lib/utils/base-path.d.ts +106 -13
  228. package/lib/utils/base-path.js +254 -41
  229. package/lib/utils/base-path.test.js +219 -51
  230. package/lib/utils/get-architecture.d.ts +7 -8
  231. package/lib/utils/get-architecture.js +12 -13
  232. package/lib/utils/zip-directory.d.ts +14 -0
  233. package/lib/utils/zip-directory.js +121 -0
  234. package/lib/utils/zip-directory.test.d.ts +1 -0
  235. package/lib/utils/zip-directory.test.js +99 -0
  236. package/package.json +22 -3
  237. package/scripts/capture-adapter-fixture.mjs +325 -0
  238. package/scripts/e2e-deploy.sh +398 -0
  239. package/scripts/e2e-harness/README.md +707 -0
  240. package/scripts/e2e-harness/app.js +370 -0
  241. package/scripts/e2e-harness/common.sh +249 -0
  242. package/scripts/e2e-harness/screen.mjs +302 -0
  243. package/scripts/e2e-harness/stage-proxy.mjs +145 -0
  244. package/scripts/e2e-harness/type-filters.mjs +47 -0
  245. package/scripts/e2e-logs.sh +75 -0
  246. package/scripts/e2e-offline.sh +91 -0
  247. package/scripts/e2e-sweep.sh +370 -0
  248. package/scripts/e2e-warm.sh +114 -0
  249. package/scripts/zero-config-build.mjs +418 -0
  250. package/docs/1k-concurrent-users-executions.png +0 -0
  251. package/docs/plans/adapter-runtime-release.md +0 -362
  252. package/docs/plans/phase-0-image-function-opt-in.md +0 -295
  253. package/lib/generated-structs/OptionalDockerImageFunctionProps.d.ts +0 -425
  254. package/lib/generated-structs/OptionalDockerImageFunctionProps.js +0 -3
  255. package/lib/image-optimization/handler-utils.d.ts +0 -34
  256. package/lib/image-optimization/handler-utils.js +0 -83
  257. package/lib/image-optimization/handler-utils.test.js +0 -178
  258. package/lib/nextjs-build/functions.Dockerfile +0 -31
  259. package/lib/nextjs-compute/nextjs-image-function.d.ts +0 -53
  260. package/lib/nextjs-compute/nextjs-image-function.js +0 -59
  261. package/lib/utils/experimental-flags.d.ts +0 -24
  262. package/lib/utils/experimental-flags.js +0 -30
  263. /package/lib/{image-optimization/handler-utils.test.d.ts → adapter/adapter.test.d.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
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 debug2(...args) {
187
- if (!debug2.enabled) {
186
+ function debug3(...args) {
187
+ if (!debug3.enabled) {
188
188
  return;
189
189
  }
190
- const self = debug2;
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
- debug2.namespace = namespace;
221
- debug2.useColors = createDebug.useColors();
222
- debug2.color = createDebug.selectColor(namespace);
223
- debug2.extend = extend;
224
- debug2.destroy = createDebug.destroy;
225
- Object.defineProperty(debug2, "enabled", {
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(debug2);
243
+ createDebug.init(debug3);
244
244
  }
245
- return debug2;
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(debug2) {
765
- debug2.inspectOpts = {};
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
- debug2.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
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 { CachedRouteKind } from "next/dist/server/response-cache/index.js";
797
- import { join } from "node:path";
796
+ import { join as join3 } from "node:path";
798
797
  import { fileURLToPath } from "node:url";
799
- import { readFile, writeFile, mkdir } from "node:fs/promises";
800
- import { existsSync } from "node:fs";
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
- data: Array.from(value)
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/adapter.mts
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
- var adapter = {
844
- name: "cdk-nextjs-adapter",
845
- async modifyConfig(config, { phase }) {
846
- if (phase === "phase-production-build") {
847
- return {
848
- ...config,
849
- output: "standalone",
850
- cacheHandler: config.cacheHandler ? config.cacheHandler : fileURLToPath(import.meta.resolve("cdk-nextjs/cache-handler")),
851
- images: {
852
- ...config.images,
853
- customCacheHandler: config.images.customCacheHandler ? config.images.customCacheHandler : true
854
- // TODO: remove in Next.js 17
855
- }
856
- };
857
- }
858
- return config;
859
- },
860
- async onBuildComplete(ctx) {
861
- const cacheDir = process.env.CDK_NEXTJS_INIT_CACHE_DIR || join(ctx.distDir, "cdk-nextjs-init-cache");
862
- await mkdir(cacheDir, { recursive: true });
863
- debug(`Init cache directory: ${cacheDir}`);
864
- const prerenderGroups = groupPrerendersByBasePath(ctx.outputs.prerenders);
865
- debug(`Prerender groups: ${prerenderGroups.size} groups`);
866
- const prerenderPaths = Array.from(prerenderGroups.keys());
867
- const routeToCacheKind = getRouteToCacheKindMap(
868
- ctx.outputs,
869
- prerenderPaths
870
- );
871
- debug(`Route mapping: ${routeToCacheKind.size} routes`);
872
- for (const [basePath, prerenders] of prerenderGroups) {
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
- debug(`Processing ${basePath} (${kind})`);
884
- const htmlPrerender = prerenders.find((p) => p.pathname === basePath);
885
- const rscPrerender = prerenders.find(
886
- (p) => p.pathname === `${basePath}.rsc`
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
- const segmentPrerenders = prerenders.filter(
889
- (p) => p.pathname.includes(".segments/")
890
- );
891
- if (!htmlPrerender && !rscPrerender) {
892
- debug(`SKIP: No prerender files found for ${basePath}`);
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
- let cacheEntry;
896
- if (kind === CachedRouteKind.APP_PAGE) {
897
- const html = await readPrerenderAsText(htmlPrerender);
898
- if (!html) {
899
- debug(`SKIP: No HTML content for APP_PAGE ${basePath}`);
900
- continue;
901
- }
902
- const rscData = await readPrerenderAsBuffer(rscPrerender);
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
- const headers = htmlPrerender?.fallback?.initialHeaders || {};
927
- cacheEntry = {
928
- lastModified: Date.now(),
929
- value: {
930
- kind: CachedRouteKind.APP_ROUTE,
931
- body,
932
- headers,
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 cacheKey = basePath === "/" ? "index" : basePath.replace(/^\//, "").replace(/\//g, "/");
941
- const cacheFilePath = join(cacheDir, `${cacheKey}.json`);
942
- await mkdir(
943
- join(cacheDir, cacheKey.split("/").slice(0, -1).join("/")),
944
- {
945
- recursive: true
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
- await writeFile(cacheFilePath, serializeCacheValue(cacheEntry));
949
- debug(`Created cache entry: ${cacheFilePath}`);
950
- } catch (error) {
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
- var adapter_default = adapter;
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 && existsSync(prerender.fallback.filePath)) {
959
- return await readFile(prerender.fallback.filePath, "utf-8");
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 && existsSync(prerender.fallback.filePath)) {
965
- return await readFile(prerender.fallback.filePath);
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 && existsSync(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
- function getRouteToCacheKindMap(outputs, prerenderPaths) {
981
- const routeToCacheKind = /* @__PURE__ */ new Map();
982
- const matchesDynamicRoute = (path, pattern) => {
983
- if (!pattern.includes("[")) {
984
- return path === pattern;
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
- const patternParts = pattern.split("/");
987
- const pathParts = path.split("/");
988
- if (patternParts.length !== pathParts.length) {
989
- return false;
2362
+ if (phase === "phase-production-server") {
2363
+ return {
2364
+ ...config,
2365
+ cacheHandlers: useCacheHandlers(config.cacheHandlers)
2366
+ };
990
2367
  }
991
- return patternParts.every((patternPart, i) => {
992
- if (patternPart.startsWith("[") && patternPart.endsWith("]")) {
993
- return true;
994
- }
995
- return patternPart === pathParts[i];
996
- });
997
- };
998
- for (const appPage of outputs.appPages) {
999
- routeToCacheKind.set(appPage.pathname, CachedRouteKind.APP_PAGE);
1000
- if (appPage.pathname.includes("[")) {
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
- for (const appRoute of outputs.appRoutes) {
1009
- routeToCacheKind.set(appRoute.pathname, CachedRouteKind.APP_ROUTE);
1010
- if (appRoute.pathname.includes("[")) {
1011
- for (const prerenderPath of prerenderPaths) {
1012
- if (matchesDynamicRoute(prerenderPath, appRoute.pathname)) {
1013
- routeToCacheKind.set(prerenderPath, CachedRouteKind.APP_ROUTE);
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
- return routeToCacheKind;
1019
- }
1020
- function groupPrerendersByBasePath(prerenders) {
1021
- const prerenderGroups = /* @__PURE__ */ new Map();
1022
- for (const prerender of prerenders) {
1023
- let basePath = prerender.pathname;
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
- return prerenderGroups;
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