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
@@ -0,0 +1,398 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # NEXT_TEST_DEPLOY_SCRIPT_PATH for the Next.js adapter compatibility harness.
4
+ #
5
+ # Runs with `cwd` set to the isolated temporary app the harness created for one
6
+ # test file, and must print the deployment URL - and nothing else - to stdout.
7
+ #
8
+ # @see https://nextjs.org/docs/app/api-reference/adapters/testing-adapters
9
+ # @see scripts/e2e-harness/README.md
10
+ set -euo pipefail
11
+
12
+ # stdout is the URL channel. Keep a copy of it on fd 3 and point fd 1 at stderr
13
+ # so that every command below, and anything it spawns, is diagnostics.
14
+ exec 3>&1
15
+ exec 1>&2
16
+
17
+ : "${ADAPTER_DIR:?must be set to the cdk-nextjs checkout (the harness passes it through)}"
18
+ HARNESS_DIR="$ADAPTER_DIR/scripts/e2e-harness"
19
+ # shellcheck source=scripts/e2e-harness/common.sh
20
+ source "$HARNESS_DIR/common.sh"
21
+
22
+ APP_DIR="$PWD"
23
+ CDK_BIN="${CDK_BIN:-$ADAPTER_DIR/node_modules/.bin/cdk}"
24
+ # Every file the package's `exports` points at: the adapter, and the cache
25
+ # handlers it resolves through them (src/adapter/adapter.mts). Read first, so
26
+ # that a failed read ends the script rather than checking nothing.
27
+ EXPORTED="$(node -p 'Object.values(require(process.argv[1]).exports).map((e) => e.default).join("\n")' "$ADAPTER_DIR/package.json")"
28
+ while IFS= read -r required; do
29
+ if [ ! -f "$ADAPTER_DIR/$required" ]; then
30
+ echo "harness: $ADAPTER_DIR/$required is missing. Run \`pnpm bundle && pnpm compile\` in $ADAPTER_DIR." >&2
31
+ exit 1
32
+ fi
33
+ done <<<"$EXPORTED"
34
+ if [ ! -x "$CDK_BIN" ]; then
35
+ echo "harness: no CDK CLI at $CDK_BIN. Run \`pnpm i\` in $ADAPTER_DIR, or set CDK_BIN." >&2
36
+ exit 1
37
+ fi
38
+
39
+ STACK_NAME="$(harness_stack_name)"
40
+ printf '%s\n' "$STACK_NAME" >"$HARNESS_STACK_FILE"
41
+ echo "harness: app=$APP_DIR stack=$STACK_NAME"
42
+
43
+ # A previous file's deploy that outran its per-file timeout was killed here, but
44
+ # its `cdk deploy` can outlive it, and a full update keeps going server-side.
45
+ # Deploying over either would fail or interleave two fixtures, so wait for both
46
+ # to settle: the process for HARNESS_ORPHAN_WAIT (10 minutes) before it is
47
+ # killed, the stack status for 30 minutes. The status checks below and
48
+ # `cdk deploy` itself report what is left. (A change set that was never
49
+ # executed, REVIEW_IN_PROGRESS, never settles by itself.)
50
+ harness_wait_for_cdk "$STACK_NAME"
51
+ for _ in $(seq 1 120); do
52
+ STACK_STATUS="$(harness_stack_status "$STACK_NAME")" || break
53
+ case "$STACK_STATUS" in
54
+ REVIEW_IN_PROGRESS) break ;;
55
+ *_IN_PROGRESS) echo "harness: $STACK_NAME is $STACK_STATUS; waiting" ;;
56
+ *) break ;;
57
+ esac
58
+ sleep 15
59
+ done
60
+
61
+ # The harness creates the app with `skipInstall`, so there is no node_modules yet.
62
+ #
63
+ # Which package manager installs it is the app's choice, not ours: a fixture can
64
+ # override `packageJson.packageManager` (`test/lib/next-modes/base.ts` merges it in
65
+ # verbatim), and `corepack pnpm` then hard-refuses with "This project is configured
66
+ # to use npm because …/package.json has a packageManager field" - which is how
67
+ # test/e2e/handle-non-hoisted-swc-helpers, the one fixture that pins `npm@10.9.2`,
68
+ # failed its deploy outright. So read the field and honor it.
69
+ #
70
+ # corepack is only used for pnpm, where the pinned version matters and this repo
71
+ # already relies on it. `npm` ships with node, and going through corepack would
72
+ # add a download of a specific npm build for no benefit.
73
+ PM="$(node -e 'const f=require("path").join(process.argv[1],"package.json");let s="pnpm";try{s=(require(f).packageManager||"pnpm").split("@")[0]}catch{};process.stdout.write(s)' "$APP_DIR")"
74
+ case "$PM" in
75
+ pnpm)
76
+ if command -v corepack >/dev/null 2>&1; then PM_CMD=(corepack pnpm); else PM_CMD=(pnpm); fi
77
+ # Without it pnpm walks up out of /tmp looking for a workspace root.
78
+ INSTALL_ARGS=(--ignore-workspace --prefer-offline)
79
+ ;;
80
+ *)
81
+ PM_CMD=("$PM")
82
+ # No `--prefer-offline` here, deliberately. pnpm treats it as a hint and falls
83
+ # back to the network when a cached packument has no matching version; npm
84
+ # treats the stale packument as the answer and fails outright with
85
+ #
86
+ # npm error code ETARGET
87
+ # npm error notarget No matching version found for next@16.3.5.
88
+ #
89
+ # even though the registry does have it. Any recently published version in the
90
+ # tree hits this - reproduced with `next` itself and, on a warmer cache, with
91
+ # its transitive `postcss@8.5.23` - so it fails whenever this machine's npm
92
+ # cache predates the `next` release the fixtures pin. It is what made
93
+ # test/e2e/handle-non-hoisted-swc-helpers, the one fixture that pins
94
+ # `npm@10.9.2`, look like a deploy failure.
95
+ INSTALL_ARGS=(--no-audit --no-fund)
96
+ ;;
97
+ esac
98
+ echo "harness: package manager $PM (${PM_CMD[*]})"
99
+ # The `build` script the harness writes always chains `pnpm post-build`
100
+ # (hardcoded in base.ts), so pnpm has to be on PATH whatever $PM is - and for a
101
+ # fixture that pins a *different* package manager, pnpm refuses to run any script
102
+ # at all:
103
+ #
104
+ # ERROR This project is configured to use npm
105
+ #
106
+ # which is pnpm honoring `packageJson.packageManager`. There is no way to reach
107
+ # next.js's hardcoded `pnpm post-build`, so the check has to be turned off rather
108
+ # than routed around. This is the documented opt-out and it only affects the
109
+ # packageManager assertion, not resolution or the lockfile. Exported for the
110
+ # `next build` below, which is what runs the chained script.
111
+ export npm_config_package_manager_strict=false
112
+ # Run a command without any credentials: the fixture's install and build are
113
+ # where third-party code runs, and neither needs AWS - only `cdk deploy` and the
114
+ # aws CLI calls below do. In a subshell so they keep them. The config and
115
+ # credentials files are pointed away too, for a local run whose credentials live
116
+ # there rather than in the environment; and the Actions runtime and OIDC request
117
+ # tokens go with them, since with `id-token: write` those mint a token that
118
+ # assumes the deploy role.
119
+ without_credentials() (
120
+ while IFS= read -r var; do unset "$var"; done < <(compgen -e | grep -E '^(AWS_|ACTIONS_ID_TOKEN_REQUEST_|ACTIONS_RUNTIME_TOKEN$)')
121
+ export AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null
122
+ "$@"
123
+ )
124
+ without_credentials "${PM_CMD[@]}" install "${INSTALL_ARGS[@]}"
125
+
126
+ # Make the adapter resolvable as a package rather than a loose file: the adapter
127
+ # resolves its own cache handler with
128
+ # `import.meta.resolve("cdk-nextjs/cache-handler")` (src/adapter/adapter.mts), so
129
+ # it has to be reachable through a package boundary with this repo's `exports`
130
+ # map. Same shape as `examples/app-playground`'s `sync-cdk-nextjs` script. After
131
+ # `pnpm install`, which prunes directories it does not know about.
132
+ LOCAL_PKG="node_modules/cdk-nextjs"
133
+ mkdir -p "$LOCAL_PKG/lib/adapter"
134
+ cp "$ADAPTER_DIR/package.json" "$LOCAL_PKG/package.json"
135
+ # Every bundle, not the list checked above, so a new `exports` entry is copied
136
+ # without anyone remembering to add it here.
137
+ cp "$ADAPTER_DIR"/lib/adapter/*.mjs "$LOCAL_PKG/lib/adapter/"
138
+ # `next build` reads this into `config.adapterPath`
139
+ # (next/dist/server/config-shared.js), which is how the harness's fixtures - who
140
+ # know nothing about cdk-nextjs - get built through our adapter.
141
+ export NEXT_ADAPTER_PATH="$APP_DIR/$LOCAL_PKG/lib/adapter/adapter.mjs"
142
+ # Feeds the fixture's own `post-build` marker line, and the runtime's
143
+ # `?dpl=` skew handling if a test exercises it. Per app directory rather than per
144
+ # stack: with one shared stack the stack name is a constant, and two builds
145
+ # sharing a deployment ID is exactly the skew this is supposed to detect.
146
+ export NEXT_DEPLOYMENT_ID="$(harness_app_id "$APP_DIR")"
147
+ export NEXT_TELEMETRY_DISABLED=1
148
+ # The harness appends a snippet to every fixture's next.config that aliases this
149
+ # to `__NEXT_TEST_MODE` (`test/lib/next-modes/base.ts`, "alias __NEXT_TEST_MODE
150
+ # for next-deploy"), and `define-env.ts` inlines `process.env.__NEXT_TEST_MODE`
151
+ # into the server bundle at build time. Unset, it inlines as `false` and the
152
+ # test-only branches are dead-code eliminated - including the
153
+ # `<!-- PPR_BOUNDARY_SENTINEL -->` chunk that separates a PPR response's static
154
+ # shell from its dynamic part. `splitResponseWithPPRSentinel`
155
+ # (`test/lib/e2e-utils/ppr.ts`) then puts the entire document in `staticPart`, and
156
+ # every `expect(result.static$('#dynamic-thing').length).toBe(0)` fails. Vercel
157
+ # passes it as `--build-env NEXT_PRIVATE_TEST_MODE=e2e`; this is the same thing.
158
+ export NEXT_PRIVATE_TEST_MODE=e2e
159
+
160
+ echo "harness: building with NEXT_ADAPTER_PATH=$NEXT_ADAPTER_PATH"
161
+ # `run build`, not `build`: pnpm accepts the bare script name but npm does not.
162
+ without_credentials "${PM_CMD[@]}" run build 2>&1 | tee "$HARNESS_BUILD_LOG"
163
+
164
+ # The markers the harness parses out of the logs script's output
165
+ # (`test/lib/next-modes/next-deploy.ts`'s `parseIdsFromCliOutput`). The fixture's
166
+ # chained `post-build` prints its own copy into the build log; ours is written
167
+ # first and `.match()` takes the first hit, so these win.
168
+ #
169
+ # DEPLOYMENT_ID has to be the value the *build* inlined, not the stack name: next
170
+ # inlines `NEXT_DEPLOYMENT_ID` into every asset URL as `?dpl=`, and the harness
171
+ # compares those URLs against this marker (`base.ts`'s `getDeploymentIdQuery`).
172
+ # Reporting the stack name made `mdx`'s "should work with next/image" fail on a
173
+ # `?dpl=hrns-shared` that the app never emits.
174
+ BUILD_ID="$(cat .next/BUILD_ID)"
175
+ {
176
+ echo "BUILD_ID: $BUILD_ID"
177
+ echo "DEPLOYMENT_ID: $NEXT_DEPLOYMENT_ID"
178
+ # Flip to 1 once cdk-nextjs opts into `config.supportsImmutableAssets`; until
179
+ # then static assets are re-uploaded per deploy under the same keys.
180
+ echo "NEXT_SUPPORTS_IMMUTABLE_ASSETS: 0"
181
+ } >"$HARNESS_MARKERS_FILE"
182
+
183
+ export HARNESS_APP_DIR="$APP_DIR"
184
+ export HARNESS_STACK_NAME="$STACK_NAME"
185
+ echo "harness: deploying $STACK_NAME"
186
+ # Into one shared stack, so that only the first test file of a run pays for
187
+ # creating and propagating a CloudFront distribution. `--hotswap-fallback` takes
188
+ # the fast path when everything that changed is the function's code, its
189
+ # environment or a bucket deployment, and a full CloudFormation deployment
190
+ # otherwise. The one remaining reason for the latter is the distribution's cache
191
+ # behaviors, which change when a fixture's `public/` differs from the last one's -
192
+ # see the table in scripts/e2e-harness/README.md for what actually differs between
193
+ # two fixtures.
194
+ #
195
+ # `--output` inside the app directory: a shared cdk.out would have two shards'
196
+ # test files overwrite each other's assembly.
197
+ #
198
+ # The container types hotswap too - a new image and a changed environment are
199
+ # both task-definition changes - but a hotswap into ECS is a rolling deployment,
200
+ # and by default it keeps the old task serving until the new one is healthy.
201
+ # Minimum 0% lets ECS stop the previous fixture's task without waiting for that.
202
+ # Maximum stays at 200%: 100% would force stop-then-start, but the service has
203
+ # Availability Zone Rebalancing on, which rejects any maximum <= 100% with an
204
+ # `InvalidParameterException`. The overlap that leaves is covered twice: the
205
+ # hotswap blocks until the service is stable (one deployment left, running the
206
+ # desired count), and the target-group wait below until only the new task is
207
+ # registered. Bounded, because a fixture whose server never comes up would
208
+ # otherwise hold the shard until the job's timeout; the test file fails either
209
+ # way.
210
+ HOTSWAP_ARGS=(--hotswap-fallback)
211
+ case "$(harness_nextjs_type)" in
212
+ *-containers)
213
+ HOTSWAP_ARGS+=(
214
+ --hotswap-ecs-minimum-healthy-percent 0
215
+ --hotswap-ecs-maximum-healthy-percent 200
216
+ --hotswap-ecs-stabilization-timeout-seconds 600
217
+ )
218
+ ;;
219
+ esac
220
+ # Bounded by HARNESS_DEPLOY_TIMEOUT (30 minutes), which the slowest real deploy -
221
+ # a Global Containers warm-up creating its distribution, ~20 minutes - fits
222
+ # under. A deploy that hangs past it is stopped here instead of holding the
223
+ # shard; see `harness_wait_for_cdk`.
224
+ harness_run_bounded "${HARNESS_DEPLOY_TIMEOUT:-1800}" \
225
+ "$CDK_BIN" deploy "$STACK_NAME" \
226
+ --app "node $HARNESS_DIR/app.js" \
227
+ --output "$APP_DIR/$HARNESS_CDK_OUT" \
228
+ "${HOTSWAP_ARGS[@]}" \
229
+ --require-approval never \
230
+ --ci 2>&1 | tee "$HARNESS_DEPLOY_LOG"
231
+
232
+ URL="$(harness_stack_output "$STACK_NAME" HarnessUrl)"
233
+ if [ -z "$URL" ]; then
234
+ echo "harness: deploy reported success but $STACK_NAME has no HarnessUrl output" >&2
235
+ exit 1
236
+ fi
237
+ # The host in lowercase, as a browser reports it. An ALB's DNS name has capitals
238
+ # (`hrns-r-Nextj-7SK6...`), and the suite compares `browser.url()` with
239
+ # `next.url + path` exactly, so on the Containers types every such assertion
240
+ # failed on the host alone. Only the host: the path (Regional Functions' stage)
241
+ # is case-sensitive.
242
+ URL_REST="${URL#*://}"
243
+ URL_HOST="${URL_REST%%/*}"
244
+ URL="${URL%%://*}://$(printf '%s' "$URL_HOST" | tr '[:upper:]' '[:lower:]')${URL_REST#"$URL_HOST"}"
245
+
246
+ NEXTJS_TYPE="$(harness_nextjs_type)"
247
+ case "$NEXTJS_TYPE" in
248
+ *-containers)
249
+ # A hotswap into ECS returns once `runningCount` reaches `desiredCount`, which
250
+ # is before the load balancer routes to the new task: it is still in the
251
+ # target group's `initial` health check, and the ALB answers 503 until it
252
+ # passes. A full CloudFormation update waits for that, but leaves the old task
253
+ # `draining` behind it, still answering. So wait here, either way, until the
254
+ # target group holds exactly one target and it is `healthy`.
255
+ TARGET_GROUP_ARN="$(harness_stack_output "$STACK_NAME" TargetGroupArn)"
256
+ if [ -z "$TARGET_GROUP_ARN" ]; then
257
+ echo "harness: $STACK_NAME has no TargetGroupArn output; cannot wait for the new task" >&2
258
+ exit 1
259
+ fi
260
+ TARGETS=""
261
+ for _ in $(seq 1 60); do
262
+ TARGETS="$(aws elbv2 describe-target-health --target-group-arn "$TARGET_GROUP_ARN" \
263
+ --query 'TargetHealthDescriptions[].TargetHealth.State' --output text)"
264
+ [ "$TARGETS" = "healthy" ] && break
265
+ sleep 5
266
+ done
267
+ if [ "$TARGETS" != "healthy" ]; then
268
+ echo "harness: $TARGET_GROUP_ARN never settled on one healthy target (last: ${TARGETS:-none})" >&2
269
+ exit 1
270
+ fi
271
+ echo "harness: new task healthy in $TARGET_GROUP_ARN"
272
+ ;;
273
+ esac
274
+
275
+ if [ "$NEXTJS_TYPE" = "regional-containers" ]; then
276
+ # Nothing more to do: no distribution to invalidate, and no stage to put back.
277
+ :
278
+ elif [ "$NEXTJS_TYPE" = "regional-functions" ]; then
279
+ # No distribution to invalidate - API Gateway caches nothing unless told to.
280
+ # What there is instead is the stage in `$URL`, which the harness would strip
281
+ # from every request (see stage-proxy.mjs), so the suite is pointed at a
282
+ # localhost proxy that puts it back.
283
+ #
284
+ # One proxy per stack, reused by every test file: the API ID survives hotswaps
285
+ # and full updates alike, so the target does not change. It is restarted only
286
+ # when it is gone or its target differs (a stack that was deleted and
287
+ # recreated).
288
+ PROXY_PORT="$(harness_proxy_port "$STACK_NAME")"
289
+ PROXY_STATE="$(harness_proxy_state "$STACK_NAME")"
290
+ if PROXY_PID="$(harness_proxy_pid "$STACK_NAME")" \
291
+ && [ "$(cat "$PROXY_STATE.target" 2>/dev/null)" = "$URL" ]; then
292
+ echo "harness: reusing stage proxy $PROXY_PID on :$PROXY_PORT"
293
+ else
294
+ harness_stop_proxy "$STACK_NAME"
295
+ # Detached, and with none of this script's descriptors: the harness reads
296
+ # the URL from our stdout until EOF, so a child still holding fd 3 (or the
297
+ # stderr pipe) would hang it.
298
+ nohup node "$HARNESS_DIR/stage-proxy.mjs" "$PROXY_PORT" "$URL" \
299
+ </dev/null >"$PROXY_STATE.log" 2>&1 3>&- &
300
+ PROXY_PID="$!"
301
+ printf '%s' "$PROXY_PID" >"$PROXY_STATE.pid"
302
+ printf '%s' "$URL" >"$PROXY_STATE.target"
303
+ # Ready when *this* proxy says it is listening - it logs that from the
304
+ # `listen` callback - not when something answers on the port: a leftover
305
+ # proxy for another stack answering there would have the suite test the
306
+ # wrong deployment, while ours died on EADDRINUSE.
307
+ PROXY_UP=0
308
+ for _ in $(seq 1 50); do
309
+ if grep -q "^stage-proxy: http://127.0.0.1:$PROXY_PORT " "$PROXY_STATE.log" 2>/dev/null; then
310
+ PROXY_UP=1
311
+ break
312
+ fi
313
+ kill -0 "$PROXY_PID" 2>/dev/null || break
314
+ sleep 0.1
315
+ done
316
+ if [ "$PROXY_UP" != "1" ]; then
317
+ echo "harness: stage proxy for $STACK_NAME did not start on :$PROXY_PORT; its log:" >&2
318
+ cat "$PROXY_STATE.log" >&2 || true
319
+ kill "$PROXY_PID" 2>/dev/null || true
320
+ rm -f "$PROXY_STATE.pid" "$PROXY_STATE.target"
321
+ exit 1
322
+ fi
323
+ echo "harness: started stage proxy $PROXY_PID on :$PROXY_PORT -> $URL"
324
+ fi
325
+ URL="http://127.0.0.1:$PROXY_PORT"
326
+
327
+ # Then wait out the stage switch. A fixture whose `basePath` differs from the
328
+ # last one's changes the API's resource tree, so the deploy is a full
329
+ # CloudFormation update with a new stage deployment - and CloudFormation reports
330
+ # it complete before the stage serves it. Measured: the first requests after
331
+ # such a deploy answered 500, or a 403 `MissingAuthenticationTokenException`
332
+ # for the resource tree that was just replaced, with nothing in the Lambda's
333
+ # log. So wait until API Gateway stops answering with its own errors
334
+ # (`x-amzn-errortype`) or a 5xx. Bounded: a fixture can answer 500 on purpose,
335
+ # and that is the test's to report.
336
+ #
337
+ # One probe per kind of resource the tree has, because they did not become
338
+ # ready together: the base resource answered while `_next/static` (the S3
339
+ # integration) and the `{proxy+}` catch-all under it still 500'd. The build
340
+ # manifest is a real object; the catch-all probe is a path the app 404s.
341
+ BASE_PATH="$(node -e 'try{process.stdout.write(require(process.argv[1]).config.basePath||"")}catch{}' "$APP_DIR/.next/required-server-files.json")"
342
+ PROBES=("$BASE_PATH/" "$BASE_PATH/_next/static/$BUILD_ID/_buildManifest.js" "$BASE_PATH/__cdk-nextjs-harness-probe")
343
+ api_gateway_ready() {
344
+ local path head status
345
+ for path in "${PROBES[@]}"; do
346
+ head="$(curl -s -o /dev/null -D - "$URL$path" || true)"
347
+ status="$(printf '%s' "$head" | awk 'NR==1{print $2}')"
348
+ if [ -z "$status" ] || [ "${status:0:1}" = "5" ] \
349
+ || printf '%s' "$head" | grep -qi '^x-amzn-errortype:'; then
350
+ STATUS="$path -> ${status:-no answer}"
351
+ return 1
352
+ fi
353
+ done
354
+ STATUS="all ${#PROBES[@]} probes"
355
+ }
356
+ # Five rounds in a row, not one: the new deployment reaches API Gateway's fleet
357
+ # unevenly, and a test got the replaced tree's 403 right after all three
358
+ # probes had passed once. 40 x 3s overall: the replaced tree was measured
359
+ # still answering ~90s after CloudFormation reported UPDATE_COMPLETE.
360
+ READY_ROUNDS=0
361
+ for _ in $(seq 1 40); do
362
+ if api_gateway_ready; then
363
+ READY_ROUNDS=$((READY_ROUNDS + 1))
364
+ [ "$READY_ROUNDS" -ge 5 ] && break
365
+ else
366
+ READY_ROUNDS=0
367
+ fi
368
+ sleep 3
369
+ done
370
+ echo "harness: $URL ready: $STATUS"
371
+ else
372
+ # Then evict the previous test file's app from the edge.
373
+ #
374
+ # Two things make this mandatory rather than hygienic: the stack is shared, so
375
+ # the distribution has the last fixture's responses cached under the very paths
376
+ # this one is about to request; and a hotswap never runs CloudFormation, so the
377
+ # post-deploy custom resource that would normally invalidate never fires (its
378
+ # properties are pinned in app.js for exactly that reason). Blocking until it
379
+ # completes, because the first request the harness makes is the one that would
380
+ # read a stale response.
381
+ DISTRIBUTION_ID="$(harness_stack_output "$STACK_NAME" DistributionId)"
382
+ if [ -z "$DISTRIBUTION_ID" ]; then
383
+ echo "harness: $STACK_NAME has no DistributionId output; cannot invalidate" >&2
384
+ exit 1
385
+ fi
386
+ echo "harness: invalidating $DISTRIBUTION_ID"
387
+ INVALIDATION_ID="$(aws cloudfront create-invalidation \
388
+ --distribution-id "$DISTRIBUTION_ID" \
389
+ --paths '/*' \
390
+ --query 'Invalidation.Id' --output text)"
391
+ aws cloudfront wait invalidation-completed \
392
+ --distribution-id "$DISTRIBUTION_ID" \
393
+ --id "$INVALIDATION_ID"
394
+ echo "harness: invalidation $INVALIDATION_ID complete"
395
+ fi
396
+
397
+ echo "harness: deployed $STACK_NAME at $URL"
398
+ printf '%s\n' "$URL" >&3