@opennextjs/aws 3.7.1 → 3.7.3
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/dist/adapters/cache.d.ts +1 -0
- package/dist/adapters/cache.js +14 -7
- package/dist/adapters/composable-cache.js +20 -7
- package/dist/adapters/middleware.js +2 -1
- package/dist/build/generateOutput.js +1 -1
- package/dist/build/installDeps.js +22 -0
- package/dist/build/patch/codePatcher.d.ts +19 -12
- package/dist/build/patch/codePatcher.js +21 -38
- package/dist/build/patch/patches/patchBackgroundRevalidation.d.ts +2 -4
- package/dist/build/patch/patches/patchBackgroundRevalidation.js +2 -4
- package/dist/build/patch/patches/patchEnvVar.js +9 -15
- package/dist/build/patch/patches/patchFetchCacheISR.d.ts +3 -3
- package/dist/build/patch/patches/patchFetchCacheISR.js +13 -19
- package/dist/build/patch/patches/patchFetchCacheWaitUntil.js +3 -5
- package/dist/build/patch/patches/patchNextServer.js +18 -30
- package/dist/core/requestHandler.js +3 -1
- package/dist/core/routing/cacheInterceptor.js +8 -0
- package/dist/core/routing/middleware.d.ts +1 -0
- package/dist/core/routing/middleware.js +7 -1
- package/dist/core/routing/util.d.ts +13 -0
- package/dist/core/routing/util.js +34 -1
- package/dist/core/routingHandler.d.ts +1 -0
- package/dist/core/routingHandler.js +4 -3
- package/dist/http/openNextResponse.d.ts +1 -1
- package/dist/http/openNextResponse.js +4 -1
- package/dist/overrides/wrappers/express-dev.js +6 -0
- package/dist/plugins/content-updater.d.ts +4 -3
- package/dist/plugins/content-updater.js +4 -5
- package/dist/types/open-next.d.ts +2 -0
- package/dist/types/overrides.d.ts +5 -0
- package/package.json +1 -1
package/dist/adapters/cache.d.ts
CHANGED
package/dist/adapters/cache.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getTagsFromValue, hasBeenRevalidated, writeTags } from "../utils/cache";
|
|
2
2
|
import { isBinaryContentType } from "../utils/binary";
|
|
3
3
|
import { debug, error, warn } from "./logger";
|
|
4
|
+
export const SOFT_TAG_PREFIX = "_N_T_/";
|
|
4
5
|
function isFetchCache(options) {
|
|
5
6
|
if (typeof options === "boolean") {
|
|
6
7
|
return options;
|
|
@@ -34,18 +35,22 @@ export default class Cache {
|
|
|
34
35
|
return null;
|
|
35
36
|
const _tags = [...(tags ?? []), ...(softTags ?? [])];
|
|
36
37
|
const _lastModified = cachedEntry.lastModified ?? Date.now();
|
|
37
|
-
const _hasBeenRevalidated =
|
|
38
|
+
const _hasBeenRevalidated = cachedEntry.shouldBypassTagCache
|
|
39
|
+
? false
|
|
40
|
+
: await hasBeenRevalidated(key, _tags, cachedEntry);
|
|
38
41
|
if (_hasBeenRevalidated)
|
|
39
42
|
return null;
|
|
40
43
|
// For cases where we don't have tags, we need to ensure that the soft tags are not being revalidated
|
|
41
44
|
// We only need to check for the path as it should already contain all the tags
|
|
42
45
|
if ((tags ?? []).length === 0) {
|
|
43
46
|
// Then we need to find the path for the given key
|
|
44
|
-
const path = softTags?.find((tag) => tag.startsWith(
|
|
47
|
+
const path = softTags?.find((tag) => tag.startsWith(SOFT_TAG_PREFIX) &&
|
|
45
48
|
!tag.endsWith("layout") &&
|
|
46
49
|
!tag.endsWith("page"));
|
|
47
50
|
if (path) {
|
|
48
|
-
const hasPathBeenUpdated =
|
|
51
|
+
const hasPathBeenUpdated = cachedEntry.shouldBypassTagCache
|
|
52
|
+
? false
|
|
53
|
+
: await hasBeenRevalidated(path.replace(SOFT_TAG_PREFIX, ""), [], cachedEntry);
|
|
49
54
|
if (hasPathBeenUpdated) {
|
|
50
55
|
// In case the path has been revalidated, we don't want to use the fetch cache
|
|
51
56
|
return null;
|
|
@@ -73,7 +78,9 @@ export default class Cache {
|
|
|
73
78
|
const meta = cacheData.meta;
|
|
74
79
|
const tags = getTagsFromValue(cacheData);
|
|
75
80
|
const _lastModified = cachedEntry.lastModified ?? Date.now();
|
|
76
|
-
const _hasBeenRevalidated =
|
|
81
|
+
const _hasBeenRevalidated = cachedEntry.shouldBypassTagCache
|
|
82
|
+
? false
|
|
83
|
+
: await hasBeenRevalidated(key, tags, cachedEntry);
|
|
77
84
|
if (_hasBeenRevalidated)
|
|
78
85
|
return null;
|
|
79
86
|
const store = globalThis.__openNextAls.getStore();
|
|
@@ -274,11 +281,11 @@ export default class Cache {
|
|
|
274
281
|
tag,
|
|
275
282
|
}));
|
|
276
283
|
// If the tag is a soft tag, we should also revalidate the hard tags
|
|
277
|
-
if (tag.startsWith(
|
|
284
|
+
if (tag.startsWith(SOFT_TAG_PREFIX)) {
|
|
278
285
|
for (const path of paths) {
|
|
279
286
|
// We need to find all hard tags for a given path
|
|
280
287
|
const _tags = await globalThis.tagCache.getByPath(path);
|
|
281
|
-
const hardTags = _tags.filter((t) => !t.startsWith(
|
|
288
|
+
const hardTags = _tags.filter((t) => !t.startsWith(SOFT_TAG_PREFIX));
|
|
282
289
|
// For every hard tag, we need to find all paths and revalidate them
|
|
283
290
|
for (const hardTag of hardTags) {
|
|
284
291
|
const _paths = await globalThis.tagCache.getByTag(hardTag);
|
|
@@ -296,7 +303,7 @@ export default class Cache {
|
|
|
296
303
|
// This only applies to `revalidateTag`, not to `res.revalidate()`
|
|
297
304
|
const uniquePaths = Array.from(new Set(toInsert
|
|
298
305
|
// We need to filter fetch cache key as they are not in the CDN
|
|
299
|
-
.filter((t) => t.tag.startsWith(
|
|
306
|
+
.filter((t) => t.tag.startsWith(SOFT_TAG_PREFIX))
|
|
300
307
|
.map((t) => `/${t.path}`)));
|
|
301
308
|
if (uniquePaths.length > 0) {
|
|
302
309
|
await globalThis.cdnInvalidationHandler.invalidatePaths(uniquePaths.map((path) => ({
|
|
@@ -8,7 +8,13 @@ export default {
|
|
|
8
8
|
// We first check if we have a pending write for this cache key
|
|
9
9
|
// If we do, we return the pending promise instead of fetching the cache
|
|
10
10
|
if (pendingWritePromiseMap.has(cacheKey)) {
|
|
11
|
-
|
|
11
|
+
const stored = pendingWritePromiseMap.get(cacheKey);
|
|
12
|
+
if (stored) {
|
|
13
|
+
return stored.then((entry) => ({
|
|
14
|
+
...entry,
|
|
15
|
+
value: toReadableStream(entry.value),
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
12
18
|
}
|
|
13
19
|
const result = await globalThis.incrementalCache.get(cacheKey, "composable");
|
|
14
20
|
if (!result?.value?.value) {
|
|
@@ -18,13 +24,17 @@ export default {
|
|
|
18
24
|
// We need to check if the tags associated with this entry has been revalidated
|
|
19
25
|
if (globalThis.tagCache.mode === "nextMode" &&
|
|
20
26
|
result.value.tags.length > 0) {
|
|
21
|
-
const hasBeenRevalidated =
|
|
27
|
+
const hasBeenRevalidated = result.shouldBypassTagCache
|
|
28
|
+
? false
|
|
29
|
+
: await globalThis.tagCache.hasBeenRevalidated(result.value.tags, result.lastModified);
|
|
22
30
|
if (hasBeenRevalidated)
|
|
23
31
|
return undefined;
|
|
24
32
|
}
|
|
25
33
|
else if (globalThis.tagCache.mode === "original" ||
|
|
26
34
|
globalThis.tagCache.mode === undefined) {
|
|
27
|
-
const hasBeenRevalidated =
|
|
35
|
+
const hasBeenRevalidated = result.shouldBypassTagCache
|
|
36
|
+
? false
|
|
37
|
+
: (await globalThis.tagCache.getLastModified(cacheKey, result.lastModified)) === -1;
|
|
28
38
|
if (hasBeenRevalidated)
|
|
29
39
|
return undefined;
|
|
30
40
|
}
|
|
@@ -39,14 +49,17 @@ export default {
|
|
|
39
49
|
}
|
|
40
50
|
},
|
|
41
51
|
async set(cacheKey, pendingEntry) {
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
const promiseEntry = pendingEntry.then(async (entry) => ({
|
|
53
|
+
...entry,
|
|
54
|
+
value: await fromReadableStream(entry.value),
|
|
55
|
+
}));
|
|
56
|
+
pendingWritePromiseMap.set(cacheKey, promiseEntry);
|
|
57
|
+
const entry = await promiseEntry.finally(() => {
|
|
44
58
|
pendingWritePromiseMap.delete(cacheKey);
|
|
45
59
|
});
|
|
46
|
-
const valueToStore = await fromReadableStream(entry.value);
|
|
47
60
|
await globalThis.incrementalCache.set(cacheKey, {
|
|
48
61
|
...entry,
|
|
49
|
-
value:
|
|
62
|
+
value: entry.value,
|
|
50
63
|
}, "composable");
|
|
51
64
|
if (globalThis.tagCache.mode === "original") {
|
|
52
65
|
const storedTags = await globalThis.tagCache.getByPath(cacheKey);
|
|
@@ -3,7 +3,7 @@ import { debug, error } from "../adapters/logger";
|
|
|
3
3
|
import { createGenericHandler } from "../core/createGenericHandler";
|
|
4
4
|
import { resolveAssetResolver, resolveIncrementalCache, resolveOriginResolver, resolveProxyRequest, resolveQueue, resolveTagCache, } from "../core/resolve";
|
|
5
5
|
import { constructNextUrl } from "../core/routing/util";
|
|
6
|
-
import routingHandler, { INTERNAL_EVENT_REQUEST_ID, INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_ROUTES, } from "../core/routingHandler";
|
|
6
|
+
import routingHandler, { INTERNAL_EVENT_REQUEST_ID, INTERNAL_HEADER_REWRITE_STATUS_CODE, INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_ROUTES, } from "../core/routingHandler";
|
|
7
7
|
globalThis.internalFetch = fetch;
|
|
8
8
|
globalThis.__openNextAls = new AsyncLocalStorage();
|
|
9
9
|
const defaultHandler = async (internalEvent, options) => {
|
|
@@ -39,6 +39,7 @@ const defaultHandler = async (internalEvent, options) => {
|
|
|
39
39
|
[INTERNAL_HEADER_INITIAL_URL]: internalEvent.url,
|
|
40
40
|
[INTERNAL_HEADER_RESOLVED_ROUTES]: JSON.stringify(result.resolvedRoutes),
|
|
41
41
|
[INTERNAL_EVENT_REQUEST_ID]: requestId,
|
|
42
|
+
[INTERNAL_HEADER_REWRITE_STATUS_CODE]: String(result.rewriteStatusCode),
|
|
42
43
|
},
|
|
43
44
|
},
|
|
44
45
|
isExternalRewrite: result.isExternalRewrite,
|
|
@@ -74,7 +74,7 @@ export async function generateOutput(options) {
|
|
|
74
74
|
Object.entries(config.functions ?? {}).forEach(async ([key, value]) => {
|
|
75
75
|
if (value.placement === "global") {
|
|
76
76
|
edgeFunctions[key] = {
|
|
77
|
-
bundle: `.open-next/functions/${key}`,
|
|
77
|
+
bundle: `.open-next/server-functions/${key}`,
|
|
78
78
|
handler: indexHandler,
|
|
79
79
|
...(await extractOverrideFn(value.override)),
|
|
80
80
|
};
|
|
@@ -35,6 +35,28 @@ export function installDependencies(outputDir, installOptions) {
|
|
|
35
35
|
});
|
|
36
36
|
// Copy the node_modules to the outputDir
|
|
37
37
|
fs.cpSync(path.join(tempInstallDir, "node_modules"), path.join(outputDir, "node_modules"), { recursive: true, force: true, dereference: true });
|
|
38
|
+
// This is a workaround for Node `22.17.0` and `22.17.1`
|
|
39
|
+
// https://github.com/nodejs/node/issues/59168
|
|
40
|
+
const nodeVersion = process.version;
|
|
41
|
+
if (nodeVersion === "v22.17.0" || nodeVersion === "v22.17.1") {
|
|
42
|
+
const tempBinDir = path.join(tempInstallDir, "node_modules", ".bin");
|
|
43
|
+
const outputBinDir = path.join(outputDir, "node_modules", ".bin");
|
|
44
|
+
for (const fileName of fs.readdirSync(tempBinDir)) {
|
|
45
|
+
const symlinkPath = path.join(tempBinDir, fileName);
|
|
46
|
+
const stat = fs.lstatSync(symlinkPath);
|
|
47
|
+
if (stat.isSymbolicLink()) {
|
|
48
|
+
const linkTarget = fs.readlinkSync(symlinkPath);
|
|
49
|
+
const realFilePath = path.resolve(tempBinDir, linkTarget);
|
|
50
|
+
const outputFilePath = path.join(outputBinDir, fileName);
|
|
51
|
+
if (fs.existsSync(outputFilePath)) {
|
|
52
|
+
fs.unlinkSync(outputFilePath);
|
|
53
|
+
}
|
|
54
|
+
fs.copyFileSync(realFilePath, outputFilePath);
|
|
55
|
+
fs.chmodSync(outputFilePath, "755");
|
|
56
|
+
logger.debug(`Replaced symlink ${fileName} with actual file`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
38
60
|
// Cleanup tempDir
|
|
39
61
|
fs.rmSync(tempInstallDir, { recursive: true, force: true });
|
|
40
62
|
logger.info(`Dependencies installed for ${name}`);
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import type { getManifests } from "../copyTracedFiles.js";
|
|
2
2
|
import * as buildHelper from "../helper.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
3
|
+
/**
|
|
4
|
+
* Accepted formats:
|
|
5
|
+
* - `">=16.0.0"`
|
|
6
|
+
* - `"<=16.0.0"`
|
|
7
|
+
* - `">=16.0.0 <=17.0.0"`
|
|
8
|
+
*
|
|
9
|
+
* **Be careful with spaces**
|
|
10
|
+
*/
|
|
11
|
+
export type Versions = `>=${number}.${number}.${number} <=${number}.${number}.${number}` | `>=${number}.${number}.${number}` | `<=${number}.${number}.${number}`;
|
|
13
12
|
export type PatchCodeFn = (args: {
|
|
14
13
|
/**
|
|
15
14
|
* The code of the file that needs to be patched
|
|
@@ -32,15 +31,23 @@ interface IndividualPatch {
|
|
|
32
31
|
pathFilter: RegExp;
|
|
33
32
|
contentFilter?: RegExp;
|
|
34
33
|
patchCode: PatchCodeFn;
|
|
34
|
+
versions?: Versions;
|
|
35
35
|
}
|
|
36
36
|
export interface CodePatcher {
|
|
37
37
|
name: string;
|
|
38
|
-
patches: IndividualPatch
|
|
38
|
+
patches: IndividualPatch[];
|
|
39
39
|
}
|
|
40
40
|
export declare function parseVersions(versions?: Versions): {
|
|
41
41
|
before?: string;
|
|
42
42
|
after?: string;
|
|
43
43
|
};
|
|
44
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Check whether the version is in the range
|
|
46
|
+
*
|
|
47
|
+
* @param version A semver version
|
|
48
|
+
* @param versionRange A version range
|
|
49
|
+
* @returns whether the version satisfies the range
|
|
50
|
+
*/
|
|
51
|
+
export declare function isVersionInRange(version: string, versionRange?: Versions): boolean;
|
|
45
52
|
export declare function applyCodePatches(buildOptions: buildHelper.BuildOptions, tracedFiles: string[], manifests: ReturnType<typeof getManifests>, codePatcher: CodePatcher[]): Promise<void>;
|
|
46
53
|
export {};
|
|
@@ -39,50 +39,33 @@ export function parseVersions(versions) {
|
|
|
39
39
|
after,
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
// before only
|
|
60
|
-
if (before) {
|
|
61
|
-
if (buildHelper.compareSemver(version, "<=", before)) {
|
|
62
|
-
result.push(field.field);
|
|
63
|
-
}
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
// after only
|
|
67
|
-
if (after) {
|
|
68
|
-
if (buildHelper.compareSemver(version, ">=", after)) {
|
|
69
|
-
result.push(field.field);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
42
|
+
/**
|
|
43
|
+
* Check whether the version is in the range
|
|
44
|
+
*
|
|
45
|
+
* @param version A semver version
|
|
46
|
+
* @param versionRange A version range
|
|
47
|
+
* @returns whether the version satisfies the range
|
|
48
|
+
*/
|
|
49
|
+
export function isVersionInRange(version, versionRange) {
|
|
50
|
+
const { before, after } = parseVersions(versionRange);
|
|
51
|
+
let inRange = true;
|
|
52
|
+
if (before) {
|
|
53
|
+
inRange &&= buildHelper.compareSemver(version, "<=", before);
|
|
54
|
+
}
|
|
55
|
+
if (after) {
|
|
56
|
+
inRange &&= buildHelper.compareSemver(version, ">=", after);
|
|
72
57
|
}
|
|
73
|
-
return
|
|
58
|
+
return inRange;
|
|
74
59
|
}
|
|
75
60
|
export async function applyCodePatches(buildOptions, tracedFiles, manifests, codePatcher) {
|
|
76
|
-
const nextVersion = buildOptions.nextVersion;
|
|
77
61
|
logger.time("Applying code patches");
|
|
78
62
|
// We first filter against the version
|
|
79
63
|
// We also flatten the array of patches so that we get both the name and all the necessary patches
|
|
80
|
-
const patchesToApply = codePatcher.flatMap(({ name, patches }) =>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
patch,
|
|
84
|
-
|
|
85
|
-
: [{ name, patch: patches }]);
|
|
64
|
+
const patchesToApply = codePatcher.flatMap(({ name, patches }) => {
|
|
65
|
+
return patches
|
|
66
|
+
.filter(({ versions }) => isVersionInRange(buildOptions.nextVersion, versions))
|
|
67
|
+
.map((patch) => ({ patch, name }));
|
|
68
|
+
});
|
|
86
69
|
await Promise.all(tracedFiles.map(async (filePath) => {
|
|
87
70
|
// We check the filename against the filter to see if we should apply the patch
|
|
88
71
|
const patchMatchingPath = patchesToApply.filter(({ patch }) => {
|
|
@@ -3,9 +3,7 @@ export declare const patchBackgroundRevalidation: {
|
|
|
3
3
|
name: string;
|
|
4
4
|
patches: {
|
|
5
5
|
versions: ">=14.1.0";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
patchCode: import("../codePatcher.js").PatchCodeFn;
|
|
9
|
-
};
|
|
6
|
+
pathFilter: RegExp;
|
|
7
|
+
patchCode: import("../codePatcher.js").PatchCodeFn;
|
|
10
8
|
}[];
|
|
11
9
|
};
|
|
@@ -18,10 +18,8 @@ export const patchBackgroundRevalidation = {
|
|
|
18
18
|
{
|
|
19
19
|
// TODO: test for earlier versions of Next
|
|
20
20
|
versions: ">=14.1.0",
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
patchCode: createPatchCode(rule),
|
|
24
|
-
},
|
|
21
|
+
pathFilter: getCrossPlatformPathRegex("server/response-cache/index.js"),
|
|
22
|
+
patchCode: createPatchCode(rule),
|
|
25
23
|
},
|
|
26
24
|
],
|
|
27
25
|
};
|
|
@@ -24,29 +24,23 @@ export const patchEnvVars = {
|
|
|
24
24
|
// This patch will set the `NEXT_RUNTIME` env var to "node" to avoid loading unnecessary edge deps at runtime
|
|
25
25
|
{
|
|
26
26
|
versions: ">=15.0.0",
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
patchCode: createPatchCode(envVarRuleCreator("NEXT_RUNTIME", '"node"')),
|
|
31
|
-
},
|
|
27
|
+
pathFilter: /module\.compiled\.js$/,
|
|
28
|
+
contentFilter: /process\.env\.NEXT_RUNTIME/,
|
|
29
|
+
patchCode: createPatchCode(envVarRuleCreator("NEXT_RUNTIME", '"node"')),
|
|
32
30
|
},
|
|
33
31
|
// This patch will set `NODE_ENV` to production to avoid loading unnecessary dev deps at runtime
|
|
34
32
|
{
|
|
35
33
|
versions: ">=15.0.0",
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
patchCode: createPatchCode(envVarRuleCreator("NODE_ENV", '"production"')),
|
|
40
|
-
},
|
|
34
|
+
pathFilter: /(module\.compiled|react\/index|react\/jsx-runtime|react-dom\/index)\.js$/,
|
|
35
|
+
contentFilter: /process\.env\.NODE_ENV/,
|
|
36
|
+
patchCode: createPatchCode(envVarRuleCreator("NODE_ENV", '"production"')),
|
|
41
37
|
},
|
|
42
38
|
// This patch will set `TURBOPACK` env to false to avoid loading turbopack related deps at runtime
|
|
43
39
|
{
|
|
44
40
|
versions: ">=15.0.0",
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
patchCode: createPatchCode(envVarRuleCreator("TURBOPACK", "false")),
|
|
49
|
-
},
|
|
41
|
+
pathFilter: /module\.compiled\.js$/,
|
|
42
|
+
contentFilter: /process\.env\.TURBOPACK/,
|
|
43
|
+
patchCode: createPatchCode(envVarRuleCreator("TURBOPACK", "false")),
|
|
50
44
|
},
|
|
51
45
|
],
|
|
52
46
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CodePatcher } from "../codePatcher.js";
|
|
2
|
-
export declare const fetchRule = "\nrule:\n kind: member_expression\n pattern: $WORK_STORE.isOnDemandRevalidate\n inside:\n kind: ternary_expression\n all:\n - has: {kind: 'null'}\n - has
|
|
3
|
-
export declare const unstable_cacheRule = "\nrule:\n kind: member_expression\n pattern: $STORE_OR_CACHE.isOnDemandRevalidate\n inside:\n kind: if_statement\n stopBy: end\n has:\n kind: statement_block\n has:\n kind: variable_declarator\n has
|
|
4
|
-
export declare const useCacheRule = "\nrule:\n kind: member_expression\n pattern: $STORE_OR_CACHE.isOnDemandRevalidate\n inside:\n kind: binary_expression\n has
|
|
2
|
+
export declare const fetchRule = "\nrule:\n kind: member_expression\n pattern: $WORK_STORE.isOnDemandRevalidate\n inside:\n kind: ternary_expression\n all:\n - has: {kind: 'null'}\n - has:\n kind: await_expression\n has:\n kind: call_expression\n all:\n - has:\n kind: member_expression\n has:\n kind: property_identifier\n field: property\n regex: get\n - has:\n kind: arguments\n has:\n kind: object\n has:\n kind: pair\n all:\n - has:\n kind: property_identifier\n field: key\n regex: softTags\n inside:\n kind: variable_declarator\n\nfix:\n ($WORK_STORE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)\n";
|
|
3
|
+
export declare const unstable_cacheRule = "\nrule:\n kind: member_expression\n pattern: $STORE_OR_CACHE.isOnDemandRevalidate\n inside:\n kind: if_statement\n stopBy: end\n has:\n kind: statement_block\n has:\n kind: variable_declarator\n has:\n kind: await_expression\n has:\n kind: call_expression\n all:\n - has:\n kind: member_expression\n has:\n kind: property_identifier\n field: property\n regex: get\n - has:\n kind: arguments\n has:\n kind: object\n has:\n kind: pair\n all:\n - has:\n kind: property_identifier\n field: key\n regex: softTags\n stopBy: end\nfix:\n ($STORE_OR_CACHE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)\n";
|
|
4
|
+
export declare const useCacheRule = "\nrule:\n kind: member_expression\n pattern: $STORE_OR_CACHE.isOnDemandRevalidate\n inside:\n kind: binary_expression\n has:\n kind: member_expression\n pattern: $STORE_OR_CACHE.isDraftMode\n inside:\n kind: if_statement\n stopBy: end\n has:\n kind: return_statement\n any:\n - has:\n kind: 'true'\n - has:\n regex: '!0'\n stopBy: end\nfix:\n '($STORE_OR_CACHE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)'";
|
|
5
5
|
export declare const patchFetchCacheForISR: CodePatcher;
|
|
6
6
|
export declare const patchUnstableCacheForISR: CodePatcher;
|
|
7
7
|
export declare const patchUseCacheForISR: CodePatcher;
|
|
@@ -9,7 +9,7 @@ rule:
|
|
|
9
9
|
kind: ternary_expression
|
|
10
10
|
all:
|
|
11
11
|
- has: {kind: 'null'}
|
|
12
|
-
- has:
|
|
12
|
+
- has:
|
|
13
13
|
kind: await_expression
|
|
14
14
|
has:
|
|
15
15
|
kind: call_expression
|
|
@@ -48,7 +48,7 @@ rule:
|
|
|
48
48
|
kind: statement_block
|
|
49
49
|
has:
|
|
50
50
|
kind: variable_declarator
|
|
51
|
-
has:
|
|
51
|
+
has:
|
|
52
52
|
kind: await_expression
|
|
53
53
|
has:
|
|
54
54
|
kind: call_expression
|
|
@@ -80,13 +80,13 @@ rule:
|
|
|
80
80
|
pattern: $STORE_OR_CACHE.isOnDemandRevalidate
|
|
81
81
|
inside:
|
|
82
82
|
kind: binary_expression
|
|
83
|
-
has:
|
|
83
|
+
has:
|
|
84
84
|
kind: member_expression
|
|
85
85
|
pattern: $STORE_OR_CACHE.isDraftMode
|
|
86
86
|
inside:
|
|
87
87
|
kind: if_statement
|
|
88
88
|
stopBy: end
|
|
89
|
-
has:
|
|
89
|
+
has:
|
|
90
90
|
kind: return_statement
|
|
91
91
|
any:
|
|
92
92
|
- has:
|
|
@@ -101,11 +101,9 @@ export const patchFetchCacheForISR = {
|
|
|
101
101
|
patches: [
|
|
102
102
|
{
|
|
103
103
|
versions: ">=14.0.0",
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
patchCode: createPatchCode(fetchRule, Lang.JavaScript),
|
|
108
|
-
},
|
|
104
|
+
pathFilter: getCrossPlatformPathRegex(String.raw `(server/chunks/.*\.js|.*\.runtime\..*\.js|patch-fetch\.js)$`, { escape: false }),
|
|
105
|
+
contentFilter: /\.isOnDemandRevalidate/,
|
|
106
|
+
patchCode: createPatchCode(fetchRule, Lang.JavaScript),
|
|
109
107
|
},
|
|
110
108
|
],
|
|
111
109
|
};
|
|
@@ -114,11 +112,9 @@ export const patchUnstableCacheForISR = {
|
|
|
114
112
|
patches: [
|
|
115
113
|
{
|
|
116
114
|
versions: ">=14.2.0",
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
patchCode: createPatchCode(unstable_cacheRule, Lang.JavaScript),
|
|
121
|
-
},
|
|
115
|
+
pathFilter: getCrossPlatformPathRegex(String.raw `(server/chunks/.*\.js|.*\.runtime\..*\.js|spec-extension/unstable-cache\.js)$`, { escape: false }),
|
|
116
|
+
contentFilter: /\.isOnDemandRevalidate/,
|
|
117
|
+
patchCode: createPatchCode(unstable_cacheRule, Lang.JavaScript),
|
|
122
118
|
},
|
|
123
119
|
],
|
|
124
120
|
};
|
|
@@ -127,11 +123,9 @@ export const patchUseCacheForISR = {
|
|
|
127
123
|
patches: [
|
|
128
124
|
{
|
|
129
125
|
versions: ">=15.3.0",
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
patchCode: createPatchCode(useCacheRule, Lang.JavaScript),
|
|
134
|
-
},
|
|
126
|
+
pathFilter: getCrossPlatformPathRegex(String.raw `(server/chunks/.*\.js|\.runtime\..*\.js|use-cache/use-cache-wrapper\.js)$`, { escape: false }),
|
|
127
|
+
contentFilter: /\.isOnDemandRevalidate/,
|
|
128
|
+
patchCode: createPatchCode(useCacheRule, Lang.JavaScript),
|
|
135
129
|
},
|
|
136
130
|
],
|
|
137
131
|
};
|
|
@@ -26,11 +26,9 @@ export const patchFetchCacheSetMissingWaitUntil = {
|
|
|
26
26
|
patches: [
|
|
27
27
|
{
|
|
28
28
|
versions: ">=15.0.0",
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
patchCode: createPatchCode(rule),
|
|
33
|
-
},
|
|
29
|
+
pathFilter: getCrossPlatformPathRegex(String.raw `(server/chunks/.*\.js|.*\.runtime\..*\.js|patch-fetch\.js)$`, { escape: false }),
|
|
30
|
+
contentFilter: /arrayBuffer\(\)\s*\.then/,
|
|
31
|
+
patchCode: createPatchCode(rule),
|
|
34
32
|
},
|
|
35
33
|
],
|
|
36
34
|
};
|
|
@@ -71,27 +71,21 @@ const pathFilter = getCrossPlatformPathRegex(String.raw `/next/dist/server/next-
|
|
|
71
71
|
const babelPatches = [
|
|
72
72
|
// Empty the body of `NextServer#runMiddleware`
|
|
73
73
|
{
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
patchCode: createPatchCode(createEmptyBodyRule("runMiddleware")),
|
|
78
|
-
},
|
|
74
|
+
pathFilter,
|
|
75
|
+
contentFilter: /runMiddleware\(/,
|
|
76
|
+
patchCode: createPatchCode(createEmptyBodyRule("runMiddleware")),
|
|
79
77
|
},
|
|
80
78
|
// Empty the body of `NextServer#runEdgeFunction`
|
|
81
79
|
{
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
patchCode: createPatchCode(createEmptyBodyRule("runEdgeFunction")),
|
|
86
|
-
},
|
|
80
|
+
pathFilter,
|
|
81
|
+
contentFilter: /runEdgeFunction\(/,
|
|
82
|
+
patchCode: createPatchCode(createEmptyBodyRule("runEdgeFunction")),
|
|
87
83
|
},
|
|
88
84
|
// Drop `error-inspect` that pulls babel
|
|
89
85
|
{
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
patchCode: createPatchCode(errorInspectRule),
|
|
94
|
-
},
|
|
86
|
+
pathFilter,
|
|
87
|
+
contentFilter: /error-inspect/,
|
|
88
|
+
patchCode: createPatchCode(errorInspectRule),
|
|
95
89
|
},
|
|
96
90
|
];
|
|
97
91
|
export const patchNextServer = {
|
|
@@ -99,30 +93,24 @@ export const patchNextServer = {
|
|
|
99
93
|
patches: [
|
|
100
94
|
// Empty the body of `NextServer#imageOptimizer` - unused in OpenNext
|
|
101
95
|
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
patchCode: createPatchCode(createEmptyBodyRule("imageOptimizer")),
|
|
106
|
-
},
|
|
96
|
+
pathFilter,
|
|
97
|
+
contentFilter: /imageOptimizer\(/,
|
|
98
|
+
patchCode: createPatchCode(createEmptyBodyRule("imageOptimizer")),
|
|
107
99
|
},
|
|
108
100
|
// Disable Next background preloading done at creation of `NextServer`
|
|
109
101
|
{
|
|
110
102
|
versions: ">=14.0.0",
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
patchCode: createPatchCode(disablePreloadingRule),
|
|
115
|
-
},
|
|
103
|
+
pathFilter,
|
|
104
|
+
contentFilter: /this\.nextConfig\.experimental\.preloadEntriesOnStart/,
|
|
105
|
+
patchCode: createPatchCode(disablePreloadingRule),
|
|
116
106
|
},
|
|
117
107
|
// Don't match edge functions in `NextServer`
|
|
118
108
|
{
|
|
119
109
|
// Next 12 and some version of 13 use the bundled middleware/edge function
|
|
120
110
|
versions: ">=14.0.0",
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
patchCode: createPatchCode(removeMiddlewareManifestRule),
|
|
125
|
-
},
|
|
111
|
+
pathFilter,
|
|
112
|
+
contentFilter: /getMiddlewareManifest/,
|
|
113
|
+
patchCode: createPatchCode(removeMiddlewareManifestRule),
|
|
126
114
|
},
|
|
127
115
|
...babelPatches,
|
|
128
116
|
],
|
|
@@ -5,7 +5,7 @@ import { NextConfig } from "../adapters/config/index";
|
|
|
5
5
|
import { debug, error } from "../adapters/logger";
|
|
6
6
|
import { patchAsyncStorage } from "./patchAsyncStorage";
|
|
7
7
|
import { constructNextUrl, convertRes, convertToQuery, convertToQueryString, createServerResponse, } from "./routing/util";
|
|
8
|
-
import routingHandler, { INTERNAL_EVENT_REQUEST_ID, INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_ROUTES, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_HEADER_PREFIX_LEN, } from "./routingHandler";
|
|
8
|
+
import routingHandler, { INTERNAL_EVENT_REQUEST_ID, INTERNAL_HEADER_REWRITE_STATUS_CODE, INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_ROUTES, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_HEADER_PREFIX_LEN, } from "./routingHandler";
|
|
9
9
|
import { requestHandler, setNextjsPrebundledReact } from "./util";
|
|
10
10
|
// This is used to identify requests in the cache
|
|
11
11
|
globalThis.__openNextAls = new AsyncLocalStorage();
|
|
@@ -37,6 +37,7 @@ export async function openNextHandler(internalEvent, options) {
|
|
|
37
37
|
resolvedRoutes: initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES]
|
|
38
38
|
? JSON.parse(initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES])
|
|
39
39
|
: [],
|
|
40
|
+
rewriteStatusCode: Number.parseInt(initialHeaders[INTERNAL_HEADER_REWRITE_STATUS_CODE]),
|
|
40
41
|
};
|
|
41
42
|
let routingResult = {
|
|
42
43
|
internalEvent,
|
|
@@ -263,6 +264,7 @@ async function tryRenderError(type, res, internalEvent) {
|
|
|
263
264
|
}
|
|
264
265
|
catch (e) {
|
|
265
266
|
error("NextJS request failed.", e);
|
|
267
|
+
res.statusCode = 500;
|
|
266
268
|
res.setHeader("Content-Type", "application/json");
|
|
267
269
|
res.end(JSON.stringify({
|
|
268
270
|
message: "Server failed to respond.",
|
|
@@ -144,6 +144,14 @@ export async function cacheInterceptor(event) {
|
|
|
144
144
|
if (Boolean(event.headers["next-action"]) ||
|
|
145
145
|
Boolean(event.headers["x-prerender-revalidate"]))
|
|
146
146
|
return event;
|
|
147
|
+
// Check for Next.js preview mode cookies
|
|
148
|
+
const cookies = event.headers.cookie || "";
|
|
149
|
+
const hasPreviewData = cookies.includes("__prerender_bypass") ||
|
|
150
|
+
cookies.includes("__next_preview_data");
|
|
151
|
+
if (hasPreviewData) {
|
|
152
|
+
debug("Preview mode detected, passing through to handler");
|
|
153
|
+
return event;
|
|
154
|
+
}
|
|
147
155
|
// We localize the path in case i18n is enabled
|
|
148
156
|
let localizedPath = localizePath(event);
|
|
149
157
|
// If using basePath we need to remove it from the path
|
|
@@ -2,6 +2,7 @@ import type { InternalEvent, InternalResult } from "../../types/open-next.js";
|
|
|
2
2
|
type MiddlewareEvent = InternalEvent & {
|
|
3
3
|
responseHeaders?: Record<string, string | string[]>;
|
|
4
4
|
isExternalRewrite?: boolean;
|
|
5
|
+
rewriteStatusCode?: number;
|
|
5
6
|
};
|
|
6
7
|
type Middleware = (request: Request) => Response | Promise<Response>;
|
|
7
8
|
type MiddlewareLoader = () => Promise<{
|
|
@@ -2,10 +2,11 @@ import { FunctionsConfigManifest, MiddlewareManifest, NextConfig, PrerenderManif
|
|
|
2
2
|
import { emptyReadableStream } from "../../utils/stream.js";
|
|
3
3
|
import { getQueryFromSearchParams } from "../../overrides/converters/utils.js";
|
|
4
4
|
import { localizePath } from "./i18n/index.js";
|
|
5
|
-
import { convertBodyToReadableStream, getMiddlewareMatch, isExternal, } from "./util.js";
|
|
5
|
+
import { convertBodyToReadableStream, getMiddlewareMatch, isExternal, normalizeLocationHeader, } from "./util.js";
|
|
6
6
|
const middlewareManifest = MiddlewareManifest;
|
|
7
7
|
const functionsConfigManifest = FunctionsConfigManifest;
|
|
8
8
|
const middleMatch = getMiddlewareMatch(middlewareManifest, functionsConfigManifest);
|
|
9
|
+
const REDIRECTS = new Set([301, 302, 303, 307, 308]);
|
|
9
10
|
function defaultMiddlewareLoader() {
|
|
10
11
|
// @ts-expect-error - This is bundled
|
|
11
12
|
return import("./middleware.mjs");
|
|
@@ -94,6 +95,10 @@ export async function handleMiddleware(internalEvent, initialSearch, middlewareL
|
|
|
94
95
|
? [...resHeaders[key], value]
|
|
95
96
|
: [value];
|
|
96
97
|
}
|
|
98
|
+
else if (REDIRECTS.has(statusCode) &&
|
|
99
|
+
key.toLowerCase() === "location") {
|
|
100
|
+
resHeaders[key] = normalizeLocationHeader(value, internalEvent.url);
|
|
101
|
+
}
|
|
97
102
|
else {
|
|
98
103
|
resHeaders[key] = value;
|
|
99
104
|
}
|
|
@@ -146,5 +151,6 @@ export async function handleMiddleware(internalEvent, initialSearch, middlewareL
|
|
|
146
151
|
cookies: internalEvent.cookies,
|
|
147
152
|
remoteAddress: internalEvent.remoteAddress,
|
|
148
153
|
isExternalRewrite,
|
|
154
|
+
rewriteStatusCode: statusCode,
|
|
149
155
|
};
|
|
150
156
|
}
|
|
@@ -104,3 +104,16 @@ export declare function fixISRHeaders(headers: OutgoingHttpHeaders): void;
|
|
|
104
104
|
*/
|
|
105
105
|
export declare function createServerResponse(routingResult: RoutingResult, headers: Record<string, string | string[] | undefined>, responseStream?: StreamCreator): OpenNextNodeResponse;
|
|
106
106
|
export declare function invalidateCDNOnRequest(params: RoutingResult, headers: OutgoingHttpHeaders): Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Normalizes the Location header to either be a relative path or a full URL.
|
|
109
|
+
* If the Location header is relative to the origin, it will return a relative path.
|
|
110
|
+
* If it is an absolute URL, it will return the full URL.
|
|
111
|
+
* Redirects from Next config query parameters are encoded using `stringifyQs`
|
|
112
|
+
* Redirects from the middleware the query parameters are not encoded.
|
|
113
|
+
*
|
|
114
|
+
* @param location The Location header value
|
|
115
|
+
* @param baseUrl The base URL to use for relative paths (i.e the original request URL)
|
|
116
|
+
* @param encodeQuery Optional flag to indicate if query parameters should be encoded in the Location header
|
|
117
|
+
* @returns An absolute or relative Location header value
|
|
118
|
+
*/
|
|
119
|
+
export declare function normalizeLocationHeader(location: string, baseUrl: string, encodeQuery?: boolean): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
+
import { parse as parseQs, stringify as stringifyQs } from "node:querystring";
|
|
2
3
|
import { Readable } from "node:stream";
|
|
3
4
|
import { BuildId, HtmlPages, NextConfig } from "../../adapters/config/index.js";
|
|
4
5
|
import { OpenNextNodeResponse } from "../../http/openNextResponse.js";
|
|
@@ -338,7 +339,7 @@ export function createServerResponse(routingResult, headers, responseStream) {
|
|
|
338
339
|
}, async (_headers) => {
|
|
339
340
|
await revalidateIfRequired(internalEvent.headers.host, internalEvent.rawPath, _headers);
|
|
340
341
|
await invalidateCDNOnRequest(routingResult, _headers);
|
|
341
|
-
}, responseStream, headers);
|
|
342
|
+
}, responseStream, headers, routingResult.rewriteStatusCode);
|
|
342
343
|
}
|
|
343
344
|
// This function is used only for `res.revalidate()`
|
|
344
345
|
export async function invalidateCDNOnRequest(params, headers) {
|
|
@@ -356,3 +357,35 @@ export async function invalidateCDNOnRequest(params, headers) {
|
|
|
356
357
|
]);
|
|
357
358
|
}
|
|
358
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* Normalizes the Location header to either be a relative path or a full URL.
|
|
362
|
+
* If the Location header is relative to the origin, it will return a relative path.
|
|
363
|
+
* If it is an absolute URL, it will return the full URL.
|
|
364
|
+
* Redirects from Next config query parameters are encoded using `stringifyQs`
|
|
365
|
+
* Redirects from the middleware the query parameters are not encoded.
|
|
366
|
+
*
|
|
367
|
+
* @param location The Location header value
|
|
368
|
+
* @param baseUrl The base URL to use for relative paths (i.e the original request URL)
|
|
369
|
+
* @param encodeQuery Optional flag to indicate if query parameters should be encoded in the Location header
|
|
370
|
+
* @returns An absolute or relative Location header value
|
|
371
|
+
*/
|
|
372
|
+
export function normalizeLocationHeader(location, baseUrl, encodeQuery = false) {
|
|
373
|
+
if (!URL.canParse(location)) {
|
|
374
|
+
// If the location is not a valid URL, return it as-is
|
|
375
|
+
return location;
|
|
376
|
+
}
|
|
377
|
+
const locationURL = new URL(location);
|
|
378
|
+
const origin = new URL(baseUrl).origin;
|
|
379
|
+
let search = locationURL.search;
|
|
380
|
+
// If encodeQuery is true, we need to encode the query parameters
|
|
381
|
+
// We could have used URLSearchParams, but that doesn't match what Next does.
|
|
382
|
+
if (encodeQuery && search) {
|
|
383
|
+
search = `?${stringifyQs(parseQs(search.slice(1)))}`;
|
|
384
|
+
}
|
|
385
|
+
const href = `${locationURL.origin}${locationURL.pathname}${search}${locationURL.hash}`;
|
|
386
|
+
// The URL is relative if the origin is the same as the base URL's origin
|
|
387
|
+
if (locationURL.origin === origin) {
|
|
388
|
+
return href.slice(origin.length);
|
|
389
|
+
}
|
|
390
|
+
return href;
|
|
391
|
+
}
|
|
@@ -6,6 +6,7 @@ export declare const INTERNAL_HEADER_PREFIX = "x-opennext-";
|
|
|
6
6
|
export declare const INTERNAL_HEADER_INITIAL_URL = "x-opennext-initial-url";
|
|
7
7
|
export declare const INTERNAL_HEADER_LOCALE = "x-opennext-locale";
|
|
8
8
|
export declare const INTERNAL_HEADER_RESOLVED_ROUTES = "x-opennext-resolved-routes";
|
|
9
|
+
export declare const INTERNAL_HEADER_REWRITE_STATUS_CODE = "x-opennext-rewrite-status-code";
|
|
9
10
|
export declare const INTERNAL_EVENT_REQUEST_ID = "x-opennext-request-id";
|
|
10
11
|
export default function routingHandler(event: InternalEvent, { assetResolver }: {
|
|
11
12
|
assetResolver?: AssetResolver;
|
|
@@ -5,13 +5,14 @@ import { detectLocale } from "./routing/i18n";
|
|
|
5
5
|
import { fixDataPage, getNextConfigHeaders, handleFallbackFalse, handleRedirects, handleRewrites, } from "./routing/matcher";
|
|
6
6
|
import { handleMiddleware } from "./routing/middleware";
|
|
7
7
|
import { dynamicRouteMatcher, staticRouteMatcher, } from "./routing/routeMatcher";
|
|
8
|
-
import { constructNextUrl } from "./routing/util";
|
|
8
|
+
import { constructNextUrl, normalizeLocationHeader } from "./routing/util";
|
|
9
9
|
export const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
|
|
10
10
|
export const MIDDLEWARE_HEADER_PREFIX_LEN = MIDDLEWARE_HEADER_PREFIX.length;
|
|
11
11
|
export const INTERNAL_HEADER_PREFIX = "x-opennext-";
|
|
12
12
|
export const INTERNAL_HEADER_INITIAL_URL = `${INTERNAL_HEADER_PREFIX}initial-url`;
|
|
13
13
|
export const INTERNAL_HEADER_LOCALE = `${INTERNAL_HEADER_PREFIX}locale`;
|
|
14
14
|
export const INTERNAL_HEADER_RESOLVED_ROUTES = `${INTERNAL_HEADER_PREFIX}resolved-routes`;
|
|
15
|
+
export const INTERNAL_HEADER_REWRITE_STATUS_CODE = `${INTERNAL_HEADER_PREFIX}rewrite-status-code`;
|
|
15
16
|
export const INTERNAL_EVENT_REQUEST_ID = `${INTERNAL_HEADER_PREFIX}request-id`;
|
|
16
17
|
// Geolocation headers starting from Nextjs 15
|
|
17
18
|
// See https://github.com/vercel/vercel/blob/7714b1c/packages/functions/src/headers.ts
|
|
@@ -66,8 +67,7 @@ export default async function routingHandler(event, { assetResolver }) {
|
|
|
66
67
|
const redirect = handleRedirects(eventOrResult, RoutesManifest.redirects);
|
|
67
68
|
if (redirect) {
|
|
68
69
|
// We need to encode the value in the Location header to make sure it is valid according to RFC
|
|
69
|
-
|
|
70
|
-
redirect.headers.Location = new URL(redirect.headers.Location).href;
|
|
70
|
+
redirect.headers.Location = normalizeLocationHeader(redirect.headers.Location, event.url, true);
|
|
71
71
|
debug("redirect", redirect);
|
|
72
72
|
return redirect;
|
|
73
73
|
}
|
|
@@ -171,6 +171,7 @@ export default async function routingHandler(event, { assetResolver }) {
|
|
|
171
171
|
locale: NextConfig.i18n
|
|
172
172
|
? detectLocale(eventOrResult, NextConfig.i18n)
|
|
173
173
|
: undefined,
|
|
174
|
+
rewriteStatusCode: middlewareEventOrResult.rewriteStatusCode,
|
|
174
175
|
};
|
|
175
176
|
}
|
|
176
177
|
catch (e) {
|
|
@@ -34,7 +34,7 @@ export declare class OpenNextNodeResponse extends Transform implements ServerRes
|
|
|
34
34
|
socket: Socket | null;
|
|
35
35
|
setTimeout(_msecs: number, _callback?: (() => void) | undefined): this;
|
|
36
36
|
addTrailers(_headers: OutgoingHttpHeaders | readonly [string, string][]): void;
|
|
37
|
-
constructor(fixHeaders: (headers: OutgoingHttpHeaders) => void, onEnd: (headers: OutgoingHttpHeaders) => Promise<void>, streamCreator?: StreamCreator | undefined, initialHeaders?: OutgoingHttpHeaders | undefined);
|
|
37
|
+
constructor(fixHeaders: (headers: OutgoingHttpHeaders) => void, onEnd: (headers: OutgoingHttpHeaders) => Promise<void>, streamCreator?: StreamCreator | undefined, initialHeaders?: OutgoingHttpHeaders | undefined, statusCode?: number);
|
|
38
38
|
get originalResponse(): this;
|
|
39
39
|
get finished(): boolean;
|
|
40
40
|
setHeader(name: string, value: string | string[]): this;
|
|
@@ -51,12 +51,15 @@ export class OpenNextNodeResponse extends Transform {
|
|
|
51
51
|
addTrailers(_headers) {
|
|
52
52
|
throw new Error(CANNOT_BE_USED);
|
|
53
53
|
}
|
|
54
|
-
constructor(fixHeaders, onEnd, streamCreator, initialHeaders) {
|
|
54
|
+
constructor(fixHeaders, onEnd, streamCreator, initialHeaders, statusCode) {
|
|
55
55
|
super();
|
|
56
56
|
this.fixHeaders = fixHeaders;
|
|
57
57
|
this.onEnd = onEnd;
|
|
58
58
|
this.streamCreator = streamCreator;
|
|
59
59
|
this.initialHeaders = initialHeaders;
|
|
60
|
+
if (statusCode !== undefined) {
|
|
61
|
+
this.statusCode = statusCode;
|
|
62
|
+
}
|
|
60
63
|
}
|
|
61
64
|
// Necessary for next 12
|
|
62
65
|
// We might have to implement all the methods here
|
|
@@ -20,6 +20,12 @@ const wrapper = async (handler, converter) => {
|
|
|
20
20
|
await imageHandler(internalEvent, { streamCreator });
|
|
21
21
|
});
|
|
22
22
|
app.all("*paths", async (req, res) => {
|
|
23
|
+
if (req.protocol === "http" && req.hostname === "localhost") {
|
|
24
|
+
// This is used internally by Next.js during redirects in server actions. We need to set it to the origin of the request.
|
|
25
|
+
process.env.__NEXT_PRIVATE_ORIGIN = `${req.protocol}://${req.hostname}`;
|
|
26
|
+
// This is to make `next-auth` and other libraries that rely on this header to work locally out of the box.
|
|
27
|
+
req.headers["x-forwarded-proto"] = req.protocol;
|
|
28
|
+
}
|
|
23
29
|
const internalEvent = await converter.convertFrom(req);
|
|
24
30
|
const streamCreator = {
|
|
25
31
|
writeHeaders: (prelude) => {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { OnLoadOptions, Plugin, PluginBuild } from "esbuild";
|
|
7
7
|
import type { BuildOptions } from "../build/helper";
|
|
8
|
-
import { type
|
|
8
|
+
import { type Versions } from "../build/patch/codePatcher.js";
|
|
9
9
|
export type * from "esbuild";
|
|
10
10
|
/**
|
|
11
11
|
* The callbacks returns either an updated content or undefined if the content is unchanged.
|
|
@@ -23,6 +23,7 @@ export type OnUpdateOptions = OnLoadOptions & {
|
|
|
23
23
|
};
|
|
24
24
|
export type Updater = OnUpdateOptions & {
|
|
25
25
|
callback: Callback;
|
|
26
|
+
versions?: Versions;
|
|
26
27
|
};
|
|
27
28
|
export declare class ContentUpdater {
|
|
28
29
|
private buildOptions;
|
|
@@ -34,10 +35,10 @@ export declare class ContentUpdater {
|
|
|
34
35
|
* The callbacks are called in order of registration.
|
|
35
36
|
*
|
|
36
37
|
* @param name The name of the plugin (must be unique).
|
|
37
|
-
* @param
|
|
38
|
+
* @param updaters A list of code updaters
|
|
38
39
|
* @returns A noop ESBuild plugin.
|
|
39
40
|
*/
|
|
40
|
-
updateContent(name: string,
|
|
41
|
+
updateContent(name: string, updaters: Updater[]): Plugin;
|
|
41
42
|
/**
|
|
42
43
|
* Returns an ESBuild plugin applying the registered updates.
|
|
43
44
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The updater allows multiple plugins to update the content.
|
|
5
5
|
*/
|
|
6
6
|
import { readFile } from "node:fs/promises";
|
|
7
|
-
import {
|
|
7
|
+
import { isVersionInRange } from "../build/patch/codePatcher.js";
|
|
8
8
|
export class ContentUpdater {
|
|
9
9
|
buildOptions;
|
|
10
10
|
updaters = new Map();
|
|
@@ -17,15 +17,14 @@ export class ContentUpdater {
|
|
|
17
17
|
* The callbacks are called in order of registration.
|
|
18
18
|
*
|
|
19
19
|
* @param name The name of the plugin (must be unique).
|
|
20
|
-
* @param
|
|
20
|
+
* @param updaters A list of code updaters
|
|
21
21
|
* @returns A noop ESBuild plugin.
|
|
22
22
|
*/
|
|
23
|
-
updateContent(name,
|
|
23
|
+
updateContent(name, updaters) {
|
|
24
24
|
if (this.updaters.has(name)) {
|
|
25
25
|
throw new Error(`Plugin "${name}" already registered`);
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
this.updaters.set(name, updaters);
|
|
27
|
+
this.updaters.set(name, updaters.filter(({ versions }) => isVersionInRange(this.buildOptions.nextVersion, versions)));
|
|
29
28
|
return {
|
|
30
29
|
name,
|
|
31
30
|
setup() { },
|
|
@@ -20,6 +20,7 @@ export type InternalResult = {
|
|
|
20
20
|
headers: Record<string, string | string[]>;
|
|
21
21
|
body: ReadableStream;
|
|
22
22
|
isBase64Encoded: boolean;
|
|
23
|
+
rewriteStatusCode?: number;
|
|
23
24
|
} & BaseEventOrResult<"core">;
|
|
24
25
|
export interface StreamCreator {
|
|
25
26
|
writeHeaders(prelude: {
|
|
@@ -92,6 +93,7 @@ export interface RoutingResult {
|
|
|
92
93
|
initialURL: string;
|
|
93
94
|
locale?: string;
|
|
94
95
|
resolvedRoutes: ResolvedRoute[];
|
|
96
|
+
rewriteStatusCode?: number;
|
|
95
97
|
}
|
|
96
98
|
export interface MiddlewareResult extends RoutingResult, BaseEventOrResult<"middleware"> {
|
|
97
99
|
}
|
|
@@ -63,6 +63,11 @@ export type CachedFetchValue = {
|
|
|
63
63
|
export type WithLastModified<T> = {
|
|
64
64
|
lastModified?: number;
|
|
65
65
|
value?: T;
|
|
66
|
+
/**
|
|
67
|
+
* If set to true, we will not check the tag cache for this entry.
|
|
68
|
+
* `revalidateTag` and `revalidatePath` may not work as expected.
|
|
69
|
+
*/
|
|
70
|
+
shouldBypassTagCache?: boolean;
|
|
66
71
|
};
|
|
67
72
|
export type CacheEntryType = Extension;
|
|
68
73
|
export type CacheValue<CacheType extends CacheEntryType> = (CacheType extends "fetch" ? CachedFetchValue : CacheType extends "cache" ? CachedFile : StoredComposableCacheEntry) & {
|