@opennextjs/aws 3.9.15 → 3.10.0
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 +3 -1
- package/dist/adapters/cache.js +84 -18
- package/dist/adapters/composable-cache.d.ts +12 -0
- package/dist/adapters/composable-cache.js +82 -2
- package/dist/adapters/dynamo-provider.js +2 -0
- package/dist/adapters/revalidate.js +2 -1
- package/dist/build/compileCache.js +2 -3
- package/dist/build/copyTracedFiles.d.ts +1 -0
- package/dist/build/copyTracedFiles.js +20 -0
- package/dist/build/createAssets.js +4 -1
- package/dist/build/helper.d.ts +7 -12
- package/dist/build/helper.js +19 -57
- package/dist/build/patch/patches/patchNextServer.d.ts +1 -0
- package/dist/build/patch/patches/patchNextServer.js +19 -0
- package/dist/build/patch/patches/patchOriginalNextConfig.js +4 -15
- package/dist/core/routing/cacheInterceptor.js +2 -1
- package/dist/core/routing/i18n/index.js +4 -2
- package/dist/overrides/proxyExternalRequest/fetch.js +10 -1
- package/dist/overrides/tagCache/dummy.js +3 -0
- package/dist/overrides/tagCache/dynamodb-lite.js +94 -8
- package/dist/overrides/tagCache/dynamodb-nextMode.d.ts +2 -1
- package/dist/overrides/tagCache/dynamodb-nextMode.js +112 -27
- package/dist/overrides/tagCache/dynamodb.js +104 -22
- package/dist/overrides/tagCache/fs-dev-nextMode.d.ts +2 -1
- package/dist/overrides/tagCache/fs-dev-nextMode.js +37 -6
- package/dist/overrides/tagCache/fs-dev.js +40 -8
- package/dist/types/cache.d.ts +21 -0
- package/dist/types/global.d.ts +5 -2
- package/dist/types/next-types.d.ts +1 -0
- package/dist/types/overrides.d.ts +42 -1
- package/dist/utils/cache.d.ts +22 -2
- package/dist/utils/cache.js +44 -4
- package/dist/utils/promise.js +2 -0
- package/dist/utils/requestCache.d.ts +20 -0
- package/dist/utils/requestCache.js +27 -0
- package/dist/utils/semver.d.ts +10 -0
- package/dist/utils/semver.js +55 -0
- package/package.json +6 -6
package/dist/adapters/cache.d.ts
CHANGED
|
@@ -11,7 +11,9 @@ export default class Cache {
|
|
|
11
11
|
getFetchCache(key: string, softTags?: string[], tags?: string[]): Promise<CacheHandlerValue | null>;
|
|
12
12
|
getIncrementalCache(key: string): Promise<CacheHandlerValue | null>;
|
|
13
13
|
set(key: string, data?: IncrementalCacheValue, ctx?: IncrementalCacheContext): Promise<void>;
|
|
14
|
-
revalidateTag(tags: string | string[]
|
|
14
|
+
revalidateTag(tags: string | string[], durations?: {
|
|
15
|
+
expire?: number;
|
|
16
|
+
}): Promise<void>;
|
|
15
17
|
private updateTagsOnSet;
|
|
16
18
|
private extractRevalidateForSet;
|
|
17
19
|
}
|
package/dist/adapters/cache.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { getTagsFromValue, hasBeenRevalidated, writeTags } from "../utils/cache";
|
|
1
|
+
import { getTagsFromValue, hasBeenRevalidated, isStale, writeTags, } from "../utils/cache";
|
|
2
2
|
import { isBinaryContentType } from "../utils/binary";
|
|
3
|
+
import { compareSemver } from "../utils/semver";
|
|
3
4
|
import { debug, error, warn } from "./logger";
|
|
4
5
|
export const SOFT_TAG_PREFIX = "_N_T_/";
|
|
5
6
|
function isFetchCache(options) {
|
|
@@ -57,8 +58,11 @@ export default class Cache {
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
}
|
|
61
|
+
const _isStale = cachedEntry.shouldBypassTagCache
|
|
62
|
+
? false
|
|
63
|
+
: await isStale(key, _tags, _lastModified);
|
|
60
64
|
return {
|
|
61
|
-
lastModified: _lastModified,
|
|
65
|
+
lastModified: _isStale ? 1 : _lastModified,
|
|
62
66
|
value: cachedEntry.value,
|
|
63
67
|
};
|
|
64
68
|
}
|
|
@@ -77,21 +81,27 @@ export default class Cache {
|
|
|
77
81
|
const cacheData = cachedEntry.value;
|
|
78
82
|
const meta = cacheData.meta;
|
|
79
83
|
const tags = getTagsFromValue(cacheData);
|
|
80
|
-
|
|
84
|
+
let _lastModified = cachedEntry.lastModified ?? Date.now();
|
|
81
85
|
const _hasBeenRevalidated = cachedEntry.shouldBypassTagCache
|
|
82
86
|
? false
|
|
83
87
|
: await hasBeenRevalidated(key, tags, cachedEntry);
|
|
84
88
|
if (_hasBeenRevalidated)
|
|
85
89
|
return null;
|
|
90
|
+
const _isStale = cachedEntry.shouldBypassTagCache
|
|
91
|
+
? false
|
|
92
|
+
: await isStale(key, tags, _lastModified);
|
|
86
93
|
const store = globalThis.__openNextAls.getStore();
|
|
87
94
|
if (store) {
|
|
88
|
-
store.lastModified = _lastModified;
|
|
95
|
+
store.lastModified = _isStale ? 1 : _lastModified;
|
|
96
|
+
_lastModified = store.lastModified;
|
|
89
97
|
}
|
|
90
98
|
if (cacheData?.type === "route") {
|
|
91
99
|
return {
|
|
92
100
|
lastModified: _lastModified,
|
|
93
101
|
value: {
|
|
94
|
-
kind: globalThis.
|
|
102
|
+
kind: compareSemver(globalThis.nextVersion, ">=", "15.0.0")
|
|
103
|
+
? "APP_ROUTE"
|
|
104
|
+
: "ROUTE",
|
|
95
105
|
body: Buffer.from(cacheData.body ?? Buffer.alloc(0), isBinaryContentType(String(meta?.headers?.["content-type"]))
|
|
96
106
|
? "base64"
|
|
97
107
|
: "utf8"),
|
|
@@ -101,7 +111,8 @@ export default class Cache {
|
|
|
101
111
|
};
|
|
102
112
|
}
|
|
103
113
|
if (cacheData?.type === "page" || cacheData?.type === "app") {
|
|
104
|
-
if (globalThis.
|
|
114
|
+
if (compareSemver(globalThis.nextVersion, ">=", "15.0.0") &&
|
|
115
|
+
cacheData?.type === "app") {
|
|
105
116
|
const segmentData = new Map();
|
|
106
117
|
if (cacheData.segmentData) {
|
|
107
118
|
for (const [segmentPath, segmentContent] of Object.entries(cacheData.segmentData ?? {})) {
|
|
@@ -124,7 +135,9 @@ export default class Cache {
|
|
|
124
135
|
return {
|
|
125
136
|
lastModified: _lastModified,
|
|
126
137
|
value: {
|
|
127
|
-
kind: globalThis.
|
|
138
|
+
kind: compareSemver(globalThis.nextVersion, ">=", "15.0.0")
|
|
139
|
+
? "PAGES"
|
|
140
|
+
: "PAGE",
|
|
128
141
|
html: cacheData.html,
|
|
129
142
|
pageData: cacheData.type === "page" ? cacheData.json : cacheData.rsc,
|
|
130
143
|
status: meta?.status,
|
|
@@ -209,7 +222,13 @@ export default class Cache {
|
|
|
209
222
|
break;
|
|
210
223
|
}
|
|
211
224
|
case "APP_PAGE": {
|
|
212
|
-
const { html, rscData, headers, status } = data;
|
|
225
|
+
const { html, rscData, headers, status, segmentData, postponed } = data;
|
|
226
|
+
const segmentToWrite = {};
|
|
227
|
+
if (segmentData) {
|
|
228
|
+
for (const [segmentPath, segmentContent,] of segmentData.entries()) {
|
|
229
|
+
segmentToWrite[segmentPath] = segmentContent.toString("utf8");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
213
232
|
await globalThis.incrementalCache.set(key, {
|
|
214
233
|
type: "app",
|
|
215
234
|
html,
|
|
@@ -217,8 +236,10 @@ export default class Cache {
|
|
|
217
236
|
meta: {
|
|
218
237
|
status,
|
|
219
238
|
headers,
|
|
239
|
+
postponed,
|
|
220
240
|
},
|
|
221
241
|
revalidate,
|
|
242
|
+
segmentData: segmentData ? segmentToWrite : undefined,
|
|
222
243
|
}, "cache");
|
|
223
244
|
break;
|
|
224
245
|
}
|
|
@@ -248,7 +269,7 @@ export default class Cache {
|
|
|
248
269
|
detachedPromise?.resolve();
|
|
249
270
|
}
|
|
250
271
|
}
|
|
251
|
-
async revalidateTag(tags) {
|
|
272
|
+
async revalidateTag(tags, durations) {
|
|
252
273
|
const config = globalThis.openNextConfig.dangerous;
|
|
253
274
|
if (config?.disableTagCache || config?.disableIncrementalCache) {
|
|
254
275
|
return;
|
|
@@ -260,7 +281,25 @@ export default class Cache {
|
|
|
260
281
|
try {
|
|
261
282
|
if (globalThis.tagCache.mode === "nextMode") {
|
|
262
283
|
const paths = (await globalThis.tagCache.getPathsByTags?.(_tags)) ?? [];
|
|
263
|
-
|
|
284
|
+
const now = Date.now();
|
|
285
|
+
const tagsToWrite = _tags.map((tag) => {
|
|
286
|
+
if (durations) {
|
|
287
|
+
// Use provided durations
|
|
288
|
+
return {
|
|
289
|
+
tag,
|
|
290
|
+
stale: now,
|
|
291
|
+
expire: durations.expire !== undefined
|
|
292
|
+
? now + durations.expire * 1000
|
|
293
|
+
: undefined,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
// Immediate expiration, default behavior before next 16, now only with {expire: 0}
|
|
297
|
+
return {
|
|
298
|
+
tag,
|
|
299
|
+
expire: now,
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
await writeTags(tagsToWrite);
|
|
264
303
|
if (paths.length > 0) {
|
|
265
304
|
// TODO: we should introduce a new method in cdnInvalidationHandler to invalidate paths by tags for cdn that supports it
|
|
266
305
|
// It also means that we'll need to provide the tags used in every request to the wrapper or converter.
|
|
@@ -283,10 +322,25 @@ export default class Cache {
|
|
|
283
322
|
// Find all keys with the given tag
|
|
284
323
|
const paths = await globalThis.tagCache.getByTag(tag);
|
|
285
324
|
debug("Items", paths);
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
tag
|
|
289
|
-
|
|
325
|
+
const now = Date.now();
|
|
326
|
+
const toInsert = paths.map((path) => {
|
|
327
|
+
const baseEntry = { path, tag };
|
|
328
|
+
if (durations) {
|
|
329
|
+
// Use provided durations
|
|
330
|
+
return {
|
|
331
|
+
...baseEntry,
|
|
332
|
+
stale: now,
|
|
333
|
+
expire: durations.expire !== undefined
|
|
334
|
+
? now + durations.expire * 1000
|
|
335
|
+
: undefined,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
// Default behavior: immediate expiration
|
|
339
|
+
return {
|
|
340
|
+
...baseEntry,
|
|
341
|
+
expire: now,
|
|
342
|
+
};
|
|
343
|
+
});
|
|
290
344
|
// If the tag is a soft tag, we should also revalidate the hard tags
|
|
291
345
|
if (tag.startsWith(SOFT_TAG_PREFIX)) {
|
|
292
346
|
for (const path of paths) {
|
|
@@ -297,10 +351,22 @@ export default class Cache {
|
|
|
297
351
|
for (const hardTag of hardTags) {
|
|
298
352
|
const _paths = await globalThis.tagCache.getByTag(hardTag);
|
|
299
353
|
debug({ hardTag, _paths });
|
|
300
|
-
toInsert.push(..._paths.map((path) =>
|
|
301
|
-
path,
|
|
302
|
-
|
|
303
|
-
|
|
354
|
+
toInsert.push(..._paths.map((path) => {
|
|
355
|
+
const baseEntry = { path, tag: hardTag };
|
|
356
|
+
if (durations) {
|
|
357
|
+
return {
|
|
358
|
+
...baseEntry,
|
|
359
|
+
stale: now,
|
|
360
|
+
expire: durations.expire !== undefined
|
|
361
|
+
? now + durations.expire * 1000
|
|
362
|
+
: undefined,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
...baseEntry,
|
|
367
|
+
expire: now,
|
|
368
|
+
};
|
|
369
|
+
}));
|
|
304
370
|
}
|
|
305
371
|
}
|
|
306
372
|
}
|
|
@@ -21,5 +21,17 @@ declare const _default: {
|
|
|
21
21
|
*/
|
|
22
22
|
expireTags(...tags: string[]): Promise<void>;
|
|
23
23
|
receiveExpiredTags(...tags: string[]): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Added in Next.js 16. Updates tags with optional stale/expire durations.
|
|
26
|
+
* Mirrors the logic in `Cache.revalidateTag` but without CDN invalidation
|
|
27
|
+
* since composable cache keys are not URL paths.
|
|
28
|
+
*
|
|
29
|
+
* When `durations` is provided, marks tags as stale immediately and optionally
|
|
30
|
+
* sets an expiry timestamp. When omitted, immediately expires tags (no grace period).
|
|
31
|
+
* durations.expire is in seconds, but we convert it to milliseconds for storage and comparison.
|
|
32
|
+
*/
|
|
33
|
+
updateTags(tags: string[], durations?: {
|
|
34
|
+
expire?: number;
|
|
35
|
+
}): Promise<void>;
|
|
24
36
|
};
|
|
25
37
|
export default _default;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeTags } from "../utils/cache";
|
|
1
|
+
import { isStale, writeTags } from "../utils/cache";
|
|
2
2
|
import { fromReadableStream, toReadableStream } from "../utils/stream";
|
|
3
3
|
import { debug } from "./logger";
|
|
4
4
|
const pendingWritePromiseMap = new Map();
|
|
@@ -21,14 +21,22 @@ export default {
|
|
|
21
21
|
return undefined;
|
|
22
22
|
}
|
|
23
23
|
debug("composable cache result", result);
|
|
24
|
-
|
|
24
|
+
let revalidate = result.value.revalidate;
|
|
25
25
|
if (globalThis.tagCache.mode === "nextMode" &&
|
|
26
26
|
result.value.tags.length > 0) {
|
|
27
|
+
// We need to check if the tags associated with this entry has been revalidated
|
|
27
28
|
const hasBeenRevalidated = result.shouldBypassTagCache
|
|
28
29
|
? false
|
|
29
30
|
: await globalThis.tagCache.hasBeenRevalidated(result.value.tags, result.lastModified);
|
|
30
31
|
if (hasBeenRevalidated)
|
|
31
32
|
return undefined;
|
|
33
|
+
// Check if tags are stale – entry is valid but needs background revalidation
|
|
34
|
+
const isCacheStale = result.shouldBypassTagCache
|
|
35
|
+
? false
|
|
36
|
+
: await isStale(cacheKey, result.value.tags, result.lastModified);
|
|
37
|
+
if (isCacheStale) {
|
|
38
|
+
revalidate = -1;
|
|
39
|
+
}
|
|
32
40
|
}
|
|
33
41
|
else if (globalThis.tagCache.mode === "original" ||
|
|
34
42
|
globalThis.tagCache.mode === undefined) {
|
|
@@ -37,9 +45,17 @@ export default {
|
|
|
37
45
|
: (await globalThis.tagCache.getLastModified(cacheKey, result.lastModified)) === -1;
|
|
38
46
|
if (hasBeenRevalidated)
|
|
39
47
|
return undefined;
|
|
48
|
+
// Check if tags are stale – entry is valid but needs background revalidation
|
|
49
|
+
const isCacheStale = result.shouldBypassTagCache
|
|
50
|
+
? false
|
|
51
|
+
: await isStale(cacheKey, result.value.tags, result.lastModified);
|
|
52
|
+
if (isCacheStale) {
|
|
53
|
+
revalidate = -1;
|
|
54
|
+
}
|
|
40
55
|
}
|
|
41
56
|
return {
|
|
42
57
|
...result.value,
|
|
58
|
+
revalidate,
|
|
43
59
|
value: toReadableStream(result.value.value),
|
|
44
60
|
};
|
|
45
61
|
}
|
|
@@ -118,4 +134,68 @@ export default {
|
|
|
118
134
|
// This function does absolutely nothing
|
|
119
135
|
return;
|
|
120
136
|
},
|
|
137
|
+
/**
|
|
138
|
+
* Added in Next.js 16. Updates tags with optional stale/expire durations.
|
|
139
|
+
* Mirrors the logic in `Cache.revalidateTag` but without CDN invalidation
|
|
140
|
+
* since composable cache keys are not URL paths.
|
|
141
|
+
*
|
|
142
|
+
* When `durations` is provided, marks tags as stale immediately and optionally
|
|
143
|
+
* sets an expiry timestamp. When omitted, immediately expires tags (no grace period).
|
|
144
|
+
* durations.expire is in seconds, but we convert it to milliseconds for storage and comparison.
|
|
145
|
+
*/
|
|
146
|
+
async updateTags(tags, durations) {
|
|
147
|
+
const config = globalThis.openNextConfig.dangerous;
|
|
148
|
+
if (config?.disableTagCache || config?.disableIncrementalCache) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (tags.length === 0) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const now = Date.now();
|
|
156
|
+
if (globalThis.tagCache.mode === "nextMode") {
|
|
157
|
+
const tagsToWrite = tags.map((tag) => {
|
|
158
|
+
if (durations) {
|
|
159
|
+
return {
|
|
160
|
+
tag,
|
|
161
|
+
stale: now,
|
|
162
|
+
expire: durations.expire !== undefined
|
|
163
|
+
? now + durations.expire * 1000
|
|
164
|
+
: undefined,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// Default: immediate expiry, no grace period
|
|
168
|
+
return { tag, expire: now };
|
|
169
|
+
});
|
|
170
|
+
await writeTags(tagsToWrite);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Original mode: resolve tag → path mappings first
|
|
174
|
+
const originalTagCache = globalThis.tagCache;
|
|
175
|
+
const pathsPerTag = await Promise.all(tags.map(async (tag) => {
|
|
176
|
+
const paths = await originalTagCache.getByTag(tag);
|
|
177
|
+
return paths.map((path) => {
|
|
178
|
+
if (durations) {
|
|
179
|
+
return {
|
|
180
|
+
path,
|
|
181
|
+
tag,
|
|
182
|
+
stale: now,
|
|
183
|
+
expire: durations.expire !== undefined
|
|
184
|
+
? now + durations.expire * 1000
|
|
185
|
+
: undefined,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return { path, tag, expire: now };
|
|
189
|
+
});
|
|
190
|
+
}));
|
|
191
|
+
const toWrite = pathsPerTag.flat();
|
|
192
|
+
if (toWrite.length > 0) {
|
|
193
|
+
await writeTags(toWrite);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
debug("Failed to update tags", e);
|
|
199
|
+
}
|
|
200
|
+
},
|
|
121
201
|
};
|
|
@@ -30,6 +30,8 @@ async function insert(requestType) {
|
|
|
30
30
|
tag: item.tag.S,
|
|
31
31
|
path: item.path.S,
|
|
32
32
|
revalidatedAt: Number.parseInt(item.revalidatedAt.N),
|
|
33
|
+
...(item.stale && { stale: Number.parseInt(item.stale.N) }),
|
|
34
|
+
...(item.expire && { expire: Number.parseInt(item.expire.N) }),
|
|
33
35
|
}));
|
|
34
36
|
await tagCache.writeTags(parsedData);
|
|
35
37
|
return {
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { createGenericHandler } from "../core/createGenericHandler.js";
|
|
5
5
|
import { debug, error } from "./logger.js";
|
|
6
6
|
const prerenderManifest = loadPrerenderManifest();
|
|
7
|
+
const ALLOWED_STATUS_CODES = [200, 307, 308, 404];
|
|
7
8
|
const defaultHandler = async (event) => {
|
|
8
9
|
const failedRecords = [];
|
|
9
10
|
for (const record of event.records) {
|
|
@@ -32,7 +33,7 @@ const defaultHandler = async (event) => {
|
|
|
32
33
|
headers: res.headers,
|
|
33
34
|
statusCode: res.statusCode,
|
|
34
35
|
});
|
|
35
|
-
if (res.statusCode
|
|
36
|
+
if (!ALLOWED_STATUS_CODES.includes(res.statusCode ?? 0) ||
|
|
36
37
|
res.headers["x-nextjs-cache"] !== "REVALIDATED") {
|
|
37
38
|
failedRecords.push(record);
|
|
38
39
|
}
|
|
@@ -11,7 +11,6 @@ export function compileCache(options, format = "cjs") {
|
|
|
11
11
|
const { config } = options;
|
|
12
12
|
const ext = format === "cjs" ? "cjs" : "mjs";
|
|
13
13
|
const compiledCacheFile = path.join(options.buildDir, `cache.${ext}`);
|
|
14
|
-
const isAfter15 = buildHelper.compareSemver(options.nextVersion, ">=", "15.0.0");
|
|
15
14
|
// Normal cache
|
|
16
15
|
buildHelper.esbuildSync({
|
|
17
16
|
external: ["next", "styled-jsx", "react", "@aws-sdk/*"],
|
|
@@ -23,7 +22,7 @@ export function compileCache(options, format = "cjs") {
|
|
|
23
22
|
js: [
|
|
24
23
|
`globalThis.disableIncrementalCache = ${config.dangerous?.disableIncrementalCache ?? false};`,
|
|
25
24
|
`globalThis.disableDynamoDBCache = ${config.dangerous?.disableTagCache ?? false};`,
|
|
26
|
-
`globalThis.
|
|
25
|
+
`globalThis.nextVersion = "${options.nextVersion}";`,
|
|
27
26
|
].join(""),
|
|
28
27
|
},
|
|
29
28
|
}, options);
|
|
@@ -41,7 +40,7 @@ export function compileCache(options, format = "cjs") {
|
|
|
41
40
|
js: [
|
|
42
41
|
`globalThis.disableIncrementalCache = ${config.dangerous?.disableIncrementalCache ?? false};`,
|
|
43
42
|
`globalThis.disableDynamoDBCache = ${config.dangerous?.disableTagCache ?? false};`,
|
|
44
|
-
`globalThis.
|
|
43
|
+
`globalThis.nextVersion = "${options.nextVersion}";`,
|
|
45
44
|
].join(""),
|
|
46
45
|
},
|
|
47
46
|
}, options);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function copyFileAndMakeOwnerWritable(src: string, dest: string): void;
|
|
8
8
|
export declare function isExcluded(srcPath: string): boolean;
|
|
9
|
+
export declare function isNonLinuxPlatformPackage(srcPath: string): boolean;
|
|
9
10
|
interface CopyTracedFilesOptions {
|
|
10
11
|
buildOutputPath: string;
|
|
11
12
|
packagePath: string;
|
|
@@ -40,6 +40,12 @@ export function isExcluded(srcPath) {
|
|
|
40
40
|
escape: false,
|
|
41
41
|
})));
|
|
42
42
|
}
|
|
43
|
+
const NON_LINUX_PLATFORMS = ["darwin", "win32", "freebsd"];
|
|
44
|
+
const platformPattern = NON_LINUX_PLATFORMS.join("|");
|
|
45
|
+
const nonLinuxPlatformRegex = getCrossPlatformPathRegex(`/node_modules/(?:@[^/]+/)?(?:[^/]+-)?(${platformPattern})-[^/]+/`, { escape: false });
|
|
46
|
+
export function isNonLinuxPlatformPackage(srcPath) {
|
|
47
|
+
return nonLinuxPlatformRegex.test(srcPath);
|
|
48
|
+
}
|
|
43
49
|
function copyPatchFile(outputDir) {
|
|
44
50
|
const patchFile = path.join(__dirname, "patch", "patchedAsyncStorage.js");
|
|
45
51
|
const outputPatchFile = path.join(outputDir, "patchedAsyncStorage.cjs");
|
|
@@ -190,11 +196,22 @@ File ${serverPath} does not exist
|
|
|
190
196
|
const tracedFiles = [];
|
|
191
197
|
const erroredFiles = [];
|
|
192
198
|
//Actually copy the files
|
|
199
|
+
const skippedPlatformPackages = new Set();
|
|
193
200
|
filesToCopy.forEach((to, from) => {
|
|
194
201
|
// We don't want to copy excluded packages (e.g. sharp)
|
|
195
202
|
if (isExcluded(from)) {
|
|
196
203
|
return;
|
|
197
204
|
}
|
|
205
|
+
// Skip non-Linux platform-specific native binaries (e.g. @swc/core-darwin-arm64)
|
|
206
|
+
// Set OPEN_NEXT_SKIP_PLATFORM_FILTER=true to bypass this check
|
|
207
|
+
if (!process.env.OPEN_NEXT_SKIP_PLATFORM_FILTER &&
|
|
208
|
+
isNonLinuxPlatformPackage(from)) {
|
|
209
|
+
const match = from.match(/node_modules\/(?:\.pnpm\/.*\/node_modules\/)?((?:@[^/]+\/)?[^/]+)/);
|
|
210
|
+
if (match) {
|
|
211
|
+
skippedPlatformPackages.add(match[1]);
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
198
215
|
tracedFiles.push(to);
|
|
199
216
|
mkdirSync(path.dirname(to), { recursive: true });
|
|
200
217
|
let symlink = null;
|
|
@@ -229,6 +246,9 @@ File ${serverPath} does not exist
|
|
|
229
246
|
}
|
|
230
247
|
}
|
|
231
248
|
});
|
|
249
|
+
if (skippedPlatformPackages.size > 0) {
|
|
250
|
+
logger.debug("Skipped non-Linux platform packages:", [...skippedPlatformPackages].join(", "));
|
|
251
|
+
}
|
|
232
252
|
readdirSync(standaloneNextDir)
|
|
233
253
|
.filter((fileOrDir) => !statSync(path.join(standaloneNextDir, fileOrDir)).isDirectory())
|
|
234
254
|
.forEach((file) => {
|
|
@@ -39,7 +39,10 @@ export function createStaticAssets(options, { useBasePath = false } = {}) {
|
|
|
39
39
|
fs.copyFileSync(path.join(appBuildOutputPath, ".next/BUILD_ID"), path.join(outputPath, "BUILD_ID"));
|
|
40
40
|
fs.cpSync(path.join(appBuildOutputPath, ".next/static"), path.join(outputPath, "_next", "static"), { recursive: true });
|
|
41
41
|
if (fs.existsSync(appPublicPath)) {
|
|
42
|
-
fs.cpSync(appPublicPath, outputPath, {
|
|
42
|
+
fs.cpSync(appPublicPath, outputPath, {
|
|
43
|
+
recursive: true,
|
|
44
|
+
dereference: true,
|
|
45
|
+
});
|
|
43
46
|
}
|
|
44
47
|
const appSrcPath = fs.existsSync(path.join(appPath, "src"))
|
|
45
48
|
? "src/app"
|
package/dist/build/helper.d.ts
CHANGED
|
@@ -62,16 +62,8 @@ export declare function getHtmlPages(dotNextPath: string): Set<string>;
|
|
|
62
62
|
export declare function getBuildId(options: BuildOptions): string;
|
|
63
63
|
export declare function getOpenNextVersion(): string;
|
|
64
64
|
export declare function getNextVersion(appPath: string): string;
|
|
65
|
-
export type SemverOp
|
|
66
|
-
|
|
67
|
-
* Compare two semver versions.
|
|
68
|
-
*
|
|
69
|
-
* @param v1 - First version. Can be "latest", otherwise it should be a valid semver version in the format of `major.minor.patch`. Usually is the next version from the package.json without canary suffix. If minor or patch are missing, they are considered 0.
|
|
70
|
-
* @param v2 - Second version. Should not be "latest", it should be a valid semver version in the format of `major.minor.patch`. If minor or patch are missing, they are considered 0.
|
|
71
|
-
* @example
|
|
72
|
-
* compareSemver("2.0.0", ">=", "1.0.0") === true
|
|
73
|
-
*/
|
|
74
|
-
export declare function compareSemver(v1: string, operator: SemverOp, v2: string): boolean;
|
|
65
|
+
export type { SemverOp } from "../utils/semver.js";
|
|
66
|
+
export { compareSemver } from "../utils/semver.js";
|
|
75
67
|
/**
|
|
76
68
|
* Check if a Next.js version is supported by the Next.js team.
|
|
77
69
|
* Major versions are supported for 2 years following their initial release.
|
|
@@ -95,9 +87,12 @@ export declare function copyEnvFile(appPath: string, packagePath: string, output
|
|
|
95
87
|
* Finds the path to the Next configuration file if it exists.
|
|
96
88
|
*
|
|
97
89
|
* @param appPath The directory to check for the Next config file
|
|
98
|
-
* @returns
|
|
90
|
+
* @returns An object with the full path to Next config file alongside a flag indicating whether the file is in typescript if it exists, undefined otherwise
|
|
99
91
|
*/
|
|
100
|
-
export declare function findNextConfig({ appPath, }: Pick<BuildOptions, "appPath">):
|
|
92
|
+
export declare function findNextConfig({ appPath, }: Pick<BuildOptions, "appPath">): {
|
|
93
|
+
path: string;
|
|
94
|
+
isTypescript: boolean;
|
|
95
|
+
} | undefined;
|
|
101
96
|
/**
|
|
102
97
|
* Check we are in a Nextjs app by looking for the Nextjs config file.
|
|
103
98
|
*/
|
package/dist/build/helper.js
CHANGED
|
@@ -214,61 +214,7 @@ export function getNextVersion(appPath) {
|
|
|
214
214
|
// Drop the -canary.n suffix
|
|
215
215
|
return version.split("-")[0];
|
|
216
216
|
}
|
|
217
|
-
|
|
218
|
-
* Compare two semver versions.
|
|
219
|
-
*
|
|
220
|
-
* @param v1 - First version. Can be "latest", otherwise it should be a valid semver version in the format of `major.minor.patch`. Usually is the next version from the package.json without canary suffix. If minor or patch are missing, they are considered 0.
|
|
221
|
-
* @param v2 - Second version. Should not be "latest", it should be a valid semver version in the format of `major.minor.patch`. If minor or patch are missing, they are considered 0.
|
|
222
|
-
* @example
|
|
223
|
-
* compareSemver("2.0.0", ">=", "1.0.0") === true
|
|
224
|
-
*/
|
|
225
|
-
export function compareSemver(v1, operator, v2) {
|
|
226
|
-
// - = 0 when versions are equal
|
|
227
|
-
// - > 0 if v1 > v2
|
|
228
|
-
// - < 0 if v2 > v1
|
|
229
|
-
let versionDiff = 0;
|
|
230
|
-
if (v1 === "latest") {
|
|
231
|
-
versionDiff = 1;
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
if (/^[^\d]/.test(v1)) {
|
|
235
|
-
// biome-ignore lint/style/noParameterAssign:
|
|
236
|
-
v1 = v1.substring(1);
|
|
237
|
-
}
|
|
238
|
-
if (/^[^\d]/.test(v2)) {
|
|
239
|
-
// biome-ignore lint/style/noParameterAssign:
|
|
240
|
-
v2 = v2.substring(1);
|
|
241
|
-
}
|
|
242
|
-
const [major1, minor1 = 0, patch1 = 0] = v1.split(".").map(Number);
|
|
243
|
-
const [major2, minor2 = 0, patch2 = 0] = v2.split(".").map(Number);
|
|
244
|
-
if (Number.isNaN(major1) || Number.isNaN(major2)) {
|
|
245
|
-
throw new Error("The major version is required.");
|
|
246
|
-
}
|
|
247
|
-
if (major1 !== major2) {
|
|
248
|
-
versionDiff = major1 - major2;
|
|
249
|
-
}
|
|
250
|
-
else if (minor1 !== minor2) {
|
|
251
|
-
versionDiff = minor1 - minor2;
|
|
252
|
-
}
|
|
253
|
-
else if (patch1 !== patch2) {
|
|
254
|
-
versionDiff = patch1 - patch2;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
switch (operator) {
|
|
258
|
-
case "=":
|
|
259
|
-
return versionDiff === 0;
|
|
260
|
-
case ">=":
|
|
261
|
-
return versionDiff >= 0;
|
|
262
|
-
case "<=":
|
|
263
|
-
return versionDiff <= 0;
|
|
264
|
-
case ">":
|
|
265
|
-
return versionDiff > 0;
|
|
266
|
-
case "<":
|
|
267
|
-
return versionDiff < 0;
|
|
268
|
-
default:
|
|
269
|
-
throw new Error(`Unsupported operator: ${operator}`);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
217
|
+
export { compareSemver } from "../utils/semver.js";
|
|
272
218
|
/**
|
|
273
219
|
* Next.js major version release dates (YYYY-MM-DD).
|
|
274
220
|
* Each major version is supported for 2 years from its release date.
|
|
@@ -352,10 +298,26 @@ export function copyEnvFile(appPath, packagePath, outputPath) {
|
|
|
352
298
|
* Finds the path to the Next configuration file if it exists.
|
|
353
299
|
*
|
|
354
300
|
* @param appPath The directory to check for the Next config file
|
|
355
|
-
* @returns
|
|
301
|
+
* @returns An object with the full path to Next config file alongside a flag indicating whether the file is in typescript if it exists, undefined otherwise
|
|
356
302
|
*/
|
|
357
303
|
export function findNextConfig({ appPath, }) {
|
|
358
|
-
|
|
304
|
+
const extensions = [
|
|
305
|
+
{ ext: "ts", isTypescript: true },
|
|
306
|
+
{ ext: "mts", isTypescript: true },
|
|
307
|
+
{ ext: "cts", isTypescript: true },
|
|
308
|
+
{ ext: "js", isTypescript: false },
|
|
309
|
+
{ ext: "mjs", isTypescript: false },
|
|
310
|
+
{ ext: "cjs", isTypescript: false },
|
|
311
|
+
];
|
|
312
|
+
for (const { ext, isTypescript } of extensions) {
|
|
313
|
+
const configPath = path.join(appPath, `next.config.${ext}`);
|
|
314
|
+
if (fs.existsSync(configPath)) {
|
|
315
|
+
return {
|
|
316
|
+
path: configPath,
|
|
317
|
+
isTypescript,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
359
321
|
}
|
|
360
322
|
/**
|
|
361
323
|
* Check we are in a Nextjs app by looking for the Nextjs config file.
|
|
@@ -2,6 +2,7 @@ import type { CodePatcher } from "../codePatcher.js";
|
|
|
2
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
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
4
|
export declare const emptyHandleNextImageRequestRule = "\nrule:\n kind: assignment_expression\n pattern: this.handleNextImageRequest = $VALUE\n inside:\n kind: method_definition\n stopBy: end\n has:\n kind: property_identifier\n regex: ^constructor$\n inside:\n kind: class_body\nfix:\n this.handleNextImageRequest = async (req, res, parsedUrl) => false\n";
|
|
5
|
+
export declare const provideInternalWaitUntil = "\nrule:\n kind: return_statement\n inside:\n kind: method_definition\n stopBy: end\n has:\n kind: property_identifier\n regex: getInternalWaitUntil\n\nfix:\n return globalThis.__openNextAls.getStore()?.waitUntil;\n";
|
|
5
6
|
/**
|
|
6
7
|
* Swaps the body for a throwing implementation
|
|
7
8
|
*
|
|
@@ -47,6 +47,19 @@ rule:
|
|
|
47
47
|
fix:
|
|
48
48
|
this.handleNextImageRequest = async (req, res, parsedUrl) => false
|
|
49
49
|
`;
|
|
50
|
+
export const provideInternalWaitUntil = `
|
|
51
|
+
rule:
|
|
52
|
+
kind: return_statement
|
|
53
|
+
inside:
|
|
54
|
+
kind: method_definition
|
|
55
|
+
stopBy: end
|
|
56
|
+
has:
|
|
57
|
+
kind: property_identifier
|
|
58
|
+
regex: getInternalWaitUntil
|
|
59
|
+
|
|
60
|
+
fix:
|
|
61
|
+
return globalThis.__openNextAls.getStore()?.waitUntil;
|
|
62
|
+
`;
|
|
50
63
|
/**
|
|
51
64
|
* Swaps the body for a throwing implementation
|
|
52
65
|
*
|
|
@@ -119,6 +132,12 @@ export const patchNextServer = {
|
|
|
119
132
|
contentFilter: /getMiddlewareManifest/,
|
|
120
133
|
patchCode: createPatchCode(removeMiddlewareManifestRule),
|
|
121
134
|
},
|
|
135
|
+
{
|
|
136
|
+
versions: ">=16.0.0",
|
|
137
|
+
pathFilter,
|
|
138
|
+
contentFilter: /getInternalWaitUntil/,
|
|
139
|
+
patchCode: createPatchCode(provideInternalWaitUntil),
|
|
140
|
+
},
|
|
122
141
|
...babelPatches,
|
|
123
142
|
],
|
|
124
143
|
};
|
|
@@ -45,25 +45,14 @@ export async function patchOriginalNextConfig(options) {
|
|
|
45
45
|
* @returns
|
|
46
46
|
*/
|
|
47
47
|
async function importNextConfigFromSource(buildOptions) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const possibleExtensions = [...tsExtensions, ".mjs", ".js", ".cjs"];
|
|
51
|
-
let configPath;
|
|
52
|
-
let configExtension;
|
|
53
|
-
for (const ext of possibleExtensions) {
|
|
54
|
-
const testPath = path.join(buildOptions.appPath, `next.config${ext}`);
|
|
55
|
-
if (fs.existsSync(testPath)) {
|
|
56
|
-
configPath = testPath;
|
|
57
|
-
configExtension = ext;
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
if (!configPath || !configExtension) {
|
|
48
|
+
const nextConfigDetails = buildHelper.findNextConfig(buildOptions);
|
|
49
|
+
if (!nextConfigDetails) {
|
|
62
50
|
throw new Error("Could not find next.config file");
|
|
63
51
|
}
|
|
52
|
+
const { path: configPath, isTypescript: configIsTs } = nextConfigDetails;
|
|
64
53
|
let configToImport;
|
|
65
54
|
// Only compile if the extension is a TypeScript extension
|
|
66
|
-
if (
|
|
55
|
+
if (configIsTs) {
|
|
67
56
|
await build({
|
|
68
57
|
entryPoints: [configPath],
|
|
69
58
|
outfile: path.join(buildOptions.tempBuildDir, "next.config.mjs"),
|