@opennextjs/aws 3.5.2 → 3.5.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.
- package/dist/adapters/config/util.d.ts +1 -0
- package/dist/adapters/config/util.js +5 -2
- package/dist/adapters/logger.js +1 -1
- package/dist/build/compileCache.js +1 -1
- package/dist/build/compileConfig.d.ts +6 -2
- package/dist/build/compileConfig.js +8 -7
- package/dist/build/constant.d.ts +1 -0
- package/dist/build/constant.js +1 -0
- package/dist/build/copyTracedFiles.d.ts +21 -1
- package/dist/build/copyTracedFiles.js +28 -5
- package/dist/build/createImageOptimizationBundle.js +1 -1
- package/dist/build/createMiddleware.js +1 -0
- package/dist/build/createServerBundle.d.ts +9 -1
- package/dist/build/createServerBundle.js +39 -15
- package/dist/build/edge/createEdgeBundle.d.ts +9 -2
- package/dist/build/edge/createEdgeBundle.js +28 -17
- package/dist/build/helper.d.ts +3 -2
- package/dist/build/helper.js +45 -20
- package/dist/build/patch/astCodePatcher.d.ts +58 -0
- package/dist/build/patch/astCodePatcher.js +76 -0
- package/dist/build/patch/codePatcher.d.ts +46 -0
- package/dist/build/patch/codePatcher.js +119 -0
- package/dist/build/patch/patchFetchCacheISR.d.ts +5 -0
- package/dist/build/patch/patchFetchCacheISR.js +102 -0
- package/dist/build/patch/patchFetchCacheWaitUntil.d.ts +3 -0
- package/dist/build/patch/patchFetchCacheWaitUntil.js +36 -0
- package/dist/build.js +1 -1
- package/dist/core/requestHandler.js +3 -1
- package/dist/core/routing/cacheInterceptor.js +6 -1
- package/dist/core/routing/middleware.js +12 -2
- package/dist/core/routing/queue.d.ts +1 -0
- package/dist/core/routing/queue.js +12 -8
- package/dist/core/routing/util.js +3 -3
- package/dist/logger.d.ts +8 -0
- package/dist/logger.js +2 -0
- package/dist/overrides/wrappers/express-dev.js +2 -0
- package/dist/overrides/wrappers/node.js +0 -1
- package/dist/plugins/content-updater.d.ts +48 -0
- package/dist/plugins/content-updater.js +65 -0
- package/dist/plugins/edge.d.ts +3 -3
- package/dist/plugins/edge.js +18 -8
- package/dist/types/next-types.d.ts +3 -0
- package/dist/types/overrides.d.ts +2 -0
- package/package.json +4 -2
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type Edit, Lang, type NapiConfig, type SgNode } from "@ast-grep/napi";
|
|
2
|
+
import type { PatchCodeFn } from "./codePatcher";
|
|
3
|
+
export type * from "@ast-grep/napi";
|
|
4
|
+
/**
|
|
5
|
+
* fix has the same meaning as in yaml rules
|
|
6
|
+
* see https://ast-grep.github.io/guide/rewrite-code.html#using-fix-in-yaml-rule
|
|
7
|
+
*/
|
|
8
|
+
export type RuleConfig = NapiConfig & {
|
|
9
|
+
fix?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Returns the `Edit`s and `Match`es for an ast-grep rule in yaml format
|
|
13
|
+
*
|
|
14
|
+
* The rule must have a `fix` to rewrite the matched node.
|
|
15
|
+
*
|
|
16
|
+
* Tip: use https://ast-grep.github.io/playground.html to create rules.
|
|
17
|
+
*
|
|
18
|
+
* @param rule The rule. Either a yaml string or an instance of `RuleConfig`
|
|
19
|
+
* @param root The root node
|
|
20
|
+
* @param once only apply once
|
|
21
|
+
* @returns A list of edits and a list of matches.
|
|
22
|
+
*/
|
|
23
|
+
export declare function applyRule(rule: string | RuleConfig, root: SgNode, { once }?: {
|
|
24
|
+
once?: boolean | undefined;
|
|
25
|
+
}): {
|
|
26
|
+
edits: Edit[];
|
|
27
|
+
matches: SgNode[];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Parse a file and obtain its root.
|
|
31
|
+
*
|
|
32
|
+
* @param path The file path
|
|
33
|
+
* @param lang The language to parse. Defaults to TypeScript.
|
|
34
|
+
* @returns The root for the file.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseFile(path: string, lang?: Lang): SgNode;
|
|
37
|
+
/**
|
|
38
|
+
* Patches the code from by applying the rule.
|
|
39
|
+
*
|
|
40
|
+
* This function is mainly for on off edits and tests,
|
|
41
|
+
* use `getRuleEdits` to apply multiple rules.
|
|
42
|
+
*
|
|
43
|
+
* @param code The source code
|
|
44
|
+
* @param rule The astgrep rule (yaml or NapiConfig)
|
|
45
|
+
* @param lang The language used by the source code
|
|
46
|
+
* @param lang Whether to apply the rule only once
|
|
47
|
+
* @returns The patched code
|
|
48
|
+
*/
|
|
49
|
+
export declare function patchCode(code: string, rule: string | RuleConfig, { lang, once }?: {
|
|
50
|
+
lang?: Lang | undefined;
|
|
51
|
+
once?: boolean | undefined;
|
|
52
|
+
}): string;
|
|
53
|
+
/**
|
|
54
|
+
* @param rule
|
|
55
|
+
* @param lang
|
|
56
|
+
* @returns a callback applying the the rule.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createPatchCode(rule: string | RuleConfig, lang?: Lang): PatchCodeFn;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Mostly copied from the cloudflare adapter
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { Lang, parse, } from "@ast-grep/napi";
|
|
4
|
+
import yaml from "yaml";
|
|
5
|
+
/**
|
|
6
|
+
* Returns the `Edit`s and `Match`es for an ast-grep rule in yaml format
|
|
7
|
+
*
|
|
8
|
+
* The rule must have a `fix` to rewrite the matched node.
|
|
9
|
+
*
|
|
10
|
+
* Tip: use https://ast-grep.github.io/playground.html to create rules.
|
|
11
|
+
*
|
|
12
|
+
* @param rule The rule. Either a yaml string or an instance of `RuleConfig`
|
|
13
|
+
* @param root The root node
|
|
14
|
+
* @param once only apply once
|
|
15
|
+
* @returns A list of edits and a list of matches.
|
|
16
|
+
*/
|
|
17
|
+
export function applyRule(rule, root, { once = false } = {}) {
|
|
18
|
+
const ruleConfig = typeof rule === "string" ? yaml.parse(rule) : rule;
|
|
19
|
+
if (ruleConfig.transform) {
|
|
20
|
+
throw new Error("transform is not supported");
|
|
21
|
+
}
|
|
22
|
+
if (!ruleConfig.fix) {
|
|
23
|
+
throw new Error("no fix to apply");
|
|
24
|
+
}
|
|
25
|
+
const fix = ruleConfig.fix;
|
|
26
|
+
const matches = once
|
|
27
|
+
? [root.find(ruleConfig)].filter((m) => m !== null)
|
|
28
|
+
: root.findAll(ruleConfig);
|
|
29
|
+
const edits = [];
|
|
30
|
+
matches.forEach((match) => {
|
|
31
|
+
edits.push(match.replace(
|
|
32
|
+
// Replace known placeholders by their value
|
|
33
|
+
fix
|
|
34
|
+
.replace(/\$\$\$([A-Z0-9_]+)/g, (_m, name) => match
|
|
35
|
+
.getMultipleMatches(name)
|
|
36
|
+
.map((n) => n.text())
|
|
37
|
+
.join(""))
|
|
38
|
+
.replace(/\$([A-Z0-9_]+)/g, (m, name) => match.getMatch(name)?.text() ?? m)));
|
|
39
|
+
});
|
|
40
|
+
return { edits, matches };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse a file and obtain its root.
|
|
44
|
+
*
|
|
45
|
+
* @param path The file path
|
|
46
|
+
* @param lang The language to parse. Defaults to TypeScript.
|
|
47
|
+
* @returns The root for the file.
|
|
48
|
+
*/
|
|
49
|
+
export function parseFile(path, lang = Lang.TypeScript) {
|
|
50
|
+
return parse(lang, readFileSync(path, { encoding: "utf-8" })).root();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Patches the code from by applying the rule.
|
|
54
|
+
*
|
|
55
|
+
* This function is mainly for on off edits and tests,
|
|
56
|
+
* use `getRuleEdits` to apply multiple rules.
|
|
57
|
+
*
|
|
58
|
+
* @param code The source code
|
|
59
|
+
* @param rule The astgrep rule (yaml or NapiConfig)
|
|
60
|
+
* @param lang The language used by the source code
|
|
61
|
+
* @param lang Whether to apply the rule only once
|
|
62
|
+
* @returns The patched code
|
|
63
|
+
*/
|
|
64
|
+
export function patchCode(code, rule, { lang = Lang.TypeScript, once = false } = {}) {
|
|
65
|
+
const node = parse(lang, code).root();
|
|
66
|
+
const { edits } = applyRule(rule, node, { once });
|
|
67
|
+
return node.commitEdits(edits);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* @param rule
|
|
71
|
+
* @param lang
|
|
72
|
+
* @returns a callback applying the the rule.
|
|
73
|
+
*/
|
|
74
|
+
export function createPatchCode(rule, lang = Lang.TypeScript) {
|
|
75
|
+
return async ({ code }) => patchCode(code, rule, { lang });
|
|
76
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { getManifests } from "../copyTracedFiles.js";
|
|
2
|
+
import * as buildHelper from "../helper.js";
|
|
3
|
+
type Versions = `>=${number}.${number}.${number} <=${number}.${number}.${number}` | `>=${number}.${number}.${number}` | `<=${number}.${number}.${number}`;
|
|
4
|
+
export interface VersionedField<T> {
|
|
5
|
+
/**
|
|
6
|
+
* The versions of Next.js that this field should be used for
|
|
7
|
+
* Should be in the format `">=16.0.0 <=17.0.0"` or `">=16.0.0"` or `"<=17.0.0"`
|
|
8
|
+
* **Be careful with spaces**
|
|
9
|
+
*/
|
|
10
|
+
versions?: Versions;
|
|
11
|
+
field: T;
|
|
12
|
+
}
|
|
13
|
+
export type PatchCodeFn = (args: {
|
|
14
|
+
/**
|
|
15
|
+
* The code of the file that needs to be patched
|
|
16
|
+
*/
|
|
17
|
+
code: string;
|
|
18
|
+
/**
|
|
19
|
+
* The final path of the file that needs to be patched
|
|
20
|
+
*/
|
|
21
|
+
filePath: string;
|
|
22
|
+
/**
|
|
23
|
+
* All files that are traced and will be included in the bundle
|
|
24
|
+
*/
|
|
25
|
+
tracedFiles: string[];
|
|
26
|
+
/**
|
|
27
|
+
* Next.js manifests that are used by Next at runtime
|
|
28
|
+
*/
|
|
29
|
+
manifests: ReturnType<typeof getManifests>;
|
|
30
|
+
}) => Promise<string>;
|
|
31
|
+
interface IndividualPatch {
|
|
32
|
+
pathFilter: RegExp;
|
|
33
|
+
contentFilter?: RegExp;
|
|
34
|
+
patchCode: PatchCodeFn;
|
|
35
|
+
}
|
|
36
|
+
export interface CodePatcher {
|
|
37
|
+
name: string;
|
|
38
|
+
patches: IndividualPatch | VersionedField<IndividualPatch>[];
|
|
39
|
+
}
|
|
40
|
+
export declare function parseVersions(versions?: Versions): {
|
|
41
|
+
before?: string;
|
|
42
|
+
after?: string;
|
|
43
|
+
};
|
|
44
|
+
export declare function extractVersionedField<T>(fields: VersionedField<T>[], version: string): T[];
|
|
45
|
+
export declare function applyCodePatches(buildOptions: buildHelper.BuildOptions, tracedFiles: string[], manifests: ReturnType<typeof getManifests>, codePatcher: CodePatcher[]): Promise<void>;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import logger from "../../logger.js";
|
|
3
|
+
import * as buildHelper from "../helper.js";
|
|
4
|
+
export function parseVersions(versions) {
|
|
5
|
+
if (!versions) {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
// We need to use regex to extract the versions
|
|
9
|
+
const versionRegex = /([<>]=)(\d+\.\d+\.\d+)/g;
|
|
10
|
+
const matches = Array.from(versions.matchAll(versionRegex));
|
|
11
|
+
if (matches.length === 0) {
|
|
12
|
+
throw new Error("Invalid version range, no matches found");
|
|
13
|
+
}
|
|
14
|
+
if (matches.length > 2) {
|
|
15
|
+
throw new Error("Invalid version range, too many matches found");
|
|
16
|
+
}
|
|
17
|
+
let after;
|
|
18
|
+
let before;
|
|
19
|
+
for (const match of matches) {
|
|
20
|
+
const [_, operator, version] = match;
|
|
21
|
+
if (operator === "<=") {
|
|
22
|
+
before = version;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
after = version;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Before returning we reconstruct the version string and compare it to the original
|
|
29
|
+
// If they don't match we throw an error
|
|
30
|
+
// We have to do this because template literal types here seems to allow for extra spaces
|
|
31
|
+
// that could easily break the version comparison and allow some patch to be applied on incorrect versions
|
|
32
|
+
// This might even go unnoticed
|
|
33
|
+
const reconstructedVersion = `${after ? `>=${after}` : ""}${before ? `${after ? " " : ""}<=${before}` : ""}`;
|
|
34
|
+
if (reconstructedVersion !== versions) {
|
|
35
|
+
throw new Error("Invalid version range, the reconstructed version does not match the original");
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
before,
|
|
39
|
+
after,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function extractVersionedField(fields, version) {
|
|
43
|
+
const result = [];
|
|
44
|
+
for (const field of fields) {
|
|
45
|
+
// No versions specified, the patch always apply
|
|
46
|
+
if (!field.versions) {
|
|
47
|
+
result.push(field.field);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const { before, after } = parseVersions(field.versions);
|
|
51
|
+
// range
|
|
52
|
+
if (before && after) {
|
|
53
|
+
if (buildHelper.compareSemver(version, "<=", before) &&
|
|
54
|
+
buildHelper.compareSemver(version, ">=", after)) {
|
|
55
|
+
result.push(field.field);
|
|
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
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
export async function applyCodePatches(buildOptions, tracedFiles, manifests, codePatcher) {
|
|
76
|
+
const nextVersion = buildOptions.nextVersion;
|
|
77
|
+
logger.time("Applying code patches");
|
|
78
|
+
// We first filter against the version
|
|
79
|
+
// 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 }) => Array.isArray(patches)
|
|
81
|
+
? extractVersionedField(patches, nextVersion).map((patch) => ({
|
|
82
|
+
name,
|
|
83
|
+
patch,
|
|
84
|
+
}))
|
|
85
|
+
: [{ name, patch: patches }]);
|
|
86
|
+
await Promise.all(tracedFiles.map(async (filePath) => {
|
|
87
|
+
// We check the filename against the filter to see if we should apply the patch
|
|
88
|
+
const patchMatchingPath = patchesToApply.filter(({ patch }) => {
|
|
89
|
+
return filePath.match(patch.pathFilter);
|
|
90
|
+
});
|
|
91
|
+
if (patchMatchingPath.length === 0) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
95
|
+
// We filter a last time against the content this time
|
|
96
|
+
const patchToApply = patchMatchingPath.filter(({ patch }) => {
|
|
97
|
+
if (!patch.contentFilter) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
return content.match(patch.contentFilter);
|
|
101
|
+
});
|
|
102
|
+
if (patchToApply.length === 0) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// We apply the patches
|
|
106
|
+
let patchedContent = content;
|
|
107
|
+
for (const { patch, name } of patchToApply) {
|
|
108
|
+
logger.debug(`Applying code patch: ${name} to ${filePath}`);
|
|
109
|
+
patchedContent = await patch.patchCode({
|
|
110
|
+
code: patchedContent,
|
|
111
|
+
filePath,
|
|
112
|
+
tracedFiles,
|
|
113
|
+
manifests,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
await fs.writeFile(filePath, patchedContent);
|
|
117
|
+
}));
|
|
118
|
+
logger.timeEnd("Applying code patches");
|
|
119
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CodePatcher } from "./codePatcher";
|
|
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 patchFetchCacheForISR: CodePatcher;
|
|
5
|
+
export declare const patchUnstableCacheForISR: CodePatcher;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Lang } from "@ast-grep/napi";
|
|
2
|
+
import { getCrossPlatformPathRegex } from "../../utils/regex.js";
|
|
3
|
+
import { createPatchCode } from "./astCodePatcher.js";
|
|
4
|
+
export const fetchRule = `
|
|
5
|
+
rule:
|
|
6
|
+
kind: member_expression
|
|
7
|
+
pattern: $WORK_STORE.isOnDemandRevalidate
|
|
8
|
+
inside:
|
|
9
|
+
kind: ternary_expression
|
|
10
|
+
all:
|
|
11
|
+
- has: {kind: 'null'}
|
|
12
|
+
- has:
|
|
13
|
+
kind: await_expression
|
|
14
|
+
has:
|
|
15
|
+
kind: call_expression
|
|
16
|
+
all:
|
|
17
|
+
- has:
|
|
18
|
+
kind: member_expression
|
|
19
|
+
has:
|
|
20
|
+
kind: property_identifier
|
|
21
|
+
field: property
|
|
22
|
+
regex: get
|
|
23
|
+
- has:
|
|
24
|
+
kind: arguments
|
|
25
|
+
has:
|
|
26
|
+
kind: object
|
|
27
|
+
has:
|
|
28
|
+
kind: pair
|
|
29
|
+
all:
|
|
30
|
+
- has:
|
|
31
|
+
kind: property_identifier
|
|
32
|
+
field: key
|
|
33
|
+
regex: softTags
|
|
34
|
+
inside:
|
|
35
|
+
kind: variable_declarator
|
|
36
|
+
|
|
37
|
+
fix:
|
|
38
|
+
($WORK_STORE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)
|
|
39
|
+
`;
|
|
40
|
+
export const unstable_cacheRule = `
|
|
41
|
+
rule:
|
|
42
|
+
kind: member_expression
|
|
43
|
+
pattern: $STORE_OR_CACHE.isOnDemandRevalidate
|
|
44
|
+
inside:
|
|
45
|
+
kind: if_statement
|
|
46
|
+
stopBy: end
|
|
47
|
+
has:
|
|
48
|
+
kind: statement_block
|
|
49
|
+
has:
|
|
50
|
+
kind: variable_declarator
|
|
51
|
+
has:
|
|
52
|
+
kind: await_expression
|
|
53
|
+
has:
|
|
54
|
+
kind: call_expression
|
|
55
|
+
all:
|
|
56
|
+
- has:
|
|
57
|
+
kind: member_expression
|
|
58
|
+
has:
|
|
59
|
+
kind: property_identifier
|
|
60
|
+
field: property
|
|
61
|
+
regex: get
|
|
62
|
+
- has:
|
|
63
|
+
kind: arguments
|
|
64
|
+
has:
|
|
65
|
+
kind: object
|
|
66
|
+
has:
|
|
67
|
+
kind: pair
|
|
68
|
+
all:
|
|
69
|
+
- has:
|
|
70
|
+
kind: property_identifier
|
|
71
|
+
field: key
|
|
72
|
+
regex: softTags
|
|
73
|
+
stopBy: end
|
|
74
|
+
fix:
|
|
75
|
+
($STORE_OR_CACHE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)
|
|
76
|
+
`;
|
|
77
|
+
export const patchFetchCacheForISR = {
|
|
78
|
+
name: "patch-fetch-cache-for-isr",
|
|
79
|
+
patches: [
|
|
80
|
+
{
|
|
81
|
+
versions: ">=14.0.0",
|
|
82
|
+
field: {
|
|
83
|
+
pathFilter: getCrossPlatformPathRegex(String.raw `(server/chunks/.*\.js|.*\.runtime\..*\.js|patch-fetch\.js)$`, { escape: false }),
|
|
84
|
+
contentFilter: /\.isOnDemandRevalidate/,
|
|
85
|
+
patchCode: createPatchCode(fetchRule, Lang.JavaScript),
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
export const patchUnstableCacheForISR = {
|
|
91
|
+
name: "patch-unstable-cache-for-isr",
|
|
92
|
+
patches: [
|
|
93
|
+
{
|
|
94
|
+
versions: ">=14.2.0",
|
|
95
|
+
field: {
|
|
96
|
+
pathFilter: getCrossPlatformPathRegex(String.raw `(server/chunks/.*\.js|.*\.runtime\..*\.js|spec-extension/unstable-cache\.js)$`, { escape: false }),
|
|
97
|
+
contentFilter: /\.isOnDemandRevalidate/,
|
|
98
|
+
patchCode: createPatchCode(unstable_cacheRule, Lang.JavaScript),
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { CodePatcher } from "./codePatcher";
|
|
2
|
+
export declare const rule = "\nrule:\n kind: call_expression\n pattern: $PROMISE\n all:\n - has: { pattern: $_.arrayBuffer().then, stopBy: end }\n - has: { pattern: \"Buffer.from\", stopBy: end }\n - any:\n - inside:\n kind: sequence_expression\n inside:\n kind: return_statement\n - inside:\n kind: expression_statement\n precedes:\n kind: return_statement\n - has: { pattern: $_.FETCH, stopBy: end }\n\nfix: |\n globalThis.__openNextAls?.getStore()?.pendingPromiseRunner.add($PROMISE)\n";
|
|
3
|
+
export declare const patchFetchCacheSetMissingWaitUntil: CodePatcher;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getCrossPlatformPathRegex } from "../../utils/regex.js";
|
|
2
|
+
import { createPatchCode } from "./astCodePatcher.js";
|
|
3
|
+
export const rule = `
|
|
4
|
+
rule:
|
|
5
|
+
kind: call_expression
|
|
6
|
+
pattern: $PROMISE
|
|
7
|
+
all:
|
|
8
|
+
- has: { pattern: $_.arrayBuffer().then, stopBy: end }
|
|
9
|
+
- has: { pattern: "Buffer.from", stopBy: end }
|
|
10
|
+
- any:
|
|
11
|
+
- inside:
|
|
12
|
+
kind: sequence_expression
|
|
13
|
+
inside:
|
|
14
|
+
kind: return_statement
|
|
15
|
+
- inside:
|
|
16
|
+
kind: expression_statement
|
|
17
|
+
precedes:
|
|
18
|
+
kind: return_statement
|
|
19
|
+
- has: { pattern: $_.FETCH, stopBy: end }
|
|
20
|
+
|
|
21
|
+
fix: |
|
|
22
|
+
globalThis.__openNextAls?.getStore()?.pendingPromiseRunner.add($PROMISE)
|
|
23
|
+
`;
|
|
24
|
+
export const patchFetchCacheSetMissingWaitUntil = {
|
|
25
|
+
name: "patch-fetch-cache-set-missing-wait-until",
|
|
26
|
+
patches: [
|
|
27
|
+
{
|
|
28
|
+
versions: ">=15.0.0",
|
|
29
|
+
field: {
|
|
30
|
+
pathFilter: getCrossPlatformPathRegex(String.raw `(server/chunks/.*\.js|.*\.runtime\..*\.js|patch-fetch\.js)$`, { escape: false }),
|
|
31
|
+
contentFilter: /arrayBuffer\(\)\s*\.then/,
|
|
32
|
+
patchCode: createPatchCode(rule),
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
package/dist/build.js
CHANGED
|
@@ -17,7 +17,7 @@ export async function build(openNextConfigPath, nodeExternals) {
|
|
|
17
17
|
showWarningOnWindows();
|
|
18
18
|
const baseDir = process.cwd();
|
|
19
19
|
const openNextDistDir = url.fileURLToPath(new URL(".", import.meta.url));
|
|
20
|
-
const { config, buildDir } = await compileOpenNextConfig(baseDir, openNextConfigPath, nodeExternals);
|
|
20
|
+
const { config, buildDir } = await compileOpenNextConfig(baseDir, openNextConfigPath, { nodeExternals });
|
|
21
21
|
// Initialize options
|
|
22
22
|
const options = buildHelper.normalizeOptions(config, openNextDistDir, buildDir);
|
|
23
23
|
logger.setLevel(options.debug ? "debug" : "info");
|
|
@@ -9,7 +9,9 @@ import routingHandler, { INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_R
|
|
|
9
9
|
import { requestHandler, setNextjsPrebundledReact } from "./util";
|
|
10
10
|
// This is used to identify requests in the cache
|
|
11
11
|
globalThis.__openNextAls = new AsyncLocalStorage();
|
|
12
|
+
//#override patchAsyncStorage
|
|
12
13
|
patchAsyncStorage();
|
|
14
|
+
//#endOverride
|
|
13
15
|
export async function openNextHandler(internalEvent, options) {
|
|
14
16
|
const initialHeaders = internalEvent.headers;
|
|
15
17
|
// We run everything in the async local storage context so that it is available in the middleware as well as in NextServer
|
|
@@ -94,7 +96,7 @@ export async function openNextHandler(internalEvent, options) {
|
|
|
94
96
|
resolvedRoutes: [],
|
|
95
97
|
origin: false,
|
|
96
98
|
initialURL: internalEvent.url,
|
|
97
|
-
}, headers, options.streamCreator);
|
|
99
|
+
}, routingResult.headers, options.streamCreator);
|
|
98
100
|
response.statusCode = routingResult.statusCode;
|
|
99
101
|
response.flushHeaders();
|
|
100
102
|
const [bodyToConsume, bodyToReturn] = routingResult.body.tee();
|
|
@@ -48,7 +48,12 @@ async function computeCacheControl(path, body, host, revalidate, lastModified) {
|
|
|
48
48
|
url = `${NextConfig.basePath}${url}`;
|
|
49
49
|
}
|
|
50
50
|
await globalThis.queue.send({
|
|
51
|
-
MessageBody: {
|
|
51
|
+
MessageBody: {
|
|
52
|
+
host,
|
|
53
|
+
url,
|
|
54
|
+
eTag: etag,
|
|
55
|
+
lastModified: lastModified ?? Date.now(),
|
|
56
|
+
},
|
|
52
57
|
MessageDeduplicationId: hash(`${path}-${lastModified}-${etag}`),
|
|
53
58
|
MessageGroupId: generateMessageGroupId(path),
|
|
54
59
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FunctionsConfigManifest, MiddlewareManifest, NextConfig, } from "../../adapters/config/index.js";
|
|
1
|
+
import { FunctionsConfigManifest, MiddlewareManifest, NextConfig, PrerenderManifest, } from "../../adapters/config/index.js";
|
|
2
2
|
import { emptyReadableStream } from "../../utils/stream.js";
|
|
3
3
|
import { localizePath } from "./i18n/index.js";
|
|
4
4
|
//NOTE: we should try to avoid importing stuff from next as much as possible
|
|
@@ -24,7 +24,11 @@ function defaultMiddlewareLoader() {
|
|
|
24
24
|
export async function handleMiddleware(internalEvent, middlewareLoader = defaultMiddlewareLoader) {
|
|
25
25
|
const headers = internalEvent.headers;
|
|
26
26
|
// We bypass the middleware if the request is internal
|
|
27
|
-
if
|
|
27
|
+
// We should only do that if the request has the correct `x-prerender-revalidate` header
|
|
28
|
+
// The `x-prerender-revalidate` header is set at build time and should be safe to trust
|
|
29
|
+
if (headers["x-isr"] &&
|
|
30
|
+
headers["x-prerender-revalidate"] ===
|
|
31
|
+
PrerenderManifest.preview.previewModeId)
|
|
28
32
|
return internalEvent;
|
|
29
33
|
// We only need the normalizedPath to check if the middleware should run
|
|
30
34
|
const normalizedPath = localizePath(internalEvent);
|
|
@@ -73,6 +77,12 @@ export async function handleMiddleware(internalEvent, middlewareLoader = default
|
|
|
73
77
|
const reqHeaders = {};
|
|
74
78
|
const resHeaders = {};
|
|
75
79
|
responseHeaders.delete("x-middleware-override-headers");
|
|
80
|
+
/* Next will set the header `x-middleware-set-cookie` when you `set-cookie` in the middleware.
|
|
81
|
+
* We can delete it here since it will be set in `set-cookie` aswell. Next removes this header in the response themselves.
|
|
82
|
+
* `x-middleware-next` is set when you invoke `NextResponse.next()`. We can delete it here aswell.
|
|
83
|
+
*/
|
|
84
|
+
responseHeaders.delete("x-middleware-set-cookie");
|
|
85
|
+
responseHeaders.delete("x-middleware-next");
|
|
76
86
|
const xMiddlewareKey = "x-middleware-request-";
|
|
77
87
|
responseHeaders.forEach((value, key) => {
|
|
78
88
|
if (key.startsWith(xMiddlewareKey)) {
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
export function generateShardId(rawPath, maxConcurrency, prefix) {
|
|
2
|
+
let a = cyrb128(rawPath);
|
|
3
|
+
// We use mulberry32 to generate a random int between 0 and MAX_REVALIDATE_CONCURRENCY
|
|
4
|
+
let t = (a += 0x6d2b79f5);
|
|
5
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
6
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
7
|
+
const randomFloat = ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
8
|
+
// This will generate a random int between 0 and maxConcurrency
|
|
9
|
+
const randomInt = Math.floor(randomFloat * maxConcurrency);
|
|
10
|
+
return `${prefix}-${randomInt}`;
|
|
11
|
+
}
|
|
1
12
|
// Since we're using a FIFO queue, every messageGroupId is treated sequentially
|
|
2
13
|
// This could cause a backlog of messages in the queue if there is too much page to
|
|
3
14
|
// revalidate at once. To avoid this, we generate a random messageGroupId for each
|
|
@@ -6,17 +17,10 @@
|
|
|
6
17
|
// will always have the same messageGroupId.
|
|
7
18
|
// https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript#answer-47593316
|
|
8
19
|
export function generateMessageGroupId(rawPath) {
|
|
9
|
-
let a = cyrb128(rawPath);
|
|
10
|
-
// We use mulberry32 to generate a random int between 0 and MAX_REVALIDATE_CONCURRENCY
|
|
11
|
-
let t = (a += 0x6d2b79f5);
|
|
12
|
-
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
13
|
-
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
14
|
-
const randomFloat = ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
15
20
|
// This will generate a random int between 0 and MAX_REVALIDATE_CONCURRENCY
|
|
16
21
|
// This means that we could have 1000 revalidate request at the same time
|
|
17
22
|
const maxConcurrency = Number.parseInt(process.env.MAX_REVALIDATE_CONCURRENCY ?? "10");
|
|
18
|
-
|
|
19
|
-
return `revalidate-${randomInt}`;
|
|
23
|
+
return generateShardId(rawPath, maxConcurrency, "revalidate");
|
|
20
24
|
}
|
|
21
25
|
// Used to generate a hash int from a string
|
|
22
26
|
function cyrb128(str) {
|
|
@@ -268,10 +268,10 @@ export async function revalidateIfRequired(host, rawPath, headers, req) {
|
|
|
268
268
|
const lastModified = globalThis.__openNextAls.getStore()?.lastModified ?? 0;
|
|
269
269
|
// For some weird cases, lastModified is not set, haven't been able to figure out yet why
|
|
270
270
|
// For those cases we add the etag to the deduplication id, it might help
|
|
271
|
-
const
|
|
271
|
+
const eTag = `${headers.etag ?? headers.ETag ?? ""}`;
|
|
272
272
|
await globalThis.queue.send({
|
|
273
|
-
MessageBody: { host, url: revalidateUrl },
|
|
274
|
-
MessageDeduplicationId: hash(`${rawPath}-${lastModified}-${
|
|
273
|
+
MessageBody: { host, url: revalidateUrl, eTag, lastModified },
|
|
274
|
+
MessageDeduplicationId: hash(`${rawPath}-${lastModified}-${eTag}`),
|
|
275
275
|
MessageGroupId: generateMessageGroupId(rawPath),
|
|
276
276
|
});
|
|
277
277
|
}
|
package/dist/logger.d.ts
CHANGED
|
@@ -8,5 +8,13 @@ declare const _default: {
|
|
|
8
8
|
};
|
|
9
9
|
warn: (...args: any[]) => void;
|
|
10
10
|
error: (...args: any[]) => void;
|
|
11
|
+
time: {
|
|
12
|
+
(label?: string): void;
|
|
13
|
+
(label?: string): void;
|
|
14
|
+
};
|
|
15
|
+
timeEnd: {
|
|
16
|
+
(label?: string): void;
|
|
17
|
+
(label?: string): void;
|
|
18
|
+
};
|
|
11
19
|
};
|
|
12
20
|
export default _default;
|
package/dist/logger.js
CHANGED
|
@@ -19,7 +19,9 @@ const wrapper = async (handler, converter) => {
|
|
|
19
19
|
const internalEvent = await converter.convertFrom(req);
|
|
20
20
|
const streamCreator = {
|
|
21
21
|
writeHeaders: (prelude) => {
|
|
22
|
+
res.setHeader("Set-Cookie", prelude.cookies);
|
|
22
23
|
res.writeHead(prelude.statusCode, prelude.headers);
|
|
24
|
+
res.flushHeaders();
|
|
23
25
|
return res;
|
|
24
26
|
},
|
|
25
27
|
onFinish: () => { },
|