@opennextjs/aws 3.1.4

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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +94 -0
  3. package/dist/adapters/binary.d.ts +1 -0
  4. package/dist/adapters/binary.js +63 -0
  5. package/dist/adapters/cache.d.ts +96 -0
  6. package/dist/adapters/cache.js +278 -0
  7. package/dist/adapters/config/index.d.ts +27 -0
  8. package/dist/adapters/config/index.js +21 -0
  9. package/dist/adapters/config/util.d.ts +28 -0
  10. package/dist/adapters/config/util.js +84 -0
  11. package/dist/adapters/dynamo-provider.d.ts +8 -0
  12. package/dist/adapters/dynamo-provider.js +42 -0
  13. package/dist/adapters/edge-adapter.d.ts +8 -0
  14. package/dist/adapters/edge-adapter.js +54 -0
  15. package/dist/adapters/image-optimization-adapter.d.ts +4 -0
  16. package/dist/adapters/image-optimization-adapter.js +193 -0
  17. package/dist/adapters/logger.d.ts +13 -0
  18. package/dist/adapters/logger.js +51 -0
  19. package/dist/adapters/middleware.d.ts +5 -0
  20. package/dist/adapters/middleware.js +84 -0
  21. package/dist/adapters/plugins/14.1/util.d.ts +1 -0
  22. package/dist/adapters/plugins/14.1/util.js +29 -0
  23. package/dist/adapters/plugins/image-optimization/image-optimization.d.ts +11 -0
  24. package/dist/adapters/plugins/image-optimization/image-optimization.js +14 -0
  25. package/dist/adapters/plugins/image-optimization/image-optimization.replacement.d.ts +11 -0
  26. package/dist/adapters/plugins/image-optimization/image-optimization.replacement.js +22 -0
  27. package/dist/adapters/plugins/without-routing/requestHandler.d.ts +2 -0
  28. package/dist/adapters/plugins/without-routing/requestHandler.js +10 -0
  29. package/dist/adapters/revalidate.d.ts +9 -0
  30. package/dist/adapters/revalidate.js +71 -0
  31. package/dist/adapters/server-adapter.d.ts +4 -0
  32. package/dist/adapters/server-adapter.js +27 -0
  33. package/dist/adapters/util.d.ts +10 -0
  34. package/dist/adapters/util.js +28 -0
  35. package/dist/adapters/warmer-function.d.ts +12 -0
  36. package/dist/adapters/warmer-function.js +80 -0
  37. package/dist/build/bundleNextServer.d.ts +1 -0
  38. package/dist/build/bundleNextServer.js +87 -0
  39. package/dist/build/compileConfig.d.ts +3 -0
  40. package/dist/build/compileConfig.js +64 -0
  41. package/dist/build/copyTracedFiles.d.ts +1 -0
  42. package/dist/build/copyTracedFiles.js +207 -0
  43. package/dist/build/createServerBundle.d.ts +3 -0
  44. package/dist/build/createServerBundle.js +228 -0
  45. package/dist/build/edge/createEdgeBundle.d.ts +20 -0
  46. package/dist/build/edge/createEdgeBundle.js +149 -0
  47. package/dist/build/generateOutput.d.ts +2 -0
  48. package/dist/build/generateOutput.js +239 -0
  49. package/dist/build/helper.d.ts +33 -0
  50. package/dist/build/helper.js +193 -0
  51. package/dist/build/patch/patchedAsyncStorage.d.ts +5 -0
  52. package/dist/build/patch/patchedAsyncStorage.js +16 -0
  53. package/dist/build/validateConfig.d.ts +2 -0
  54. package/dist/build/validateConfig.js +76 -0
  55. package/dist/build.d.ts +7 -0
  56. package/dist/build.js +533 -0
  57. package/dist/cache/incremental/s3-lite.d.ts +3 -0
  58. package/dist/cache/incremental/s3-lite.js +72 -0
  59. package/dist/cache/incremental/s3.d.ts +3 -0
  60. package/dist/cache/incremental/s3.js +44 -0
  61. package/dist/cache/incremental/types.d.ts +34 -0
  62. package/dist/cache/incremental/types.js +1 -0
  63. package/dist/cache/next-types.d.ts +62 -0
  64. package/dist/cache/next-types.js +1 -0
  65. package/dist/cache/tag/constants.d.ts +2 -0
  66. package/dist/cache/tag/constants.js +17 -0
  67. package/dist/cache/tag/dynamodb-lite.d.ts +3 -0
  68. package/dist/cache/tag/dynamodb-lite.js +174 -0
  69. package/dist/cache/tag/dynamodb.d.ts +3 -0
  70. package/dist/cache/tag/dynamodb.js +129 -0
  71. package/dist/cache/tag/types.d.ts +11 -0
  72. package/dist/cache/tag/types.js +1 -0
  73. package/dist/converters/aws-apigw-v1.d.ts +3 -0
  74. package/dist/converters/aws-apigw-v1.js +89 -0
  75. package/dist/converters/aws-apigw-v2.d.ts +3 -0
  76. package/dist/converters/aws-apigw-v2.js +99 -0
  77. package/dist/converters/aws-cloudfront.d.ts +3 -0
  78. package/dist/converters/aws-cloudfront.js +170 -0
  79. package/dist/converters/dummy.d.ts +7 -0
  80. package/dist/converters/dummy.js +16 -0
  81. package/dist/converters/edge.d.ts +6 -0
  82. package/dist/converters/edge.js +88 -0
  83. package/dist/converters/node.d.ts +3 -0
  84. package/dist/converters/node.js +45 -0
  85. package/dist/converters/sqs-revalidate.d.ts +4 -0
  86. package/dist/converters/sqs-revalidate.js +22 -0
  87. package/dist/converters/utils.d.ts +1 -0
  88. package/dist/converters/utils.js +9 -0
  89. package/dist/core/createGenericHandler.d.ts +11 -0
  90. package/dist/core/createGenericHandler.js +16 -0
  91. package/dist/core/createMainHandler.d.ts +18 -0
  92. package/dist/core/createMainHandler.js +24 -0
  93. package/dist/core/edgeFunctionHandler.d.ts +54 -0
  94. package/dist/core/edgeFunctionHandler.js +21 -0
  95. package/dist/core/patchAsyncStorage.d.ts +1 -0
  96. package/dist/core/patchAsyncStorage.js +16 -0
  97. package/dist/core/requestHandler.d.ts +3 -0
  98. package/dist/core/requestHandler.js +162 -0
  99. package/dist/core/require-hooks.d.ts +3 -0
  100. package/dist/core/require-hooks.js +102 -0
  101. package/dist/core/resolve.d.ts +31 -0
  102. package/dist/core/resolve.js +81 -0
  103. package/dist/core/routing/cacheInterceptor.d.ts +2 -0
  104. package/dist/core/routing/cacheInterceptor.js +162 -0
  105. package/dist/core/routing/i18n/accept-header.d.ts +1 -0
  106. package/dist/core/routing/i18n/accept-header.js +101 -0
  107. package/dist/core/routing/i18n/index.d.ts +2 -0
  108. package/dist/core/routing/i18n/index.js +36 -0
  109. package/dist/core/routing/matcher.d.ts +25 -0
  110. package/dist/core/routing/matcher.js +294 -0
  111. package/dist/core/routing/middleware.d.ts +7 -0
  112. package/dist/core/routing/middleware.js +167 -0
  113. package/dist/core/routing/util.d.ts +105 -0
  114. package/dist/core/routing/util.js +415 -0
  115. package/dist/core/routingHandler.d.ts +8 -0
  116. package/dist/core/routingHandler.js +127 -0
  117. package/dist/core/util.d.ts +5 -0
  118. package/dist/core/util.js +84 -0
  119. package/dist/helpers/withCloudflare.d.ts +65 -0
  120. package/dist/helpers/withCloudflare.js +58 -0
  121. package/dist/helpers/withSST.d.ts +54 -0
  122. package/dist/helpers/withSST.js +28 -0
  123. package/dist/http/index.d.ts +2 -0
  124. package/dist/http/index.js +4 -0
  125. package/dist/http/openNextResponse.d.ts +78 -0
  126. package/dist/http/openNextResponse.js +281 -0
  127. package/dist/http/request.d.ts +12 -0
  128. package/dist/http/request.js +35 -0
  129. package/dist/http/util.d.ts +4 -0
  130. package/dist/http/util.js +34 -0
  131. package/dist/index.d.ts +2 -0
  132. package/dist/index.js +37 -0
  133. package/dist/logger.d.ts +12 -0
  134. package/dist/logger.js +13 -0
  135. package/dist/minimize-js.d.ts +1 -0
  136. package/dist/minimize-js.js +101 -0
  137. package/dist/overrides/imageLoader/host.d.ts +3 -0
  138. package/dist/overrides/imageLoader/host.js +29 -0
  139. package/dist/overrides/imageLoader/s3.d.ts +3 -0
  140. package/dist/overrides/imageLoader/s3.js +33 -0
  141. package/dist/plugins/edge.d.ts +15 -0
  142. package/dist/plugins/edge.js +157 -0
  143. package/dist/plugins/replacement.d.ts +42 -0
  144. package/dist/plugins/replacement.js +74 -0
  145. package/dist/plugins/resolve.d.ts +18 -0
  146. package/dist/plugins/resolve.js +45 -0
  147. package/dist/queue/sqs-lite.d.ts +3 -0
  148. package/dist/queue/sqs-lite.js +52 -0
  149. package/dist/queue/sqs.d.ts +3 -0
  150. package/dist/queue/sqs.js +20 -0
  151. package/dist/queue/types.d.ts +12 -0
  152. package/dist/queue/types.js +1 -0
  153. package/dist/types/aws-lambda.d.ts +22 -0
  154. package/dist/types/aws-lambda.js +1 -0
  155. package/dist/types/next-types.d.ts +155 -0
  156. package/dist/types/next-types.js +2 -0
  157. package/dist/types/open-next.d.ts +303 -0
  158. package/dist/types/open-next.js +1 -0
  159. package/dist/utils/error.d.ts +23 -0
  160. package/dist/utils/error.js +32 -0
  161. package/dist/utils/fetch.d.ts +2 -0
  162. package/dist/utils/fetch.js +23 -0
  163. package/dist/utils/promise.d.ts +18 -0
  164. package/dist/utils/promise.js +48 -0
  165. package/dist/utils/stream.d.ts +5 -0
  166. package/dist/utils/stream.js +30 -0
  167. package/dist/wrappers/aws-lambda-streaming.d.ts +7 -0
  168. package/dist/wrappers/aws-lambda-streaming.js +88 -0
  169. package/dist/wrappers/aws-lambda.d.ts +7 -0
  170. package/dist/wrappers/aws-lambda.js +39 -0
  171. package/dist/wrappers/cloudflare.d.ts +11 -0
  172. package/dist/wrappers/cloudflare.js +20 -0
  173. package/dist/wrappers/node.d.ts +7 -0
  174. package/dist/wrappers/node.js +57 -0
  175. package/package.json +66 -0
package/dist/build.js ADDED
@@ -0,0 +1,533 @@
1
+ import cp from "node:child_process";
2
+ import fs, { readFileSync } from "node:fs";
3
+ import { createRequire as topLevelCreateRequire } from "node:module";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import url from "node:url";
7
+ import { isBinaryContentType } from "./adapters/binary.js";
8
+ import { compileOpenNextConfigEdge, compileOpenNextConfigNode, } from "./build/compileConfig.js";
9
+ import { createServerBundle } from "./build/createServerBundle.js";
10
+ import { buildEdgeBundle } from "./build/edge/createEdgeBundle.js";
11
+ import { generateOutput } from "./build/generateOutput.js";
12
+ import { compareSemver, copyOpenNextConfig, esbuildAsync, esbuildSync, getBuildId, getHtmlPages, normalizeOptions, removeFiles, traverseFiles, } from "./build/helper.js";
13
+ import { validateConfig } from "./build/validateConfig.js";
14
+ import logger from "./logger.js";
15
+ import { openNextReplacementPlugin } from "./plugins/replacement.js";
16
+ import { openNextResolvePlugin } from "./plugins/resolve.js";
17
+ const require = topLevelCreateRequire(import.meta.url);
18
+ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
19
+ let options;
20
+ let config;
21
+ export async function build(openNextConfigPath, nodeExternals) {
22
+ showWindowsWarning();
23
+ // Load open-next.config.ts
24
+ const tempDir = initTempDir();
25
+ let configPath = compileOpenNextConfigNode(tempDir, openNextConfigPath, nodeExternals);
26
+ // On Windows, we need to use file:// protocol to load the config file using import()
27
+ if (process.platform === "win32")
28
+ configPath = `file://${configPath}`;
29
+ config = (await import(configPath)).default;
30
+ if (!config || !config.default) {
31
+ logger.error(`config.default cannot be empty, it should be at least {}, see more info here: https://open-next.js.org/config#configuration-file`);
32
+ process.exit(1);
33
+ }
34
+ validateConfig(config);
35
+ compileOpenNextConfigEdge(tempDir, config, openNextConfigPath);
36
+ const { root: monorepoRoot, packager } = findMonorepoRoot(path.join(process.cwd(), config.appPath || "."));
37
+ // Initialize options
38
+ options = normalizeOptions(config, monorepoRoot);
39
+ logger.setLevel(options.debug ? "debug" : "info");
40
+ // Pre-build validation
41
+ checkRunningInsideNextjsApp();
42
+ printNextjsVersion();
43
+ printOpenNextVersion();
44
+ // Build Next.js app
45
+ printHeader("Building Next.js app");
46
+ setStandaloneBuildMode(monorepoRoot);
47
+ buildNextjsApp(packager);
48
+ // Generate deployable bundle
49
+ printHeader("Generating bundle");
50
+ initOutputDir(tempDir);
51
+ // Compile cache.ts
52
+ compileCache();
53
+ // Compile middleware
54
+ await createMiddleware();
55
+ createStaticAssets();
56
+ await createCacheAssets(monorepoRoot);
57
+ await createServerBundle(config, options);
58
+ await createRevalidationBundle(config);
59
+ await createImageOptimizationBundle(config);
60
+ await createWarmerBundle(config);
61
+ await generateOutput(options.appBuildOutputPath, config);
62
+ logger.info("OpenNext build complete.");
63
+ }
64
+ function showWindowsWarning() {
65
+ if (os.platform() !== "win32")
66
+ return;
67
+ logger.warn("OpenNext is not fully compatible with Windows.");
68
+ logger.warn("For optimal performance, it is recommended to use Windows Subsystem for Linux (WSL).");
69
+ logger.warn("While OpenNext may function on Windows, it could encounter unpredictable failures during runtime.");
70
+ }
71
+ function initTempDir() {
72
+ const dir = path.join(process.cwd(), ".open-next");
73
+ const tempDir = path.join(dir, ".build");
74
+ fs.rmSync(dir, { recursive: true, force: true });
75
+ fs.mkdirSync(tempDir, { recursive: true });
76
+ return tempDir;
77
+ }
78
+ function checkRunningInsideNextjsApp() {
79
+ const { appPath } = options;
80
+ const extension = ["js", "cjs", "mjs", "ts"].find((ext) => fs.existsSync(path.join(appPath, `next.config.${ext}`)));
81
+ if (!extension) {
82
+ logger.error("Error: next.config.js not found. Please make sure you are running this command inside a Next.js app.");
83
+ process.exit(1);
84
+ }
85
+ }
86
+ function findMonorepoRoot(appPath) {
87
+ let currentPath = appPath;
88
+ while (currentPath !== "/") {
89
+ const found = [
90
+ { file: "package-lock.json", packager: "npm" },
91
+ { file: "yarn.lock", packager: "yarn" },
92
+ { file: "pnpm-lock.yaml", packager: "pnpm" },
93
+ { file: "bun.lockb", packager: "bun" },
94
+ ].find((f) => fs.existsSync(path.join(currentPath, f.file)));
95
+ if (found) {
96
+ if (currentPath !== appPath) {
97
+ logger.info("Monorepo detected at", currentPath);
98
+ }
99
+ return { root: currentPath, packager: found.packager };
100
+ }
101
+ currentPath = path.dirname(currentPath);
102
+ }
103
+ // note: a lock file (package-lock.json, yarn.lock, or pnpm-lock.yaml) is
104
+ // not found in the app's directory or any of its parent directories.
105
+ // We are going to assume that the app is not part of a monorepo.
106
+ return { root: appPath, packager: "npm" };
107
+ }
108
+ function setStandaloneBuildMode(monorepoRoot) {
109
+ // Equivalent to setting `target: "standalone"` in next.config.js
110
+ process.env.NEXT_PRIVATE_STANDALONE = "true";
111
+ // Equivalent to setting `experimental.outputFileTracingRoot` in next.config.js
112
+ process.env.NEXT_PRIVATE_OUTPUT_TRACE_ROOT = monorepoRoot;
113
+ }
114
+ function buildNextjsApp(packager) {
115
+ const { appPackageJsonPath } = options;
116
+ const command = config.buildCommand ??
117
+ (["bun", "npm"].includes(packager)
118
+ ? `${packager} run build`
119
+ : `${packager} build`);
120
+ cp.execSync(command, {
121
+ stdio: "inherit",
122
+ cwd: path.dirname(appPackageJsonPath),
123
+ });
124
+ }
125
+ function printHeader(header) {
126
+ header = `OpenNext — ${header}`;
127
+ logger.info([
128
+ "",
129
+ "┌" + "─".repeat(header.length + 2) + "┐",
130
+ `│ ${header} │`,
131
+ "└" + "─".repeat(header.length + 2) + "┘",
132
+ "",
133
+ ].join("\n"));
134
+ }
135
+ function printNextjsVersion() {
136
+ const { nextVersion } = options;
137
+ logger.info(`Next.js version : ${nextVersion}`);
138
+ }
139
+ function printOpenNextVersion() {
140
+ const { openNextVersion } = options;
141
+ logger.info(`OpenNext v${openNextVersion}`);
142
+ }
143
+ function initOutputDir(tempDir) {
144
+ // We need to get the build relative to the cwd to find the compiled config
145
+ // This is needed for the case where the app is a single-version monorepo and the package.json is in the root of the monorepo
146
+ // where the build is in the app directory, but the compiled config is in the root of the monorepo.
147
+ const { outputDir, tempDir: lTempDir } = options;
148
+ const openNextConfig = readFileSync(path.join(tempDir, "open-next.config.mjs"), "utf8");
149
+ let openNextConfigEdge = null;
150
+ if (fs.existsSync(path.join(tempDir, "open-next.config.edge.mjs"))) {
151
+ openNextConfigEdge = readFileSync(path.join(tempDir, "open-next.config.edge.mjs"), "utf8");
152
+ }
153
+ fs.rmSync(outputDir, { recursive: true, force: true });
154
+ fs.mkdirSync(lTempDir, { recursive: true });
155
+ fs.writeFileSync(path.join(lTempDir, "open-next.config.mjs"), openNextConfig);
156
+ if (openNextConfigEdge) {
157
+ fs.writeFileSync(path.join(lTempDir, "open-next.config.edge.mjs"), openNextConfigEdge);
158
+ }
159
+ }
160
+ async function createWarmerBundle(config) {
161
+ logger.info(`Bundling warmer function...`);
162
+ const { outputDir } = options;
163
+ // Create output folder
164
+ const outputPath = path.join(outputDir, "warmer-function");
165
+ fs.mkdirSync(outputPath, { recursive: true });
166
+ // Copy open-next.config.mjs into the bundle
167
+ copyOpenNextConfig(options.tempDir, outputPath);
168
+ // Build Lambda code
169
+ // note: bundle in OpenNext package b/c the adatper relys on the
170
+ // "serverless-http" package which is not a dependency in user's
171
+ // Next.js app.
172
+ await esbuildAsync({
173
+ entryPoints: [path.join(__dirname, "adapters", "warmer-function.js")],
174
+ external: ["next"],
175
+ outfile: path.join(outputPath, "index.mjs"),
176
+ plugins: [
177
+ openNextResolvePlugin({
178
+ overrides: {
179
+ converter: config.warmer?.override?.converter ?? "dummy",
180
+ wrapper: config.warmer?.override?.wrapper,
181
+ },
182
+ fnName: "warmer",
183
+ }),
184
+ ],
185
+ banner: {
186
+ js: [
187
+ "import { createRequire as topLevelCreateRequire } from 'module';",
188
+ "const require = topLevelCreateRequire(import.meta.url);",
189
+ "import bannerUrl from 'url';",
190
+ "const __dirname = bannerUrl.fileURLToPath(new URL('.', import.meta.url));",
191
+ ].join(""),
192
+ },
193
+ }, options);
194
+ }
195
+ async function createRevalidationBundle(config) {
196
+ logger.info(`Bundling revalidation function...`);
197
+ const { appBuildOutputPath, outputDir } = options;
198
+ // Create output folder
199
+ const outputPath = path.join(outputDir, "revalidation-function");
200
+ fs.mkdirSync(outputPath, { recursive: true });
201
+ //Copy open-next.config.mjs into the bundle
202
+ copyOpenNextConfig(options.tempDir, outputPath);
203
+ // Build Lambda code
204
+ await esbuildAsync({
205
+ external: ["next", "styled-jsx", "react"],
206
+ entryPoints: [path.join(__dirname, "adapters", "revalidate.js")],
207
+ outfile: path.join(outputPath, "index.mjs"),
208
+ plugins: [
209
+ openNextResolvePlugin({
210
+ fnName: "revalidate",
211
+ overrides: {
212
+ converter: config.revalidate?.override?.converter ?? "sqs-revalidate",
213
+ wrapper: config.revalidate?.override?.wrapper,
214
+ },
215
+ }),
216
+ ],
217
+ }, options);
218
+ // Copy over .next/prerender-manifest.json file
219
+ fs.copyFileSync(path.join(appBuildOutputPath, ".next", "prerender-manifest.json"), path.join(outputPath, "prerender-manifest.json"));
220
+ }
221
+ async function createImageOptimizationBundle(config) {
222
+ logger.info(`Bundling image optimization function...`);
223
+ const { appPath, appBuildOutputPath, outputDir } = options;
224
+ // Create output folder
225
+ const outputPath = path.join(outputDir, "image-optimization-function");
226
+ fs.mkdirSync(outputPath, { recursive: true });
227
+ // Copy open-next.config.mjs into the bundle
228
+ copyOpenNextConfig(options.tempDir, outputPath);
229
+ const plugins = [
230
+ openNextResolvePlugin({
231
+ fnName: "imageOptimization",
232
+ overrides: {
233
+ converter: config.imageOptimization?.override?.converter,
234
+ wrapper: config.imageOptimization?.override?.wrapper,
235
+ imageLoader: config.imageOptimization?.loader,
236
+ },
237
+ }),
238
+ ];
239
+ if (compareSemver(options.nextVersion, "14.1.1") >= 0) {
240
+ plugins.push(openNextReplacementPlugin({
241
+ name: "opennext-14.1.1-image-optimization",
242
+ target: /plugins(\/|\\)image-optimization(\/|\\)image-optimization\.js/g,
243
+ replacements: [
244
+ require.resolve("./adapters/plugins/image-optimization/image-optimization.replacement.js"),
245
+ ],
246
+ }));
247
+ }
248
+ // Build Lambda code (1st pass)
249
+ // note: bundle in OpenNext package b/c the adapter relies on the
250
+ // "@aws-sdk/client-s3" package which is not a dependency in user's
251
+ // Next.js app.
252
+ await esbuildAsync({
253
+ entryPoints: [
254
+ path.join(__dirname, "adapters", "image-optimization-adapter.js"),
255
+ ],
256
+ external: ["sharp", "next"],
257
+ outfile: path.join(outputPath, "index.mjs"),
258
+ plugins,
259
+ }, options);
260
+ // Build Lambda code (2nd pass)
261
+ // note: bundle in user's Next.js app again b/c the adapter relies on the
262
+ // "next" package. And the "next" package from user's app should
263
+ // be used. We also set @opentelemetry/api as external because it seems to be
264
+ // required by Next 15 even though it's not used.
265
+ esbuildSync({
266
+ entryPoints: [path.join(outputPath, "index.mjs")],
267
+ external: ["sharp", "@opentelemetry/api"],
268
+ allowOverwrite: true,
269
+ outfile: path.join(outputPath, "index.mjs"),
270
+ banner: {
271
+ js: [
272
+ "import { createRequire as topLevelCreateRequire } from 'module';",
273
+ "const require = topLevelCreateRequire(import.meta.url);",
274
+ "import bannerUrl from 'url';",
275
+ "const __dirname = bannerUrl.fileURLToPath(new URL('.', import.meta.url));",
276
+ ].join("\n"),
277
+ },
278
+ }, options);
279
+ // Copy over .next/required-server-files.json file and BUILD_ID
280
+ fs.mkdirSync(path.join(outputPath, ".next"));
281
+ fs.copyFileSync(path.join(appBuildOutputPath, ".next/required-server-files.json"), path.join(outputPath, ".next/required-server-files.json"));
282
+ fs.copyFileSync(path.join(appBuildOutputPath, ".next/BUILD_ID"), path.join(outputPath, ".next/BUILD_ID"));
283
+ // Sharp provides pre-build binaries for all platforms. https://github.com/lovell/sharp/blob/main/docs/install.md#cross-platform
284
+ // Target should be same as used by Lambda, see https://github.com/sst/sst/blob/ca6f763fdfddd099ce2260202d0ce48c72e211ea/packages/sst/src/constructs/NextjsSite.ts#L114
285
+ // For SHARP_IGNORE_GLOBAL_LIBVIPS see: https://github.com/lovell/sharp/blob/main/docs/install.md#aws-lambda
286
+ const nodeOutputPath = path.resolve(outputPath);
287
+ const sharpVersion = process.env.SHARP_VERSION ?? "0.32.6";
288
+ const arch = config.imageOptimization?.arch ?? "arm64";
289
+ const nodeVersion = config.imageOptimization?.nodeVersion ?? "18";
290
+ //check if we are running in Windows environment then set env variables accordingly.
291
+ try {
292
+ cp.execSync(
293
+ // We might want to change the arch args to cpu args, it seems to be the documented way
294
+ `npm install --arch=${arch} --platform=linux --target=${nodeVersion} --libc=glibc --prefix="${nodeOutputPath}" sharp@${sharpVersion}`, {
295
+ stdio: "pipe",
296
+ cwd: appPath,
297
+ env: {
298
+ ...process.env,
299
+ SHARP_IGNORE_GLOBAL_LIBVIPS: "1",
300
+ },
301
+ });
302
+ }
303
+ catch (e) {
304
+ logger.error(e.stdout.toString());
305
+ logger.error(e.stderr.toString());
306
+ logger.error("Failed to install sharp.");
307
+ }
308
+ }
309
+ function createStaticAssets() {
310
+ logger.info(`Bundling static assets...`);
311
+ const { appBuildOutputPath, appPublicPath, outputDir, appPath } = options;
312
+ // Create output folder
313
+ const outputPath = path.join(outputDir, "assets");
314
+ fs.mkdirSync(outputPath, { recursive: true });
315
+ // Next.js outputs assets into multiple files. Copy into the same directory.
316
+ // Copy over:
317
+ // - .next/BUILD_ID => _next/BUILD_ID
318
+ // - .next/static => _next/static
319
+ // - public/* => *
320
+ // - app/favicon.ico or src/app/favicon.ico => favicon.ico
321
+ fs.copyFileSync(path.join(appBuildOutputPath, ".next/BUILD_ID"), path.join(outputPath, "BUILD_ID"));
322
+ fs.cpSync(path.join(appBuildOutputPath, ".next/static"), path.join(outputPath, "_next", "static"), { recursive: true });
323
+ if (fs.existsSync(appPublicPath)) {
324
+ fs.cpSync(appPublicPath, outputPath, { recursive: true });
325
+ }
326
+ const appSrcPath = fs.existsSync(path.join(appPath, "src"))
327
+ ? "src/app"
328
+ : "app";
329
+ const faviconPath = path.join(appPath, appSrcPath, "favicon.ico");
330
+ if (fs.existsSync(faviconPath)) {
331
+ fs.copyFileSync(faviconPath, path.join(outputPath, "favicon.ico"));
332
+ }
333
+ }
334
+ async function createCacheAssets(monorepoRoot) {
335
+ if (config.dangerous?.disableIncrementalCache)
336
+ return;
337
+ logger.info(`Bundling cache assets...`);
338
+ const { appBuildOutputPath, outputDir } = options;
339
+ const packagePath = path.relative(monorepoRoot, appBuildOutputPath);
340
+ const buildId = getBuildId(appBuildOutputPath);
341
+ // Copy pages to cache folder
342
+ const dotNextPath = path.join(appBuildOutputPath, ".next/standalone", packagePath);
343
+ const outputPath = path.join(outputDir, "cache", buildId);
344
+ [".next/server/pages", ".next/server/app"]
345
+ .map((dir) => path.join(dotNextPath, dir))
346
+ .filter(fs.existsSync)
347
+ .forEach((dir) => fs.cpSync(dir, outputPath, { recursive: true }));
348
+ // Remove non-cache files
349
+ const htmlPages = getHtmlPages(dotNextPath);
350
+ removeFiles(outputPath, (file) => file.endsWith(".js") ||
351
+ file.endsWith(".js.nft.json") ||
352
+ (file.endsWith(".html") && htmlPages.has(file)));
353
+ //merge cache files into a single file
354
+ const cacheFilesPath = {};
355
+ traverseFiles(outputPath, () => true, (filepath) => {
356
+ const ext = path.extname(filepath);
357
+ const newFilePath = ext !== "" ? filepath.replace(ext, ".cache") : `${filepath}.cache`;
358
+ switch (ext) {
359
+ case ".meta":
360
+ case ".html":
361
+ case ".json":
362
+ case ".body":
363
+ case ".rsc":
364
+ cacheFilesPath[newFilePath] = {
365
+ [ext.slice(1)]: filepath,
366
+ ...cacheFilesPath[newFilePath],
367
+ };
368
+ break;
369
+ case ".map":
370
+ break;
371
+ default:
372
+ logger.warn(`Unknown file extension: ${ext}`);
373
+ break;
374
+ }
375
+ });
376
+ // Generate cache file
377
+ Object.entries(cacheFilesPath).forEach(([cacheFilePath, files]) => {
378
+ const cacheFileMeta = files.meta
379
+ ? JSON.parse(fs.readFileSync(files.meta, "utf8"))
380
+ : undefined;
381
+ const cacheFileContent = {
382
+ type: files.body ? "route" : files.json ? "page" : "app",
383
+ meta: cacheFileMeta,
384
+ html: files.html ? fs.readFileSync(files.html, "utf8") : undefined,
385
+ json: files.json
386
+ ? JSON.parse(fs.readFileSync(files.json, "utf8"))
387
+ : undefined,
388
+ rsc: files.rsc ? fs.readFileSync(files.rsc, "utf8") : undefined,
389
+ body: files.body
390
+ ? fs
391
+ .readFileSync(files.body)
392
+ .toString(isBinaryContentType(cacheFileMeta.headers["content-type"])
393
+ ? "base64"
394
+ : "utf8")
395
+ : undefined,
396
+ };
397
+ fs.writeFileSync(cacheFilePath, JSON.stringify(cacheFileContent));
398
+ });
399
+ if (!config.dangerous?.disableTagCache) {
400
+ // Generate dynamodb data
401
+ // We need to traverse the cache to find every .meta file
402
+ const metaFiles = [];
403
+ // Compute dynamodb cache data
404
+ // Traverse files inside cache to find all meta files and cache tags associated with them
405
+ traverseFiles(outputPath, (file) => file.endsWith(".meta"), (filePath) => {
406
+ const fileContent = fs.readFileSync(filePath, "utf8");
407
+ const fileData = JSON.parse(fileContent);
408
+ if (fileData.headers?.["x-next-cache-tags"]) {
409
+ fileData.headers["x-next-cache-tags"]
410
+ .split(",")
411
+ .forEach((tag) => {
412
+ // TODO: We should split the tag using getDerivedTags from next.js or maybe use an in house implementation
413
+ metaFiles.push({
414
+ tag: { S: path.posix.join(buildId, tag.trim()) },
415
+ path: {
416
+ S: path.posix.join(buildId, path.relative(outputPath, filePath).replace(".meta", "")),
417
+ },
418
+ // We don't care about the revalidation time here, we just need to make sure it's there
419
+ revalidatedAt: { N: "1" },
420
+ });
421
+ });
422
+ }
423
+ });
424
+ // Copy fetch-cache to cache folder
425
+ const fetchCachePath = path.join(appBuildOutputPath, ".next/cache/fetch-cache");
426
+ if (fs.existsSync(fetchCachePath)) {
427
+ const fetchOutputPath = path.join(outputDir, "cache", "__fetch", buildId);
428
+ fs.mkdirSync(fetchOutputPath, { recursive: true });
429
+ fs.cpSync(fetchCachePath, fetchOutputPath, { recursive: true });
430
+ traverseFiles(fetchCachePath, () => true, (filepath) => {
431
+ const fileContent = fs.readFileSync(filepath, "utf8");
432
+ const fileData = JSON.parse(fileContent);
433
+ fileData?.tags?.forEach((tag) => {
434
+ metaFiles.push({
435
+ tag: { S: path.posix.join(buildId, tag) },
436
+ path: {
437
+ S: path.posix.join(buildId, path.relative(fetchCachePath, filepath)),
438
+ },
439
+ revalidatedAt: { N: "1" },
440
+ });
441
+ });
442
+ });
443
+ }
444
+ if (metaFiles.length > 0) {
445
+ const providerPath = path.join(outputDir, "dynamodb-provider");
446
+ await esbuildAsync({
447
+ external: ["@aws-sdk/client-dynamodb"],
448
+ entryPoints: [path.join(__dirname, "adapters", "dynamo-provider.js")],
449
+ outfile: path.join(providerPath, "index.mjs"),
450
+ target: ["node18"],
451
+ plugins: [
452
+ openNextResolvePlugin({
453
+ fnName: "initializationFunction",
454
+ overrides: {
455
+ converter: config.initializationFunction?.override?.converter ?? "dummy",
456
+ wrapper: config.initializationFunction?.override?.wrapper,
457
+ },
458
+ }),
459
+ ],
460
+ }, options);
461
+ //Copy open-next.config.mjs into the bundle
462
+ copyOpenNextConfig(options.tempDir, providerPath);
463
+ // TODO: check if metafiles doesn't contain duplicates
464
+ fs.writeFileSync(path.join(providerPath, "dynamodb-cache.json"), JSON.stringify(metaFiles));
465
+ }
466
+ }
467
+ // We need to remove files later because we need the metafiles for dynamodb tags cache
468
+ removeFiles(outputPath, (file) => !file.endsWith(".cache"));
469
+ }
470
+ /***************************/
471
+ /* Server Helper Functions */
472
+ /***************************/
473
+ export function compileCache(format = "cjs") {
474
+ const ext = format === "cjs" ? "cjs" : "mjs";
475
+ const outfile = path.join(options.outputDir, ".build", `cache.${ext}`);
476
+ const isAfter15 = compareSemver(options.nextVersion, "15.0.0") >= 0;
477
+ esbuildSync({
478
+ external: ["next", "styled-jsx", "react", "@aws-sdk/*"],
479
+ entryPoints: [path.join(__dirname, "adapters", "cache.js")],
480
+ outfile,
481
+ target: ["node18"],
482
+ format,
483
+ banner: {
484
+ js: [
485
+ `globalThis.disableIncrementalCache = ${config.dangerous?.disableIncrementalCache ?? false};`,
486
+ `globalThis.disableDynamoDBCache = ${config.dangerous?.disableTagCache ?? false};`,
487
+ `globalThis.isNextAfter15 = ${isAfter15};`,
488
+ ].join(""),
489
+ },
490
+ }, options);
491
+ return outfile;
492
+ }
493
+ async function createMiddleware() {
494
+ console.info(`Bundling middleware function...`);
495
+ const { appBuildOutputPath, outputDir } = options;
496
+ // Get middleware manifest
497
+ const middlewareManifest = JSON.parse(readFileSync(path.join(appBuildOutputPath, ".next/server/middleware-manifest.json"), "utf8"));
498
+ const entry = middlewareManifest.middleware["/"];
499
+ if (!entry) {
500
+ return;
501
+ }
502
+ // Create output folder
503
+ let outputPath = path.join(outputDir, "server-function");
504
+ const commonMiddlewareOptions = {
505
+ middlewareInfo: entry,
506
+ options,
507
+ appBuildOutputPath,
508
+ };
509
+ if (config.middleware?.external) {
510
+ outputPath = path.join(outputDir, "middleware");
511
+ fs.mkdirSync(outputPath, { recursive: true });
512
+ // Copy open-next.config.mjs
513
+ copyOpenNextConfig(options.tempDir, outputPath, config.middleware.override?.wrapper === "cloudflare");
514
+ // Bundle middleware
515
+ await buildEdgeBundle({
516
+ entrypoint: path.join(__dirname, "adapters", "middleware.js"),
517
+ outfile: path.join(outputPath, "handler.mjs"),
518
+ ...commonMiddlewareOptions,
519
+ overrides: config.middleware?.override,
520
+ defaultConverter: "aws-cloudfront",
521
+ includeCache: config.dangerous?.enableCacheInterception,
522
+ additionalExternals: config.edgeExternals,
523
+ });
524
+ }
525
+ else {
526
+ await buildEdgeBundle({
527
+ entrypoint: path.join(__dirname, "core", "edgeFunctionHandler.js"),
528
+ outfile: path.join(outputDir, ".build", "middleware.mjs"),
529
+ ...commonMiddlewareOptions,
530
+ onlyBuildOnce: true,
531
+ });
532
+ }
533
+ }
@@ -0,0 +1,3 @@
1
+ import { IncrementalCache } from "./types";
2
+ declare const incrementalCache: IncrementalCache;
3
+ export default incrementalCache;
@@ -0,0 +1,72 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
+ import { AwsClient } from "aws4fetch";
3
+ import path from "path";
4
+ import { IgnorableError, RecoverableError } from "../../utils/error";
5
+ import { customFetchClient } from "../../utils/fetch";
6
+ import { parseNumberFromEnv } from "../../adapters/util";
7
+ let awsClient = null;
8
+ const getAwsClient = () => {
9
+ const { CACHE_BUCKET_REGION } = process.env;
10
+ if (awsClient) {
11
+ return awsClient;
12
+ }
13
+ else {
14
+ awsClient = new AwsClient({
15
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
16
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
17
+ sessionToken: process.env.AWS_SESSION_TOKEN,
18
+ region: CACHE_BUCKET_REGION,
19
+ retries: parseNumberFromEnv(process.env.AWS_SDK_S3_MAX_ATTEMPTS),
20
+ });
21
+ return awsClient;
22
+ }
23
+ };
24
+ const awsFetch = async (key, options) => {
25
+ const { CACHE_BUCKET_REGION, CACHE_BUCKET_NAME } = process.env;
26
+ const client = getAwsClient();
27
+ const url = `https://${CACHE_BUCKET_NAME}.s3.${CACHE_BUCKET_REGION}.amazonaws.com/${key}`;
28
+ return customFetchClient(client)(url, options);
29
+ };
30
+ function buildS3Key(key, extension) {
31
+ const { CACHE_BUCKET_KEY_PREFIX, NEXT_BUILD_ID } = process.env;
32
+ return path.posix.join(CACHE_BUCKET_KEY_PREFIX ?? "", extension === "fetch" ? "__fetch" : "", NEXT_BUILD_ID ?? "", extension === "fetch" ? key : `${key}.${extension}`);
33
+ }
34
+ const incrementalCache = {
35
+ async get(key, isFetch) {
36
+ const result = await awsFetch(buildS3Key(key, isFetch ? "fetch" : "cache"), {
37
+ method: "GET",
38
+ });
39
+ if (result.status === 404) {
40
+ throw new IgnorableError("Not found");
41
+ }
42
+ else if (result.status !== 200) {
43
+ throw new RecoverableError(`Failed to get cache: ${result.status}`);
44
+ }
45
+ else {
46
+ const cacheData = await result.json();
47
+ return {
48
+ value: cacheData,
49
+ lastModified: new Date(result.headers.get("last-modified") ?? "").getTime(),
50
+ };
51
+ }
52
+ },
53
+ async set(key, value, isFetch) {
54
+ const response = await awsFetch(buildS3Key(key, isFetch ? "fetch" : "cache"), {
55
+ method: "PUT",
56
+ body: JSON.stringify(value),
57
+ });
58
+ if (response.status !== 200) {
59
+ throw new RecoverableError(`Failed to set cache: ${response.status}`);
60
+ }
61
+ },
62
+ async delete(key) {
63
+ const response = await awsFetch(buildS3Key(key, "cache"), {
64
+ method: "DELETE",
65
+ });
66
+ if (response.status !== 204) {
67
+ throw new RecoverableError(`Failed to delete cache: ${response.status}`);
68
+ }
69
+ },
70
+ name: "s3",
71
+ };
72
+ export default incrementalCache;
@@ -0,0 +1,3 @@
1
+ import { IncrementalCache } from "./types";
2
+ declare const incrementalCache: IncrementalCache;
3
+ export default incrementalCache;
@@ -0,0 +1,44 @@
1
+ import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client, } from "@aws-sdk/client-s3";
2
+ import path from "path";
3
+ import { awsLogger } from "../../adapters/logger";
4
+ import { parseNumberFromEnv } from "../../adapters/util";
5
+ const { CACHE_BUCKET_REGION, CACHE_BUCKET_KEY_PREFIX, NEXT_BUILD_ID, CACHE_BUCKET_NAME, } = process.env;
6
+ function parseS3ClientConfigFromEnv() {
7
+ return {
8
+ region: CACHE_BUCKET_REGION,
9
+ logger: awsLogger,
10
+ maxAttempts: parseNumberFromEnv(process.env.AWS_SDK_S3_MAX_ATTEMPTS),
11
+ };
12
+ }
13
+ const s3Client = new S3Client(parseS3ClientConfigFromEnv());
14
+ function buildS3Key(key, extension) {
15
+ return path.posix.join(CACHE_BUCKET_KEY_PREFIX ?? "", extension === "fetch" ? "__fetch" : "", NEXT_BUILD_ID ?? "", extension === "fetch" ? key : `${key}.${extension}`);
16
+ }
17
+ const incrementalCache = {
18
+ async get(key, isFetch) {
19
+ const result = await s3Client.send(new GetObjectCommand({
20
+ Bucket: CACHE_BUCKET_NAME,
21
+ Key: buildS3Key(key, isFetch ? "fetch" : "cache"),
22
+ }));
23
+ const cacheData = JSON.parse((await result.Body?.transformToString()) ?? "{}");
24
+ return {
25
+ value: cacheData,
26
+ lastModified: result.LastModified?.getTime(),
27
+ };
28
+ },
29
+ async set(key, value, isFetch) {
30
+ await s3Client.send(new PutObjectCommand({
31
+ Bucket: CACHE_BUCKET_NAME,
32
+ Key: buildS3Key(key, isFetch ? "fetch" : "cache"),
33
+ Body: JSON.stringify(value),
34
+ }));
35
+ },
36
+ async delete(key) {
37
+ await s3Client.send(new DeleteObjectCommand({
38
+ Bucket: CACHE_BUCKET_NAME,
39
+ Key: buildS3Key(key, "cache"),
40
+ }));
41
+ },
42
+ name: "s3",
43
+ };
44
+ export default incrementalCache;