@opennextjs/aws 3.6.4 → 3.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/build/copyTracedFiles.js +10 -5
- package/dist/build/createAssets.js +55 -51
- package/dist/build/createServerBundle.js +8 -8
- package/dist/build/patch/patches/patchNextServer.d.ts +13 -3
- package/dist/build/patch/patches/patchNextServer.js +82 -44
- package/dist/core/routing/cacheInterceptor.js +2 -1
- package/dist/core/routing/matcher.js +4 -3
- package/dist/http/openNextResponse.js +2 -2
- package/dist/http/util.d.ts +7 -1
- package/dist/http/util.js +14 -4
- package/dist/overrides/converters/aws-apigw-v2.js +2 -2
- package/dist/overrides/converters/aws-cloudfront.js +2 -2
- package/dist/overrides/converters/edge.js +14 -4
- package/dist/overrides/converters/node.js +6 -5
- package/dist/types/next-types.d.ts +2 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ OpenNext takes the Next.js build output and converts it into packages that can b
|
|
|
23
23
|
|
|
24
24
|
## Features
|
|
25
25
|
|
|
26
|
-
OpenNext aims to support all Next.js
|
|
26
|
+
OpenNext aims to support all Next.js 15 features. Some features are work in progress. If you are running into any problems make sure to check the [docs](https://opennext.js.org/aws) first before you open a [new issue](/issues/new) or visit our [Discord](https://discord.gg/opennextjs) to let us know!
|
|
27
27
|
|
|
28
28
|
- [x] App & Pages Router
|
|
29
29
|
- [x] API routes
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import url from "node:url";
|
|
2
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, statSync, symlinkSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, statSync, symlinkSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { loadAppPathsManifest, loadBuildId, loadConfig, loadFunctionsConfigManifest, loadMiddlewareManifest, loadPagesManifest, loadPrerenderManifest, } from "../adapters/config/util.js";
|
|
5
5
|
import { getCrossPlatformPathRegex } from "../utils/regex.js";
|
|
@@ -223,15 +223,15 @@ File ${serverPath} does not exist
|
|
|
223
223
|
copyFileSync(path.join(standaloneNextDir, filePath), path.join(outputNextDir, filePath));
|
|
224
224
|
}
|
|
225
225
|
};
|
|
226
|
+
const manifests = getManifests(standaloneNextDir);
|
|
227
|
+
const { config, prerenderManifest, pagesManifest } = manifests;
|
|
226
228
|
// Get all the static files - Should be only for pages dir
|
|
227
229
|
// Ideally we would filter only those that might get accessed in this specific functions
|
|
228
230
|
// Maybe even move this to s3 directly
|
|
229
231
|
if (hasPageDir) {
|
|
230
232
|
// First we get truly static files - i.e. pages without getStaticProps
|
|
231
|
-
const staticFiles = Object.values(
|
|
233
|
+
const staticFiles = Object.values(pagesManifest);
|
|
232
234
|
// Then we need to get all fallback: true dynamic routes html
|
|
233
|
-
const prerenderManifest = loadPrerenderManifest(standaloneNextDir);
|
|
234
|
-
const config = loadConfig(standaloneNextDir);
|
|
235
235
|
const locales = config.i18n?.locales;
|
|
236
236
|
Object.values(prerenderManifest.dynamicRoutes).forEach((route) => {
|
|
237
237
|
if (typeof route.fallback === "string") {
|
|
@@ -249,10 +249,15 @@ File ${serverPath} does not exist
|
|
|
249
249
|
.filter((file) => file.endsWith(".html"))
|
|
250
250
|
.forEach((file) => copyStaticFile(`server/${file}`));
|
|
251
251
|
}
|
|
252
|
+
// Copy .next/static/css from standalone to output dir
|
|
253
|
+
// needed for optimizeCss feature to work
|
|
254
|
+
if (config.experimental.optimizeCss) {
|
|
255
|
+
cpSync(path.join(standaloneNextDir, "static", "css"), path.join(outputNextDir, "static", "css"), { recursive: true });
|
|
256
|
+
}
|
|
252
257
|
logger.debug("copyTracedFiles:", Date.now() - tsStart, "ms");
|
|
253
258
|
return {
|
|
254
259
|
tracedFiles,
|
|
255
260
|
nodePackages,
|
|
256
|
-
manifests
|
|
261
|
+
manifests,
|
|
257
262
|
};
|
|
258
263
|
}
|
|
@@ -62,44 +62,46 @@ export function createCacheAssets(options) {
|
|
|
62
62
|
const packagePath = buildHelper.getPackagePath(options);
|
|
63
63
|
const buildId = buildHelper.getBuildId(options);
|
|
64
64
|
let useTagCache = false;
|
|
65
|
-
// Copy pages to cache folder
|
|
66
65
|
const dotNextPath = path.join(appBuildOutputPath, ".next/standalone", packagePath);
|
|
67
|
-
const
|
|
68
|
-
|
|
66
|
+
const outputCachePath = path.join(outputDir, "cache", buildId);
|
|
67
|
+
fs.mkdirSync(outputCachePath, { recursive: true });
|
|
68
|
+
const sourceDirs = [".next/server/pages", ".next/server/app"]
|
|
69
69
|
.map((dir) => path.join(dotNextPath, dir))
|
|
70
|
-
.filter(fs.existsSync)
|
|
71
|
-
.forEach((dir) => fs.cpSync(dir, outputPath, { recursive: true }));
|
|
72
|
-
// Remove non-cache files
|
|
70
|
+
.filter(fs.existsSync);
|
|
73
71
|
const htmlPages = buildHelper.getHtmlPages(dotNextPath);
|
|
74
|
-
|
|
72
|
+
const isFileSkipped = (relativePath) => relativePath.endsWith(".js") ||
|
|
75
73
|
relativePath.endsWith(".js.nft.json") ||
|
|
76
|
-
(relativePath.endsWith(".html") && htmlPages.has(relativePath))
|
|
74
|
+
(relativePath.endsWith(".html") && htmlPages.has(relativePath));
|
|
77
75
|
// Merge cache files into a single file
|
|
78
76
|
const cacheFilesPath = {};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
77
|
+
// Process each source directory
|
|
78
|
+
sourceDirs.forEach((sourceDir) => {
|
|
79
|
+
buildHelper.traverseFiles(sourceDir, ({ relativePath }) => !isFileSkipped(relativePath), ({ absolutePath, relativePath }) => {
|
|
80
|
+
const ext = path.extname(absolutePath);
|
|
81
|
+
switch (ext) {
|
|
82
|
+
case ".meta":
|
|
83
|
+
case ".html":
|
|
84
|
+
case ".json":
|
|
85
|
+
case ".body":
|
|
86
|
+
case ".rsc": {
|
|
87
|
+
const newFilePath = path
|
|
88
|
+
.join(outputCachePath, relativePath)
|
|
89
|
+
.substring(0, path.join(outputCachePath, relativePath).length - ext.length)
|
|
90
|
+
.replace(/\.prefetch$/, "")
|
|
91
|
+
.concat(".cache");
|
|
92
|
+
cacheFilesPath[newFilePath] = {
|
|
93
|
+
[ext.slice(1)]: absolutePath,
|
|
94
|
+
...cacheFilesPath[newFilePath],
|
|
95
|
+
};
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case ".map":
|
|
99
|
+
break;
|
|
100
|
+
default:
|
|
101
|
+
logger.warn(`Unknown file extension: ${ext}`);
|
|
102
|
+
break;
|
|
96
103
|
}
|
|
97
|
-
|
|
98
|
-
break;
|
|
99
|
-
default:
|
|
100
|
-
logger.warn(`Unknown file extension: ${ext}`);
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
104
|
+
});
|
|
103
105
|
});
|
|
104
106
|
// Generate cache file
|
|
105
107
|
Object.entries(cacheFilesPath).forEach(([cacheFilePath, files]) => {
|
|
@@ -122,6 +124,8 @@ export function createCacheAssets(options) {
|
|
|
122
124
|
: "utf8")
|
|
123
125
|
: undefined,
|
|
124
126
|
};
|
|
127
|
+
// Ensure directory exists before writing
|
|
128
|
+
fs.mkdirSync(path.dirname(cacheFilePath), { recursive: true });
|
|
125
129
|
fs.writeFileSync(cacheFilePath, JSON.stringify(cacheFileContent));
|
|
126
130
|
});
|
|
127
131
|
// We need to traverse the cache to find every .meta file
|
|
@@ -149,36 +153,36 @@ export function createCacheAssets(options) {
|
|
|
149
153
|
if (!options.config.dangerous?.disableTagCache) {
|
|
150
154
|
// Compute dynamodb cache data
|
|
151
155
|
// Traverse files inside cache to find all meta files and cache tags associated with them
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
fileData.headers["x-next-cache-tags"]
|
|
157
|
-
.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
156
|
+
sourceDirs.forEach((sourceDir) => {
|
|
157
|
+
buildHelper.traverseFiles(sourceDir, ({ absolutePath, relativePath }) => absolutePath.endsWith(".meta") && !isFileSkipped(relativePath), ({ absolutePath, relativePath }) => {
|
|
158
|
+
const fileContent = fs.readFileSync(absolutePath, "utf8");
|
|
159
|
+
const fileData = JSON.parse(fileContent);
|
|
160
|
+
if (fileData.headers?.["x-next-cache-tags"]) {
|
|
161
|
+
fileData.headers["x-next-cache-tags"]
|
|
162
|
+
.split(",")
|
|
163
|
+
.forEach((tag) => {
|
|
164
|
+
// TODO: We should split the tag using getDerivedTags from next.js or maybe use an in house implementation
|
|
165
|
+
metaFiles.push({
|
|
166
|
+
tag: { S: path.posix.join(buildId, tag.trim()) },
|
|
167
|
+
path: {
|
|
168
|
+
S: path.posix.join(buildId, relativePath.replace(".meta", "")),
|
|
169
|
+
},
|
|
170
|
+
// We don't care about the revalidation time here, we just need to make sure it's there
|
|
171
|
+
revalidatedAt: { N: "1" },
|
|
172
|
+
});
|
|
167
173
|
});
|
|
168
|
-
}
|
|
169
|
-
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
170
176
|
});
|
|
171
177
|
if (metaFiles.length > 0) {
|
|
172
178
|
useTagCache = true;
|
|
173
179
|
const providerPath = path.join(outputDir, "dynamodb-provider");
|
|
174
|
-
//Copy open-next.config.mjs into the bundle
|
|
180
|
+
// Copy open-next.config.mjs into the bundle
|
|
175
181
|
fs.mkdirSync(providerPath, { recursive: true });
|
|
176
182
|
buildHelper.copyOpenNextConfig(options.buildDir, providerPath);
|
|
177
183
|
// TODO: check if metafiles doesn't contain duplicates
|
|
178
184
|
fs.writeFileSync(path.join(providerPath, "dynamodb-cache.json"), JSON.stringify(metaFiles));
|
|
179
185
|
}
|
|
180
186
|
}
|
|
181
|
-
// We need to remove files later because we need the metafiles for dynamodb tags cache
|
|
182
|
-
buildHelper.removeFiles(outputPath, ({ relativePath }) => !relativePath.endsWith(".cache"));
|
|
183
187
|
return { useTagCache, metaFiles };
|
|
184
188
|
}
|
|
@@ -14,7 +14,7 @@ import { copyMiddlewareResources, generateEdgeBundle, } from "./edge/createEdgeB
|
|
|
14
14
|
import * as buildHelper from "./helper.js";
|
|
15
15
|
import { installDependencies } from "./installDeps.js";
|
|
16
16
|
import { applyCodePatches } from "./patch/codePatcher.js";
|
|
17
|
-
import
|
|
17
|
+
import * as patches from "./patch/patches/index.js";
|
|
18
18
|
export async function createServerBundle(options, codeCustomization) {
|
|
19
19
|
const { config } = options;
|
|
20
20
|
const foundRoutes = new Set();
|
|
@@ -119,13 +119,13 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
|
|
|
119
119
|
});
|
|
120
120
|
const additionalCodePatches = codeCustomization?.additionalCodePatches ?? [];
|
|
121
121
|
await applyCodePatches(options, tracedFiles, manifests, [
|
|
122
|
-
patchFetchCacheSetMissingWaitUntil,
|
|
123
|
-
patchFetchCacheForISR,
|
|
124
|
-
patchUnstableCacheForISR,
|
|
125
|
-
patchNextServer,
|
|
126
|
-
patchEnvVars,
|
|
127
|
-
patchBackgroundRevalidation,
|
|
128
|
-
patchUseCacheForISR,
|
|
122
|
+
patches.patchFetchCacheSetMissingWaitUntil,
|
|
123
|
+
patches.patchFetchCacheForISR,
|
|
124
|
+
patches.patchUnstableCacheForISR,
|
|
125
|
+
patches.patchNextServer,
|
|
126
|
+
patches.patchEnvVars,
|
|
127
|
+
patches.patchBackgroundRevalidation,
|
|
128
|
+
patches.patchUseCacheForISR,
|
|
129
129
|
...additionalCodePatches,
|
|
130
130
|
]);
|
|
131
131
|
// Build Lambda code
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { CodePatcher } from "../codePatcher.js";
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
4
|
-
|
|
2
|
+
export declare const disablePreloadingRule = "\nrule:\n kind: statement_block\n inside:\n kind: if_statement\n any:\n - has:\n kind: member_expression\n pattern: this.nextConfig.experimental.preloadEntriesOnStart\n stopBy: end\n - has:\n kind: binary_expression\n pattern: appDocumentPreloading === true\n stopBy: end\nfix:\n '{}'\n";
|
|
3
|
+
export declare const removeMiddlewareManifestRule = "\nrule:\n kind: statement_block\n inside:\n kind: method_definition\n has:\n kind: property_identifier\n regex: ^getMiddlewareManifest$\nfix:\n '{return null;}'\n";
|
|
4
|
+
/**
|
|
5
|
+
* Swaps the body for a throwing implementation
|
|
6
|
+
*
|
|
7
|
+
* @param methodName The name of the method
|
|
8
|
+
* @returns A rule to replace the body with a `throw`
|
|
9
|
+
*/
|
|
10
|
+
export declare function createEmptyBodyRule(methodName: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Drops `require("./node-environment-extensions/error-inspect");`
|
|
13
|
+
*/
|
|
14
|
+
export declare const errorInspectRule = "\nrule:\n pattern: require(\"./node-environment-extensions/error-inspect\");\nfix: |-\n // Removed by OpenNext\n // require(\"./node-environment-extensions/error-inspect\");\n";
|
|
5
15
|
export declare const patchNextServer: CodePatcher;
|
|
@@ -1,51 +1,24 @@
|
|
|
1
|
+
import { getCrossPlatformPathRegex } from "../../../utils/regex.js";
|
|
1
2
|
import { createPatchCode } from "../astCodePatcher.js";
|
|
2
|
-
//
|
|
3
|
-
export const minimalRule = `
|
|
4
|
-
rule:
|
|
5
|
-
kind: member_expression
|
|
6
|
-
pattern: process.env.NEXT_MINIMAL
|
|
7
|
-
any:
|
|
8
|
-
- inside:
|
|
9
|
-
kind: parenthesized_expression
|
|
10
|
-
stopBy: end
|
|
11
|
-
inside:
|
|
12
|
-
kind: if_statement
|
|
13
|
-
any:
|
|
14
|
-
- inside:
|
|
15
|
-
kind: statement_block
|
|
16
|
-
inside:
|
|
17
|
-
kind: method_definition
|
|
18
|
-
any:
|
|
19
|
-
- has: {kind: property_identifier, field: name, regex: runEdgeFunction}
|
|
20
|
-
- has: {kind: property_identifier, field: name, regex: runMiddleware}
|
|
21
|
-
- has: {kind: property_identifier, field: name, regex: imageOptimizer}
|
|
22
|
-
- has:
|
|
23
|
-
kind: statement_block
|
|
24
|
-
has:
|
|
25
|
-
kind: expression_statement
|
|
26
|
-
pattern: res.statusCode = 400;
|
|
27
|
-
fix:
|
|
28
|
-
'true'
|
|
29
|
-
`;
|
|
30
|
-
// This rule will disable the background preloading of route done by NextServer by default during the creation of NextServer
|
|
3
|
+
// Disable the background preloading of route done by NextServer by default during the creation of NextServer
|
|
31
4
|
export const disablePreloadingRule = `
|
|
32
5
|
rule:
|
|
33
6
|
kind: statement_block
|
|
34
7
|
inside:
|
|
35
8
|
kind: if_statement
|
|
36
|
-
any:
|
|
37
|
-
- has:
|
|
9
|
+
any:
|
|
10
|
+
- has:
|
|
38
11
|
kind: member_expression
|
|
39
12
|
pattern: this.nextConfig.experimental.preloadEntriesOnStart
|
|
40
13
|
stopBy: end
|
|
41
|
-
- has:
|
|
14
|
+
- has:
|
|
42
15
|
kind: binary_expression
|
|
43
16
|
pattern: appDocumentPreloading === true
|
|
44
17
|
stopBy: end
|
|
45
18
|
fix:
|
|
46
19
|
'{}'
|
|
47
20
|
`;
|
|
48
|
-
//
|
|
21
|
+
// Mostly for splitted edge functions so that we don't try to match them on the other non edge functions
|
|
49
22
|
export const removeMiddlewareManifestRule = `
|
|
50
23
|
rule:
|
|
51
24
|
kind: statement_block
|
|
@@ -53,39 +26,104 @@ rule:
|
|
|
53
26
|
kind: method_definition
|
|
54
27
|
has:
|
|
55
28
|
kind: property_identifier
|
|
56
|
-
regex: getMiddlewareManifest
|
|
29
|
+
regex: ^getMiddlewareManifest$
|
|
57
30
|
fix:
|
|
58
31
|
'{return null;}'
|
|
59
32
|
`;
|
|
33
|
+
/**
|
|
34
|
+
* Swaps the body for a throwing implementation
|
|
35
|
+
*
|
|
36
|
+
* @param methodName The name of the method
|
|
37
|
+
* @returns A rule to replace the body with a `throw`
|
|
38
|
+
*/
|
|
39
|
+
export function createEmptyBodyRule(methodName) {
|
|
40
|
+
return `
|
|
41
|
+
rule:
|
|
42
|
+
pattern:
|
|
43
|
+
selector: method_definition
|
|
44
|
+
context: "class { async ${methodName}($$$PARAMS) { $$$_ } }"
|
|
45
|
+
fix: |-
|
|
46
|
+
async ${methodName}($$$PARAMS) {
|
|
47
|
+
throw new Error("${methodName} should not be called with OpenNext");
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Drops `require("./node-environment-extensions/error-inspect");`
|
|
53
|
+
*/
|
|
54
|
+
export const errorInspectRule = `
|
|
55
|
+
rule:
|
|
56
|
+
pattern: require("./node-environment-extensions/error-inspect");
|
|
57
|
+
fix: |-
|
|
58
|
+
// Removed by OpenNext
|
|
59
|
+
// require("./node-environment-extensions/error-inspect");
|
|
60
|
+
`;
|
|
61
|
+
const pathFilter = getCrossPlatformPathRegex(String.raw `/next/dist/server/next-server\.js$`, {
|
|
62
|
+
escape: false,
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* Patches to avoid pulling babel (~4MB).
|
|
66
|
+
*
|
|
67
|
+
* Details:
|
|
68
|
+
* - empty `NextServer#runMiddleware` and `NextServer#runEdgeFunction` that are not used
|
|
69
|
+
* - drop `next/dist/server/node-environment-extensions/error-inspect.js`
|
|
70
|
+
*/
|
|
71
|
+
const babelPatches = [
|
|
72
|
+
// Empty the body of `NextServer#runMiddleware`
|
|
73
|
+
{
|
|
74
|
+
field: {
|
|
75
|
+
pathFilter,
|
|
76
|
+
contentFilter: /runMiddleware\(/,
|
|
77
|
+
patchCode: createPatchCode(createEmptyBodyRule("runMiddleware")),
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
// Empty the body of `NextServer#runEdgeFunction`
|
|
81
|
+
{
|
|
82
|
+
field: {
|
|
83
|
+
pathFilter,
|
|
84
|
+
contentFilter: /runEdgeFunction\(/,
|
|
85
|
+
patchCode: createPatchCode(createEmptyBodyRule("runEdgeFunction")),
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
// Drop `error-inspect` that pulls babel
|
|
89
|
+
{
|
|
90
|
+
field: {
|
|
91
|
+
pathFilter,
|
|
92
|
+
contentFilter: /error-inspect/,
|
|
93
|
+
patchCode: createPatchCode(errorInspectRule),
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
];
|
|
60
97
|
export const patchNextServer = {
|
|
61
98
|
name: "patch-next-server",
|
|
62
99
|
patches: [
|
|
63
|
-
//
|
|
100
|
+
// Empty the body of `NextServer#imageOptimizer` - unused in OpenNext
|
|
64
101
|
{
|
|
65
|
-
versions: ">=15.0.0",
|
|
66
102
|
field: {
|
|
67
|
-
pathFilter
|
|
68
|
-
contentFilter: /
|
|
69
|
-
patchCode: createPatchCode(
|
|
103
|
+
pathFilter,
|
|
104
|
+
contentFilter: /imageOptimizer\(/,
|
|
105
|
+
patchCode: createPatchCode(createEmptyBodyRule("imageOptimizer")),
|
|
70
106
|
},
|
|
71
107
|
},
|
|
72
|
-
// Disable Next background preloading done at creation of `
|
|
108
|
+
// Disable Next background preloading done at creation of `NextServer`
|
|
73
109
|
{
|
|
74
|
-
versions: ">=
|
|
110
|
+
versions: ">=14.0.0",
|
|
75
111
|
field: {
|
|
76
|
-
pathFilter
|
|
112
|
+
pathFilter,
|
|
77
113
|
contentFilter: /this\.nextConfig\.experimental\.preloadEntriesOnStart/,
|
|
78
114
|
patchCode: createPatchCode(disablePreloadingRule),
|
|
79
115
|
},
|
|
80
116
|
},
|
|
81
117
|
// Don't match edge functions in `NextServer`
|
|
82
118
|
{
|
|
83
|
-
|
|
119
|
+
// Next 12 and some version of 13 use the bundled middleware/edge function
|
|
120
|
+
versions: ">=14.0.0",
|
|
84
121
|
field: {
|
|
85
|
-
pathFilter
|
|
122
|
+
pathFilter,
|
|
86
123
|
contentFilter: /getMiddlewareManifest/,
|
|
87
124
|
patchCode: createPatchCode(removeMiddlewareManifestRule),
|
|
88
125
|
},
|
|
89
126
|
},
|
|
127
|
+
...babelPatches,
|
|
90
128
|
],
|
|
91
129
|
};
|
|
@@ -92,7 +92,8 @@ async function generateResult(event, localizedPath, cachedValue, lastModified) {
|
|
|
92
92
|
const cacheControl = await computeCacheControl(localizedPath, body, event.headers.host, cachedValue.revalidate, lastModified);
|
|
93
93
|
return {
|
|
94
94
|
type: "core",
|
|
95
|
-
|
|
95
|
+
// sometimes other status codes can be cached, like 404. For these cases, we should return the correct status code
|
|
96
|
+
statusCode: cachedValue.meta?.status ?? 200,
|
|
96
97
|
body: toReadableStream(body, false),
|
|
97
98
|
isBase64Encoded: false,
|
|
98
99
|
headers: {
|
|
@@ -259,7 +259,8 @@ export function handleRedirects(event, redirects) {
|
|
|
259
259
|
}
|
|
260
260
|
export function fixDataPage(internalEvent, buildId) {
|
|
261
261
|
const { rawPath, query } = internalEvent;
|
|
262
|
-
const
|
|
262
|
+
const basePath = NextConfig.basePath ?? "";
|
|
263
|
+
const dataPattern = `${basePath}/_next/data/${buildId}`;
|
|
263
264
|
// Return 404 for data requests that don't match the buildId
|
|
264
265
|
if (rawPath.startsWith("/_next/data") && !rawPath.startsWith(dataPattern)) {
|
|
265
266
|
return {
|
|
@@ -273,9 +274,9 @@ export function fixDataPage(internalEvent, buildId) {
|
|
|
273
274
|
};
|
|
274
275
|
}
|
|
275
276
|
if (rawPath.startsWith(dataPattern) && rawPath.endsWith(".json")) {
|
|
276
|
-
const newPath = rawPath
|
|
277
|
+
const newPath = `${basePath}${rawPath
|
|
277
278
|
.slice(dataPattern.length, -".json".length)
|
|
278
|
-
.replace(/^\/index$/, "/")
|
|
279
|
+
.replace(/^\/index$/, "/")}`;
|
|
279
280
|
query.__nextDataReq = "1";
|
|
280
281
|
return {
|
|
281
282
|
...internalEvent,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Transform } from "node:stream";
|
|
2
2
|
import { debug } from "../adapters/logger";
|
|
3
|
-
import {
|
|
3
|
+
import { parseHeaders, parseSetCookieHeader } from "./util";
|
|
4
4
|
const SET_COOKIE_HEADER = "set-cookie";
|
|
5
5
|
const CANNOT_BE_USED = "This cannot be used in OpenNext";
|
|
6
6
|
// We only need to implement the methods that are used by next.js
|
|
@@ -127,7 +127,7 @@ export class OpenNextNodeResponse extends Transform {
|
|
|
127
127
|
...this.initialHeaders,
|
|
128
128
|
...this.headers,
|
|
129
129
|
};
|
|
130
|
-
const initialCookies =
|
|
130
|
+
const initialCookies = parseSetCookieHeader(this.initialHeaders[SET_COOKIE_HEADER]?.toString());
|
|
131
131
|
this._cookies =
|
|
132
132
|
mergeHeadersPriority === "middleware"
|
|
133
133
|
? [...this._cookies, ...initialCookies]
|
package/dist/http/util.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type http from "node:http";
|
|
2
2
|
export declare const parseHeaders: (headers?: http.OutgoingHttpHeader[] | http.OutgoingHttpHeaders) => Record<string, string>;
|
|
3
3
|
export declare const convertHeader: (header: http.OutgoingHttpHeader) => string;
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Parses a (comma-separated) list of Set-Cookie headers
|
|
6
|
+
*
|
|
7
|
+
* @param cookies A comma-separated list of Set-Cookie headers or a list of Set-Cookie headers
|
|
8
|
+
* @returns A list of Set-Cookie header
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseSetCookieHeader(cookies: string | string[] | null | undefined): string[];
|
|
5
11
|
/**
|
|
6
12
|
*
|
|
7
13
|
* Get the query object from an iterable of [key, value] pairs
|
package/dist/http/util.js
CHANGED
|
@@ -20,13 +20,23 @@ export const convertHeader = (header) => {
|
|
|
20
20
|
}
|
|
21
21
|
return String(header);
|
|
22
22
|
};
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Parses a (comma-separated) list of Set-Cookie headers
|
|
25
|
+
*
|
|
26
|
+
* @param cookies A comma-separated list of Set-Cookie headers or a list of Set-Cookie headers
|
|
27
|
+
* @returns A list of Set-Cookie header
|
|
28
|
+
*/
|
|
29
|
+
export function parseSetCookieHeader(cookies) {
|
|
24
30
|
if (!cookies) {
|
|
25
31
|
return [];
|
|
26
32
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
if (typeof cookies === "string") {
|
|
34
|
+
// Split the cookie string on ",".
|
|
35
|
+
// Note that "," can also appear in the Expires value (i.e. `Expires=Thu, 01 June`)
|
|
36
|
+
// so we have to skip it with a negative lookbehind.
|
|
37
|
+
return cookies.split(/(?<!Expires=\w+),/i).map((c) => c.trim());
|
|
38
|
+
}
|
|
39
|
+
return cookies;
|
|
30
40
|
}
|
|
31
41
|
/**
|
|
32
42
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseSetCookieHeader } from "../../http/util";
|
|
2
2
|
import { fromReadableStream } from "../../utils/stream";
|
|
3
3
|
import { debug } from "../../adapters/logger";
|
|
4
4
|
import { convertToQuery } from "../../core/routing/util";
|
|
@@ -87,7 +87,7 @@ async function convertToApiGatewayProxyResultV2(result) {
|
|
|
87
87
|
const response = {
|
|
88
88
|
statusCode: result.statusCode,
|
|
89
89
|
headers,
|
|
90
|
-
cookies:
|
|
90
|
+
cookies: parseSetCookieHeader(result.headers["set-cookie"]),
|
|
91
91
|
body,
|
|
92
92
|
isBase64Encoded: result.isBase64Encoded,
|
|
93
93
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseSetCookieHeader } from "../../http/util";
|
|
2
2
|
import { fromReadableStream } from "../../utils/stream";
|
|
3
3
|
import { debug } from "../../adapters/logger";
|
|
4
4
|
import { convertToQuery, convertToQueryString } from "../../core/routing/util";
|
|
@@ -84,7 +84,7 @@ function convertToCloudfrontHeaders(headers, directResponse) {
|
|
|
84
84
|
(directResponse ? !cloudfrontReadOnlyHeaders.includes(key) : true))
|
|
85
85
|
.forEach(([key, value]) => {
|
|
86
86
|
if (key === "set-cookie") {
|
|
87
|
-
cloudfrontHeaders[key] =
|
|
87
|
+
cloudfrontHeaders[key] = parseSetCookieHeader(`${value}`).map((cookie) => ({
|
|
88
88
|
key,
|
|
89
89
|
value: cookie,
|
|
90
90
|
}));
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
-
import
|
|
2
|
+
import cookieParser from "cookie";
|
|
3
|
+
import { parseSetCookieHeader } from "../../http/util";
|
|
3
4
|
import { getQueryFromSearchParams } from "./utils.js";
|
|
5
|
+
// https://fetch.spec.whatwg.org/#statuses
|
|
6
|
+
const NULL_BODY_STATUSES = new Set([101, 103, 204, 205, 304]);
|
|
4
7
|
const converter = {
|
|
5
8
|
convertFrom: async (event) => {
|
|
6
9
|
const url = new URL(event.url);
|
|
@@ -15,7 +18,10 @@ const converter = {
|
|
|
15
18
|
const rawPath = url.pathname;
|
|
16
19
|
const method = event.method;
|
|
17
20
|
const shouldHaveBody = method !== "GET" && method !== "HEAD";
|
|
18
|
-
const
|
|
21
|
+
const cookieHeader = event.headers.get("cookie");
|
|
22
|
+
const cookies = cookieHeader
|
|
23
|
+
? cookieParser.parse(cookieHeader)
|
|
24
|
+
: {};
|
|
19
25
|
return {
|
|
20
26
|
type: "core",
|
|
21
27
|
method,
|
|
@@ -58,7 +64,7 @@ const converter = {
|
|
|
58
64
|
if (key === "set-cookie" && typeof value === "string") {
|
|
59
65
|
// If the value is a string, we need to parse it into an array
|
|
60
66
|
// This is the case for middleware direct result
|
|
61
|
-
const cookies =
|
|
67
|
+
const cookies = parseSetCookieHeader(value);
|
|
62
68
|
for (const cookie of cookies) {
|
|
63
69
|
headers.append(key, cookie);
|
|
64
70
|
}
|
|
@@ -73,7 +79,11 @@ const converter = {
|
|
|
73
79
|
headers.set(key, value);
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
|
-
return
|
|
82
|
+
// We should not return a body for statusCode's that doesn't allow bodies
|
|
83
|
+
const body = NULL_BODY_STATUSES.has(result.statusCode)
|
|
84
|
+
? null
|
|
85
|
+
: result.body;
|
|
86
|
+
return new Response(body, {
|
|
77
87
|
status: result.statusCode,
|
|
78
88
|
headers,
|
|
79
89
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import cookieParser from "cookie";
|
|
2
2
|
import { extractHostFromHeaders, getQueryFromSearchParams } from "./utils.js";
|
|
3
3
|
const converter = {
|
|
4
4
|
convertFrom: async (req) => {
|
|
@@ -20,6 +20,10 @@ const converter = {
|
|
|
20
20
|
// https://nodejs.org/api/http.html#messageurl
|
|
21
21
|
const url = new URL(`${req.protocol ? req.protocol : "http"}://${extractHostFromHeaders(headers)}${req.url}`);
|
|
22
22
|
const query = getQueryFromSearchParams(url.searchParams);
|
|
23
|
+
const cookieHeader = req.headers.cookie;
|
|
24
|
+
const cookies = cookieHeader
|
|
25
|
+
? cookieParser.parse(cookieHeader)
|
|
26
|
+
: {};
|
|
23
27
|
return {
|
|
24
28
|
type: "core",
|
|
25
29
|
method: req.method ?? "GET",
|
|
@@ -31,10 +35,7 @@ const converter = {
|
|
|
31
35
|
req.socket.remoteAddress ??
|
|
32
36
|
"::1",
|
|
33
37
|
query,
|
|
34
|
-
cookies
|
|
35
|
-
const [key, value] = cookie.split("=");
|
|
36
|
-
return [key, value];
|
|
37
|
-
}) ?? []),
|
|
38
|
+
cookies,
|
|
38
39
|
};
|
|
39
40
|
},
|
|
40
41
|
// Nothing to do here, it's streaming
|
|
@@ -72,10 +72,12 @@ export interface NextConfig {
|
|
|
72
72
|
experimental: {
|
|
73
73
|
serverActions?: boolean;
|
|
74
74
|
appDir?: boolean;
|
|
75
|
+
optimizeCss?: boolean;
|
|
75
76
|
};
|
|
76
77
|
images: ImageConfig;
|
|
77
78
|
poweredByHeader?: boolean;
|
|
78
79
|
serverExternalPackages?: string[];
|
|
80
|
+
deploymentId?: string;
|
|
79
81
|
}
|
|
80
82
|
export interface RouteDefinition {
|
|
81
83
|
page: string;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"access": "public"
|
|
4
4
|
},
|
|
5
5
|
"name": "@opennextjs/aws",
|
|
6
|
-
"version": "3.6.
|
|
6
|
+
"version": "3.6.6",
|
|
7
7
|
"bin": {
|
|
8
8
|
"open-next": "./dist/index.js"
|
|
9
9
|
},
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"@tsconfig/node18": "^1.0.1",
|
|
45
45
|
"aws4fetch": "^1.0.18",
|
|
46
46
|
"chalk": "^5.3.0",
|
|
47
|
+
"cookie": "^1.0.2",
|
|
47
48
|
"esbuild": "0.25.4",
|
|
48
49
|
"express": "5.0.1",
|
|
49
50
|
"path-to-regexp": "^6.3.0",
|