cdk-nextjs 0.6.2 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +1551 -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 +857 -0
  28. package/lib/adapter/build-outputs.test.d.ts +1 -0
  29. package/lib/adapter/build-outputs.test.js +863 -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 +1216 -0
  157. package/lib/runtime/core.test.d.ts +1 -0
  158. package/lib/runtime/core.test.js +1686 -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 +41600 -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 +71 -0
  213. package/lib/runtime/next-modules.js +144 -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 +41593 -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,418 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Build a throwaway Next.js app against a *packed and installed* cdk-nextjs, with
4
+ * no `adapterPath` in `next.config` — the zero-config path the README recommends
5
+ * (`NEXT_ADAPTER_PATH`), which nothing else in CI covers.
6
+ *
7
+ * pnpm compile && pnpm bundle
8
+ * node scripts/zero-config-build.mjs
9
+ *
10
+ * Why a packed tarball and not `examples/`. Every example takes cdk-nextjs as
11
+ * `link:../..`, and a link is exactly the setup zero-config cannot serve: the
12
+ * symlink resolves out of the project root, the adapter derives its cache handler
13
+ * path from its own location, and Turbopack rejects a `cacheHandler` outside
14
+ * `turbopack.root`. So the examples all set `adapterPath` explicitly, and the
15
+ * variable they would otherwise exercise goes untested. `npm install` of a `.tgz`
16
+ * extracts a real directory into the app's own `node_modules`, which is what a
17
+ * user's install looks like and the only shape under which
18
+ * `require.resolve("cdk-nextjs/adapter", { paths: [appDir] })` — the resolution
19
+ * `NextjsBuild.adapterPathEnv` performs — produces a usable path.
20
+ *
21
+ * Two cases, one install, because the second is nearly free:
22
+ *
23
+ * 1. `next.config.js`, the plain zero-config case.
24
+ * 2. `next.config.ts` using top-level `await`, built with
25
+ * `--experimental-next-config-strip-types` so Node.js's native TypeScript
26
+ * resolution loads it instead of the default swc-to-CJS transpile. This is the
27
+ * whole of what vercel/next.js's 18-file `next-config-ts-native-ts` family
28
+ * would tell us about cdk-nextjs: that `modifyConfig` still applies when the
29
+ * config arrives through the native loader. See docs/harness-coverage.md.
30
+ *
31
+ * No AWS, no deploy, no credentials. Two `next build`s and an `npm install`.
32
+ *
33
+ * Flags: `--dir <path>` to build somewhere fixed instead of a fresh mkdtemp, and
34
+ * `--keep` to leave the tree behind for inspection.
35
+ */
36
+ import { execFileSync, spawnSync } from "node:child_process";
37
+ import {
38
+ existsSync,
39
+ mkdirSync,
40
+ mkdtempSync,
41
+ readFileSync,
42
+ readdirSync,
43
+ realpathSync,
44
+ rmSync,
45
+ writeFileSync,
46
+ } from "node:fs";
47
+ import { createRequire } from "node:module";
48
+ import { tmpdir } from "node:os";
49
+ import { dirname, join, resolve } from "node:path";
50
+ import { fileURLToPath } from "node:url";
51
+
52
+ const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
53
+ const MODIFY_CONFIG_LOG = "Applying modifyConfig from cdk-nextjs-adapter";
54
+ /** `next`'s own warning when the native TS loader is not actually in use. */
55
+ const LEGACY_CONFIG_FALLBACK = "Falling back to legacy resolution";
56
+ const REACT_VERSION = "19.2.3";
57
+
58
+ function main() {
59
+ const args = process.argv.slice(2);
60
+ const dirFlag = args.indexOf("--dir");
61
+ const keep = args.includes("--keep");
62
+
63
+ const appDir = realpathSync(
64
+ dirFlag === -1
65
+ ? mkdtempSync(join(tmpdir(), "cdk-nextjs-zero-config-"))
66
+ : mkdirIfNeeded(resolve(args[dirFlag + 1])),
67
+ );
68
+ console.log(`zero-config: building in ${appDir}`);
69
+
70
+ try {
71
+ const tarball = pack(appDir);
72
+ writeFixture(appDir, tarball);
73
+ run("npm", ["install", "--no-audit", "--no-fund"], appDir);
74
+
75
+ // The same resolution `NextjsBuild.adapterPathEnv()` does, for the same
76
+ // reason: from the *app*, so the adapter and its sibling cache handler both
77
+ // live inside the project root.
78
+ const adapterPath = createRequire(join(appDir, "package.json")).resolve(
79
+ "cdk-nextjs/adapter",
80
+ );
81
+ console.log(`zero-config: NEXT_ADAPTER_PATH=${adapterPath}`);
82
+ assert(
83
+ adapterPath.startsWith(join(appDir, "node_modules", "cdk-nextjs") + "/"),
84
+ `the adapter resolved outside the app's node_modules: ${adapterPath}`,
85
+ );
86
+
87
+ writeConfigJs(appDir);
88
+ check(appDir, adapterPath, {
89
+ label: "next.config.js (default config loader)",
90
+ distDir: ".next-cjs",
91
+ buildArgs: [],
92
+ });
93
+
94
+ writeConfigTs(appDir);
95
+ check(appDir, adapterPath, {
96
+ label: "next.config.ts with top-level await (native TS loader)",
97
+ distDir: ".next-ts",
98
+ buildArgs: ["--experimental-next-config-strip-types"],
99
+ // The default loader transpiles to CJS, which cannot express top-level
100
+ // `await`; a silent fallback would surface as ERR_REQUIRE_ASYNC_MODULE
101
+ // rather than a pass, but assert on the warning too so a future fallback
102
+ // that *does* succeed cannot masquerade as native-loader coverage.
103
+ forbid: LEGACY_CONFIG_FALLBACK,
104
+ });
105
+
106
+ console.log("\nzero-config: both cases passed");
107
+ } finally {
108
+ if (keep) {
109
+ console.log(`zero-config: left ${appDir} in place (--keep)`);
110
+ } else {
111
+ rmSync(appDir, { recursive: true, force: true });
112
+ }
113
+ }
114
+ }
115
+
116
+ function mkdirIfNeeded(dir) {
117
+ mkdirSync(dir, { recursive: true });
118
+ return dir;
119
+ }
120
+
121
+ /**
122
+ * `npm pack` the working tree into `appDir`. Fails loudly if a file the package's
123
+ * `exports` points at is missing, because the tarball would otherwise be
124
+ * published-shaped but adapter-less and every assertion below would be about
125
+ * nothing.
126
+ */
127
+ function pack(appDir) {
128
+ const { exports } = JSON.parse(
129
+ readFileSync(join(repoRoot, "package.json"), "utf8"),
130
+ );
131
+ for (const { default: file } of Object.values(exports)) {
132
+ assert(
133
+ existsSync(join(repoRoot, file)),
134
+ `${file} is missing — run \`pnpm compile && pnpm bundle\` first.`,
135
+ );
136
+ }
137
+ const out = execFileSync(
138
+ "npm",
139
+ ["pack", "--ignore-scripts", "--json", "--pack-destination", appDir],
140
+ { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] },
141
+ );
142
+ const filename = JSON.parse(out)[0].filename;
143
+ const tarball = join(appDir, filename);
144
+ assert(existsSync(tarball), `npm pack reported ${filename}, which is absent`);
145
+ console.log(`zero-config: packed ${filename}`);
146
+ return tarball;
147
+ }
148
+
149
+ function writeFixture(appDir, tarball) {
150
+ // The exact `next` this repo's own lockfile resolved, not its `^16.3.5` range:
151
+ // a per-PR gate should fail on our changes, not on a patch release that landed
152
+ // between two runs of the same commit.
153
+ const nextVersion = JSON.parse(
154
+ readFileSync(
155
+ join(repoRoot, "node_modules", "next", "package.json"),
156
+ "utf8",
157
+ ),
158
+ ).version;
159
+
160
+ write(appDir, "package.json", {
161
+ name: "cdk-nextjs-zero-config-fixture",
162
+ version: "0.0.0",
163
+ private: true,
164
+ dependencies: {
165
+ // `file:` on a tarball extracts; `file:` on a directory would symlink,
166
+ // which is the case this fixture exists to avoid.
167
+ "cdk-nextjs": `file:${tarball}`,
168
+ next: nextVersion,
169
+ // Pinned for the same reason, and to the `react` in
170
+ // `examples/pnpm-workspace.yaml`'s catalog. Bump the two together.
171
+ react: REACT_VERSION,
172
+ "react-dom": REACT_VERSION,
173
+ },
174
+ devDependencies: {
175
+ "@types/node": "^24",
176
+ "@types/react": "^19",
177
+ typescript: "^5",
178
+ },
179
+ });
180
+
181
+ write(appDir, "tsconfig.json", {
182
+ compilerOptions: {
183
+ target: "ES2022",
184
+ lib: ["dom", "dom.iterable", "esnext"],
185
+ module: "esnext",
186
+ moduleResolution: "bundler",
187
+ jsx: "preserve",
188
+ strict: true,
189
+ noEmit: true,
190
+ skipLibCheck: true,
191
+ esModuleInterop: true,
192
+ resolveJsonModule: true,
193
+ isolatedModules: true,
194
+ incremental: true,
195
+ },
196
+ include: ["**/*.ts", "**/*.tsx", ".next-cjs/types/**/*.ts"],
197
+ exclude: ["node_modules"],
198
+ });
199
+
200
+ write(
201
+ appDir,
202
+ "app/layout.tsx",
203
+ `export default function RootLayout({
204
+ children,
205
+ }: {
206
+ children: React.ReactNode;
207
+ }) {
208
+ return (
209
+ <html lang="en">
210
+ <body>{children}</body>
211
+ </html>
212
+ );
213
+ }
214
+ `,
215
+ );
216
+
217
+ write(
218
+ appDir,
219
+ "app/page.tsx",
220
+ `export default function Page() {
221
+ return <main>zero-config</main>;
222
+ }
223
+ `,
224
+ );
225
+
226
+ // An ISR route, so \`onBuildComplete\` has a prerender to seed and the init
227
+ // cache is a real assertion rather than an empty directory.
228
+ write(
229
+ appDir,
230
+ "app/isr/[id]/page.tsx",
231
+ `export const revalidate = 60;
232
+
233
+ export function generateStaticParams() {
234
+ return [{ id: "1" }];
235
+ }
236
+
237
+ export default async function Page({
238
+ params,
239
+ }: {
240
+ params: Promise<{ id: string }>;
241
+ }) {
242
+ const { id } = await params;
243
+ return <main>isr {id}</main>;
244
+ }
245
+ `,
246
+ );
247
+ }
248
+
249
+ /** Zero-config: `distDir` and nothing else. No `adapterPath`. */
250
+ function writeConfigJs(appDir) {
251
+ rmSync(join(appDir, "next.config.ts"), { force: true });
252
+ write(
253
+ appDir,
254
+ "next.config.js",
255
+ `/** @type {import("next").NextConfig} */
256
+ module.exports = {
257
+ distDir: ".next-cjs",
258
+ };
259
+ `,
260
+ );
261
+ }
262
+
263
+ /**
264
+ * Also zero-config, but reached through Node.js's native TypeScript resolution:
265
+ * top-level `await` is what the default swc-to-CJS loader cannot express, so this
266
+ * file only loads under `--experimental-next-config-strip-types`. `distDir` comes
267
+ * *out* of the await, which makes every path assertion below evidence that the
268
+ * awaited value reached Next.js.
269
+ */
270
+ function writeConfigTs(appDir) {
271
+ rmSync(join(appDir, "next.config.js"), { force: true });
272
+ write(
273
+ appDir,
274
+ "next.config.ts",
275
+ `import type { NextConfig } from "next";
276
+
277
+ const resolved = await Promise.resolve({ distDir: ".next-ts" });
278
+
279
+ const nextConfig: NextConfig = { ...resolved };
280
+
281
+ export default nextConfig;
282
+ `,
283
+ );
284
+ }
285
+
286
+ function check(appDir, adapterPath, { label, distDir, buildArgs, forbid }) {
287
+ console.log(`\nzero-config: === ${label} ===`);
288
+ const dist = join(appDir, distDir);
289
+ rmSync(dist, { recursive: true, force: true });
290
+
291
+ const output = build(appDir, buildArgs, adapterPath);
292
+
293
+ assert(
294
+ output.includes(MODIFY_CONFIG_LOG),
295
+ `"${MODIFY_CONFIG_LOG}" is absent from the build log — the adapter was not loaded, so NEXT_ADAPTER_PATH did not take effect.`,
296
+ );
297
+ if (forbid) {
298
+ assert(!output.includes(forbid), `the build log contains "${forbid}"`);
299
+ }
300
+
301
+ // What `modifyConfig` returned, as the build recorded it. Covers the half of
302
+ // zero-config that the log line does not: that the adapter injected a cache
303
+ // handler resolved from its own installed location.
304
+ const required = readJson(join(dist, "required-server-files.json"));
305
+ assert(
306
+ required.config.adapterPath === adapterPath,
307
+ `required-server-files.json records adapterPath ${required.config.adapterPath}, expected ${adapterPath}`,
308
+ );
309
+ // Relative to `distDir`, which is how Next.js stores it.
310
+ const cacheHandler = resolve(dist, required.config.cacheHandler);
311
+ assert(
312
+ cacheHandler ===
313
+ join(
314
+ appDir,
315
+ "node_modules",
316
+ "cdk-nextjs",
317
+ "lib",
318
+ "adapter",
319
+ "cache-handler.mjs",
320
+ ) && existsSync(cacheHandler),
321
+ `cacheHandler resolved to ${cacheHandler}, which is not the installed package's`,
322
+ );
323
+ assert(
324
+ required.config.images.customCacheHandler === true,
325
+ "images.customCacheHandler was not set by modifyConfig",
326
+ );
327
+
328
+ // `onBuildComplete`: the manifest and the staging tree that replace
329
+ // `output: "standalone"`.
330
+ const adapterDir = join(dist, "cdk-nextjs-adapter");
331
+ const manifest = readJson(join(adapterDir, "manifest.json"));
332
+ assert(manifest.version === 1, `manifest version ${manifest.version}`);
333
+ for (const route of ["/", "/isr/[id]"]) {
334
+ assert(
335
+ manifest.entrypoints[route],
336
+ `manifest has no entrypoint for ${route}`,
337
+ );
338
+ const staged = join(
339
+ adapterDir,
340
+ "app",
341
+ manifest.entrypoints[route].filePath,
342
+ );
343
+ assert(
344
+ existsSync(staged),
345
+ `${route}'s entrypoint is not staged: ${staged}`,
346
+ );
347
+ }
348
+
349
+ const seeded = jsonFilesIn(join(dist, "cdk-nextjs-init-cache"));
350
+ assert(seeded > 0, "the init cache holds no entries");
351
+
352
+ console.log(
353
+ `zero-config: ok — ${Object.keys(manifest.entrypoints).length} entrypoints, ${seeded} seeded cache entries`,
354
+ );
355
+ }
356
+
357
+ function build(appDir, buildArgs, adapterPath) {
358
+ const next = join(appDir, "node_modules", ".bin", "next");
359
+ console.log(`$ next build ${buildArgs.join(" ")} (in ${appDir})`);
360
+ const result = spawnSync(next, ["build", ...buildArgs], {
361
+ cwd: appDir,
362
+ encoding: "utf8",
363
+ env: {
364
+ ...process.env,
365
+ // The point of the exercise: no `adapterPath` in `next.config`.
366
+ NEXT_ADAPTER_PATH: adapterPath,
367
+ NEXT_TELEMETRY_DISABLED: "1",
368
+ // Not set by CDK here, so the adapter defaults it inside `distDir` — which
369
+ // is where the init-cache assertion looks.
370
+ CDK_NEXTJS_INIT_CACHE_DIR: "",
371
+ },
372
+ });
373
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
374
+ process.stdout.write(output);
375
+ if (result.error) throw result.error;
376
+ assert(result.status === 0, `next build exited with ${result.status}`);
377
+ return output;
378
+ }
379
+
380
+ function jsonFilesIn(dir) {
381
+ if (!existsSync(dir)) return 0;
382
+ let count = 0;
383
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
384
+ count += entry.isDirectory()
385
+ ? jsonFilesIn(join(dir, entry.name))
386
+ : entry.name.endsWith(".json")
387
+ ? 1
388
+ : 0;
389
+ }
390
+ return count;
391
+ }
392
+
393
+ function readJson(path) {
394
+ assert(existsSync(path), `${path} was not written`);
395
+ return JSON.parse(readFileSync(path, "utf8"));
396
+ }
397
+
398
+ function write(appDir, relative, contents) {
399
+ const path = join(appDir, relative);
400
+ mkdirSync(dirname(path), { recursive: true });
401
+ writeFileSync(
402
+ path,
403
+ typeof contents === "string"
404
+ ? contents
405
+ : `${JSON.stringify(contents, null, 2)}\n`,
406
+ );
407
+ }
408
+
409
+ function run(cmd, args, cwd) {
410
+ console.log(`$ ${cmd} ${args.join(" ")} (in ${cwd})`);
411
+ execFileSync(cmd, args, { cwd, stdio: ["ignore", "inherit", "inherit"] });
412
+ }
413
+
414
+ function assert(condition, message) {
415
+ if (!condition) throw new Error(`zero-config: ${message}`);
416
+ }
417
+
418
+ main();