@opennextjs/cloudflare 1.6.1 → 1.6.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/api/durable-objects/sharded-tag-cache.d.ts +1 -0
- package/dist/api/durable-objects/sharded-tag-cache.js +6 -0
- package/dist/api/overrides/tag-cache/do-sharded-tag-cache.d.ts +23 -4
- package/dist/api/overrides/tag-cache/do-sharded-tag-cache.js +86 -45
- package/dist/cli/build/build.js +1 -1
- package/dist/cli/build/bundle-server.d.ts +2 -1
- package/dist/cli/build/bundle-server.js +3 -3
- package/dist/cli/build/open-next/createServerBundle.js +2 -0
- package/dist/cli/build/patches/plugins/dynamic-requires.js +8 -12
- package/dist/cli/build/patches/plugins/find-dir.js +3 -5
- package/dist/cli/build/patches/plugins/instrumentation.js +11 -17
- package/dist/cli/build/patches/plugins/load-manifest.js +8 -10
- package/dist/cli/build/patches/plugins/next-server.js +13 -15
- package/dist/cli/build/patches/plugins/open-next.js +6 -8
- package/dist/cli/build/patches/plugins/patch-depd-deprecations.js +3 -5
- package/dist/cli/build/patches/plugins/require.js +36 -38
- package/dist/cli/build/patches/plugins/res-revalidate.js +5 -7
- package/dist/cli/build/patches/plugins/use-cache.js +5 -7
- package/dist/cli/build/utils/ensure-cf-config.js +6 -4
- package/dist/cli/commands/skew-protection.d.ts +2 -2
- package/dist/cli/commands/skew-protection.js +6 -2
- package/dist/cli/templates/images.d.ts +1 -1
- package/dist/cli/templates/images.js +28 -5
- package/package.json +2 -2
|
@@ -5,4 +5,5 @@ export declare class DOShardedTagCache extends DurableObject<CloudflareEnv> {
|
|
|
5
5
|
getLastRevalidated(tags: string[]): Promise<number>;
|
|
6
6
|
hasBeenRevalidated(tags: string[], lastModified?: number): Promise<boolean>;
|
|
7
7
|
writeTags(tags: string[], lastModified: number): Promise<void>;
|
|
8
|
+
getRevalidationTimes(tags: string[]): Promise<Record<string, number>>;
|
|
8
9
|
}
|
|
@@ -34,4 +34,10 @@ export class DOShardedTagCache extends DurableObject {
|
|
|
34
34
|
this.sql.exec(`INSERT OR REPLACE INTO revalidations (tag, revalidatedAt) VALUES (?, ?)`, tag, lastModified);
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
|
+
async getRevalidationTimes(tags) {
|
|
38
|
+
const result = this.sql
|
|
39
|
+
.exec(`SELECT tag, revalidatedAt FROM revalidations WHERE tag IN (${tags.map(() => "?").join(", ")})`, ...tags)
|
|
40
|
+
.toArray();
|
|
41
|
+
return Object.fromEntries(result.map((row) => [row.tag, row.revalidatedAt]));
|
|
42
|
+
}
|
|
37
43
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { NextModeTagCache } from "@opennextjs/aws/types/overrides.js";
|
|
2
|
+
import { DOShardedTagCache } from "../../durable-objects/sharded-tag-cache.js";
|
|
2
3
|
export declare const DEFAULT_WRITE_RETRIES = 3;
|
|
3
4
|
export declare const DEFAULT_NUM_SHARDS = 4;
|
|
4
5
|
export declare const NAME = "do-sharded-tag-cache";
|
|
@@ -34,6 +35,13 @@ interface ShardedDOTagCacheOptions {
|
|
|
34
35
|
* @default 5
|
|
35
36
|
*/
|
|
36
37
|
regionalCacheTtlSec?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Whether to persist missing tags in the regional cache.
|
|
40
|
+
* This is dangerous if you don't invalidate the Cache API when you revalidate tags as you could end up storing stale data in the data cache.
|
|
41
|
+
*
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
regionalCacheDangerouslyPersistMissingTags?: boolean;
|
|
37
45
|
/**
|
|
38
46
|
* Enable shard replication to handle higher load.
|
|
39
47
|
*
|
|
@@ -91,7 +99,6 @@ export declare class DOId {
|
|
|
91
99
|
interface CacheTagKeyOptions {
|
|
92
100
|
doId: DOId;
|
|
93
101
|
tags: string[];
|
|
94
|
-
type: "boolean" | "number";
|
|
95
102
|
}
|
|
96
103
|
declare class ShardedDOTagCache implements NextModeTagCache {
|
|
97
104
|
private opts;
|
|
@@ -145,9 +152,21 @@ declare class ShardedDOTagCache implements NextModeTagCache {
|
|
|
145
152
|
writeTags(tags: string[]): Promise<void>;
|
|
146
153
|
performWriteTagsWithRetry(doId: DOId, tags: string[], lastModified: number, retryNumber?: number): Promise<void>;
|
|
147
154
|
getCacheInstance(): Promise<Cache | undefined>;
|
|
148
|
-
getCacheUrlKey(
|
|
149
|
-
|
|
150
|
-
|
|
155
|
+
getCacheUrlKey(doId: DOId, tag: string): string;
|
|
156
|
+
/**
|
|
157
|
+
* Get the last revalidation time for the tags from the regional cache
|
|
158
|
+
* If the cache is not enabled, it will return an empty array
|
|
159
|
+
* @returns An array of objects with the tag and the last revalidation time
|
|
160
|
+
*/
|
|
161
|
+
getFromRegionalCache(opts: CacheTagKeyOptions): Promise<{
|
|
162
|
+
tag: string;
|
|
163
|
+
time: number;
|
|
164
|
+
}[]>;
|
|
165
|
+
putToRegionalCache(optsKey: CacheTagKeyOptions, stub: DurableObjectStub<DOShardedTagCache>): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Deletes the regional cache for the given tags
|
|
168
|
+
* This is used to ensure that the cache is cleared when the tags are revalidated
|
|
169
|
+
*/
|
|
151
170
|
deleteRegionalCache(optsKey: CacheTagKeyOptions): Promise<void>;
|
|
152
171
|
}
|
|
153
172
|
declare const _default: (opts?: ShardedDOTagCacheOptions) => ShardedDOTagCache;
|
|
@@ -179,26 +179,25 @@ class ShardedDOTagCache {
|
|
|
179
179
|
const { isDisabled } = await this.getConfig();
|
|
180
180
|
if (isDisabled)
|
|
181
181
|
return 0;
|
|
182
|
+
if (tags.length === 0)
|
|
183
|
+
return 0; // No tags to check
|
|
184
|
+
const deduplicatedTags = Array.from(new Set(tags)); // We deduplicate the tags to avoid unnecessary requests
|
|
182
185
|
try {
|
|
183
|
-
const shardedTagGroups = this.groupTagsByDO({ tags });
|
|
186
|
+
const shardedTagGroups = this.groupTagsByDO({ tags: deduplicatedTags });
|
|
184
187
|
const shardedTagRevalidationOutcomes = await Promise.all(shardedTagGroups.map(async ({ doId, tags }) => {
|
|
185
|
-
const cachedValue = await this.getFromRegionalCache({ doId, tags
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
return parseInt(cached, 10);
|
|
190
|
-
}
|
|
191
|
-
catch (e) {
|
|
192
|
-
debug("Error while parsing cached value", e);
|
|
193
|
-
// If we can't parse the cached value, we should just ignore it and go to the durable object
|
|
194
|
-
}
|
|
188
|
+
const cachedValue = await this.getFromRegionalCache({ doId, tags });
|
|
189
|
+
// If all the value were found in the regional cache, we can just return the max value
|
|
190
|
+
if (cachedValue.length === tags.length) {
|
|
191
|
+
return Math.max(...cachedValue.map((item) => item.time));
|
|
195
192
|
}
|
|
193
|
+
// Otherwise we need to check the durable object on the ones that were not found in the cache
|
|
194
|
+
const filteredTags = deduplicatedTags.filter((tag) => !cachedValue.some((item) => item.tag === tag));
|
|
196
195
|
const stub = this.getDurableObjectStub(doId);
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
return
|
|
196
|
+
const lastRevalidated = await stub.getLastRevalidated(filteredTags);
|
|
197
|
+
const result = Math.max(...cachedValue.map((item) => item.time), lastRevalidated);
|
|
198
|
+
// We then need to populate the regional cache with the missing tags
|
|
199
|
+
getCloudflareContext().ctx.waitUntil(this.putToRegionalCache({ doId, tags }, stub));
|
|
200
|
+
return result;
|
|
202
201
|
}));
|
|
203
202
|
return Math.max(...shardedTagRevalidationOutcomes);
|
|
204
203
|
}
|
|
@@ -221,17 +220,20 @@ class ShardedDOTagCache {
|
|
|
221
220
|
try {
|
|
222
221
|
const shardedTagGroups = this.groupTagsByDO({ tags });
|
|
223
222
|
const shardedTagRevalidationOutcomes = await Promise.all(shardedTagGroups.map(async ({ doId, tags }) => {
|
|
224
|
-
const cachedValue = await this.getFromRegionalCache({ doId, tags
|
|
225
|
-
|
|
226
|
-
|
|
223
|
+
const cachedValue = await this.getFromRegionalCache({ doId, tags });
|
|
224
|
+
// If one of the cached values is newer than the lastModified, we can return true
|
|
225
|
+
const cacheHasBeenRevalidated = cachedValue.some((cachedValue) => {
|
|
226
|
+
return (cachedValue.time ?? 0) > (lastModified ?? Date.now());
|
|
227
|
+
});
|
|
228
|
+
if (cacheHasBeenRevalidated) {
|
|
229
|
+
return true;
|
|
227
230
|
}
|
|
228
231
|
const stub = this.getDurableObjectStub(doId);
|
|
229
232
|
const _hasBeenRevalidated = await stub.hasBeenRevalidated(tags, lastModified);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
getCloudflareContext().ctx.waitUntil(this.putToRegionalCache({ doId, tags, type: "boolean" }, _hasBeenRevalidated));
|
|
233
|
+
const remainingTags = tags.filter((tag) => !cachedValue.some((item) => item.tag === tag));
|
|
234
|
+
if (remainingTags.length > 0) {
|
|
235
|
+
// We need to put the missing tags in the regional cache
|
|
236
|
+
getCloudflareContext().ctx.waitUntil(this.putToRegionalCache({ doId, tags: remainingTags }, stub));
|
|
235
237
|
}
|
|
236
238
|
return _hasBeenRevalidated;
|
|
237
239
|
}));
|
|
@@ -266,10 +268,7 @@ class ShardedDOTagCache {
|
|
|
266
268
|
await stub.writeTags(tags, lastModified);
|
|
267
269
|
// Depending on the shards and the tags, deleting from the regional cache will not work for every tag
|
|
268
270
|
// We also need to delete both cache
|
|
269
|
-
await Promise.all([
|
|
270
|
-
this.deleteRegionalCache({ doId, tags, type: "boolean" }),
|
|
271
|
-
this.deleteRegionalCache({ doId, tags, type: "number" }),
|
|
272
|
-
]);
|
|
271
|
+
await Promise.all([this.deleteRegionalCache({ doId, tags })]);
|
|
273
272
|
}
|
|
274
273
|
catch (e) {
|
|
275
274
|
error("Error while writing tags", e);
|
|
@@ -293,41 +292,79 @@ class ShardedDOTagCache {
|
|
|
293
292
|
}
|
|
294
293
|
return this.localCache;
|
|
295
294
|
}
|
|
296
|
-
getCacheUrlKey(
|
|
297
|
-
|
|
298
|
-
return `http://local.cache/shard/${doId.shardId}?type=${type}&tags=${encodeURIComponent(tags.join(";"))}`;
|
|
295
|
+
getCacheUrlKey(doId, tag) {
|
|
296
|
+
return `http://local.cache/shard/${doId.shardId}?tag=${encodeURIComponent(tag)}`;
|
|
299
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* Get the last revalidation time for the tags from the regional cache
|
|
300
|
+
* If the cache is not enabled, it will return an empty array
|
|
301
|
+
* @returns An array of objects with the tag and the last revalidation time
|
|
302
|
+
*/
|
|
300
303
|
async getFromRegionalCache(opts) {
|
|
301
304
|
try {
|
|
302
305
|
if (!this.opts.regionalCache)
|
|
303
|
-
return;
|
|
306
|
+
return [];
|
|
304
307
|
const cache = await this.getCacheInstance();
|
|
305
308
|
if (!cache)
|
|
306
|
-
return;
|
|
307
|
-
|
|
309
|
+
return [];
|
|
310
|
+
const result = await Promise.all(opts.tags.map(async (tag) => {
|
|
311
|
+
const cachedResponse = await cache.match(this.getCacheUrlKey(opts.doId, tag));
|
|
312
|
+
if (!cachedResponse)
|
|
313
|
+
return null;
|
|
314
|
+
const cachedText = await cachedResponse.text();
|
|
315
|
+
try {
|
|
316
|
+
return { tag, time: parseInt(cachedText, 10) };
|
|
317
|
+
}
|
|
318
|
+
catch (e) {
|
|
319
|
+
debugCache("Error while parsing cached value", e);
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
}));
|
|
323
|
+
return result.filter((item) => item !== null);
|
|
308
324
|
}
|
|
309
325
|
catch (e) {
|
|
310
326
|
error("Error while fetching from regional cache", e);
|
|
327
|
+
return [];
|
|
311
328
|
}
|
|
312
329
|
}
|
|
313
|
-
async putToRegionalCache(optsKey,
|
|
330
|
+
async putToRegionalCache(optsKey, stub) {
|
|
314
331
|
if (!this.opts.regionalCache)
|
|
315
332
|
return;
|
|
316
333
|
const cache = await this.getCacheInstance();
|
|
317
334
|
if (!cache)
|
|
318
335
|
return;
|
|
319
336
|
const tags = optsKey.tags;
|
|
320
|
-
await
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
337
|
+
const tagsLastRevalidated = await stub.getRevalidationTimes(tags);
|
|
338
|
+
await Promise.all(tags.map(async (tag) => {
|
|
339
|
+
let lastRevalidated = tagsLastRevalidated[tag];
|
|
340
|
+
if (lastRevalidated === undefined) {
|
|
341
|
+
if (this.opts.regionalCacheDangerouslyPersistMissingTags) {
|
|
342
|
+
lastRevalidated = 0; // If the tag is not found, we set it to 0 as it means it has never been revalidated before.
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
debugCache("Tag not found in revalidation times", { tag, optsKey });
|
|
346
|
+
return; // If the tag is not found, we skip it
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const cacheKey = this.getCacheUrlKey(optsKey.doId, tag);
|
|
350
|
+
debugCache("Putting to regional cache", { cacheKey, lastRevalidated });
|
|
351
|
+
await cache.put(cacheKey, new Response(lastRevalidated.toString(), {
|
|
352
|
+
status: 200,
|
|
353
|
+
headers: {
|
|
354
|
+
"cache-control": `max-age=${this.opts.regionalCacheTtlSec ?? 5}`,
|
|
355
|
+
...(tags.length > 0
|
|
356
|
+
? {
|
|
357
|
+
"cache-tag": tags.join(","),
|
|
358
|
+
}
|
|
359
|
+
: {}),
|
|
360
|
+
},
|
|
361
|
+
}));
|
|
329
362
|
}));
|
|
330
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Deletes the regional cache for the given tags
|
|
366
|
+
* This is used to ensure that the cache is cleared when the tags are revalidated
|
|
367
|
+
*/
|
|
331
368
|
async deleteRegionalCache(optsKey) {
|
|
332
369
|
// We never want to crash because of the cache
|
|
333
370
|
try {
|
|
@@ -336,7 +373,11 @@ class ShardedDOTagCache {
|
|
|
336
373
|
const cache = await this.getCacheInstance();
|
|
337
374
|
if (!cache)
|
|
338
375
|
return;
|
|
339
|
-
await
|
|
376
|
+
await Promise.all(optsKey.tags.map(async (tag) => {
|
|
377
|
+
const cacheKey = this.getCacheUrlKey(optsKey.doId, tag);
|
|
378
|
+
debugCache("Deleting from regional cache", { cacheKey });
|
|
379
|
+
await cache.delete(cacheKey);
|
|
380
|
+
}));
|
|
340
381
|
}
|
|
341
382
|
catch (e) {
|
|
342
383
|
debugCache("Error while deleting from regional cache", e);
|
package/dist/cli/build/build.js
CHANGED
|
@@ -63,7 +63,7 @@ export async function build(options, config, projectOpts, wranglerConfig) {
|
|
|
63
63
|
}
|
|
64
64
|
await createServerBundle(options);
|
|
65
65
|
await compileDurableObjects(options);
|
|
66
|
-
await bundleServer(options);
|
|
66
|
+
await bundleServer(options, projectOpts);
|
|
67
67
|
if (!projectOpts.skipWranglerConfigCheck) {
|
|
68
68
|
await createWranglerConfigIfNotExistent(projectOpts);
|
|
69
69
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
|
|
2
|
+
import type { ProjectOptions } from "../project-options.js";
|
|
2
3
|
/**
|
|
3
4
|
* Bundle the Open Next server.
|
|
4
5
|
*/
|
|
5
|
-
export declare function bundleServer(buildOpts: BuildOptions): Promise<void>;
|
|
6
|
+
export declare function bundleServer(buildOpts: BuildOptions, projectOpts: ProjectOptions): Promise<void>;
|
|
6
7
|
/**
|
|
7
8
|
* This function apply updates to the bundled code.
|
|
8
9
|
*/
|
|
@@ -39,7 +39,7 @@ const optionalDependencies = [
|
|
|
39
39
|
/**
|
|
40
40
|
* Bundle the Open Next server.
|
|
41
41
|
*/
|
|
42
|
-
export async function bundleServer(buildOpts) {
|
|
42
|
+
export async function bundleServer(buildOpts, projectOpts) {
|
|
43
43
|
copyPackageCliFiles(packageDistDir, buildOpts);
|
|
44
44
|
const { appPath, outputDir, monorepoRoot, debug } = buildOpts;
|
|
45
45
|
const baseManifestPath = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next");
|
|
@@ -60,9 +60,9 @@ export async function bundleServer(buildOpts) {
|
|
|
60
60
|
format: "esm",
|
|
61
61
|
target: "esnext",
|
|
62
62
|
// Minify code as much as possible but stay safe by not renaming identifiers
|
|
63
|
-
minifyWhitespace: !debug,
|
|
63
|
+
minifyWhitespace: projectOpts.minify && !debug,
|
|
64
64
|
minifyIdentifiers: false,
|
|
65
|
-
minifySyntax: !debug,
|
|
65
|
+
minifySyntax: projectOpts.minify && !debug,
|
|
66
66
|
legalComments: "none",
|
|
67
67
|
metafile: true,
|
|
68
68
|
// Next traces files using the default conditions from `nft` (`node`, `require`, `import` and `default`)
|
|
@@ -154,6 +154,7 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
|
|
|
154
154
|
const isAfter141 = buildHelper.compareSemver(options.nextVersion, ">=", "14.1");
|
|
155
155
|
const isAfter142 = buildHelper.compareSemver(options.nextVersion, ">=", "14.2");
|
|
156
156
|
const isAfter152 = buildHelper.compareSemver(options.nextVersion, ">=", "15.2.0");
|
|
157
|
+
const isAfter154 = buildHelper.compareSemver(options.nextVersion, ">=", "15.4.0");
|
|
157
158
|
const disableRouting = isBefore13413 || config.middleware?.external;
|
|
158
159
|
const plugins = [
|
|
159
160
|
openNextReplacementPlugin({
|
|
@@ -164,6 +165,7 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
|
|
|
164
165
|
...(disableRouting ? ["withRouting"] : []),
|
|
165
166
|
...(isAfter142 ? ["patchAsyncStorage"] : []),
|
|
166
167
|
...(isAfter141 ? ["appendPrefetch"] : []),
|
|
168
|
+
...(isAfter154 ? [] : ["setInitialURL"]),
|
|
167
169
|
],
|
|
168
170
|
}),
|
|
169
171
|
openNextReplacementPlugin({
|
|
@@ -37,22 +37,18 @@ function getRequires(idVariable, files, serverDir) {
|
|
|
37
37
|
export function inlineDynamicRequires(updater, buildOpts) {
|
|
38
38
|
updater.updateContent("inline-node-module-loader", [
|
|
39
39
|
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
callback: async ({ contents }) => patchCode(contents, await getNodeModuleLoaderRule(buildOpts)),
|
|
46
|
-
},
|
|
40
|
+
filter: getCrossPlatformPathRegex(String.raw `/module-loader/node-module-loader\.js$`, {
|
|
41
|
+
escape: false,
|
|
42
|
+
}),
|
|
43
|
+
contentFilter: /class NodeModuleLoader {/,
|
|
44
|
+
callback: async ({ contents }) => patchCode(contents, await getNodeModuleLoaderRule(buildOpts)),
|
|
47
45
|
},
|
|
48
46
|
]);
|
|
49
47
|
updater.updateContent("inline-require-page", [
|
|
50
48
|
{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
callback: async ({ contents }) => patchCode(contents, await getRequirePageRule(buildOpts)),
|
|
55
|
-
},
|
|
49
|
+
filter: getCrossPlatformPathRegex(String.raw `/next/dist/server/require\.js$`, { escape: false }),
|
|
50
|
+
contentFilter: /function requirePage\(/,
|
|
51
|
+
callback: async ({ contents }) => patchCode(contents, await getRequirePageRule(buildOpts)),
|
|
56
52
|
},
|
|
57
53
|
]);
|
|
58
54
|
return { name: "inline-dynamic-requires", setup() { } };
|
|
@@ -9,11 +9,9 @@ import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
|
|
|
9
9
|
export function inlineFindDir(updater, buildOpts) {
|
|
10
10
|
return updater.updateContent("inline-find-dir", [
|
|
11
11
|
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
callback: async ({ contents }) => patchCode(contents, await getRule(buildOpts)),
|
|
16
|
-
},
|
|
12
|
+
filter: getCrossPlatformPathRegex(String.raw `/next/dist/lib/find-pages-dir\.js$`, { escape: false }),
|
|
13
|
+
contentFilter: /function findDir\(/,
|
|
14
|
+
callback: async ({ contents }) => patchCode(contents, await getRule(buildOpts)),
|
|
17
15
|
},
|
|
18
16
|
]);
|
|
19
17
|
}
|
|
@@ -8,31 +8,25 @@ export function patchInstrumentation(updater, buildOpts) {
|
|
|
8
8
|
const builtInstrumentationPath = getBuiltInstrumentationPath(buildOpts);
|
|
9
9
|
updater.updateContent("patch-instrumentation-next15-4", [
|
|
10
10
|
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
callback: ({ contents }) => patchCode(contents, getNext154Rule(builtInstrumentationPath)),
|
|
17
|
-
},
|
|
11
|
+
filter: getCrossPlatformPathRegex(String.raw `/server/lib/router-utils/instrumentation-globals.external\.js$`, {
|
|
12
|
+
escape: false,
|
|
13
|
+
}),
|
|
14
|
+
contentFilter: /getInstrumentationModule\(/,
|
|
15
|
+
callback: ({ contents }) => patchCode(contents, getNext154Rule(builtInstrumentationPath)),
|
|
18
16
|
},
|
|
19
17
|
]);
|
|
20
18
|
updater.updateContent("patch-instrumentation-next15", [
|
|
21
19
|
{
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
callback: ({ contents }) => patchCode(contents, getNext15Rule(builtInstrumentationPath)),
|
|
26
|
-
},
|
|
20
|
+
filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/,
|
|
21
|
+
contentFilter: /async loadInstrumentationModule\(/,
|
|
22
|
+
callback: ({ contents }) => patchCode(contents, getNext15Rule(builtInstrumentationPath)),
|
|
27
23
|
},
|
|
28
24
|
]);
|
|
29
25
|
updater.updateContent("patch-instrumentation-next14", [
|
|
30
26
|
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
callback: ({ contents }) => patchCode(contents, getNext14Rule(builtInstrumentationPath)),
|
|
35
|
-
},
|
|
27
|
+
filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/,
|
|
28
|
+
contentFilter: /async prepareImpl\(/,
|
|
29
|
+
callback: ({ contents }) => patchCode(contents, getNext14Rule(builtInstrumentationPath)),
|
|
36
30
|
},
|
|
37
31
|
]);
|
|
38
32
|
return {
|
|
@@ -13,16 +13,14 @@ import { normalizePath } from "../../utils/normalize-path.js";
|
|
|
13
13
|
export function inlineLoadManifest(updater, buildOpts) {
|
|
14
14
|
return updater.updateContent("inline-load-manifest", [
|
|
15
15
|
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return contents;
|
|
25
|
-
},
|
|
16
|
+
filter: getCrossPlatformPathRegex(String.raw `/next/dist/server/load-manifest(\.external)?\.js$`, {
|
|
17
|
+
escape: false,
|
|
18
|
+
}),
|
|
19
|
+
contentFilter: /function loadManifest\(/,
|
|
20
|
+
callback: async ({ contents }) => {
|
|
21
|
+
contents = await patchCode(contents, await getLoadManifestRule(buildOpts));
|
|
22
|
+
contents = await patchCode(contents, await getEvalManifestRule(buildOpts));
|
|
23
|
+
return contents;
|
|
26
24
|
},
|
|
27
25
|
},
|
|
28
26
|
]);
|
|
@@ -14,21 +14,19 @@ import { normalizePath } from "../../utils/index.js";
|
|
|
14
14
|
export function patchNextServer(updater, buildOpts) {
|
|
15
15
|
return updater.updateContent("next-server", [
|
|
16
16
|
{
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return contents;
|
|
31
|
-
},
|
|
17
|
+
filter: getCrossPlatformPathRegex(String.raw `/next/dist/server/next-server\.js$`, {
|
|
18
|
+
escape: false,
|
|
19
|
+
}),
|
|
20
|
+
contentFilter: /getBuildId\(/,
|
|
21
|
+
callback: async ({ contents }) => {
|
|
22
|
+
const { outputDir } = buildOpts;
|
|
23
|
+
contents = patchCode(contents, buildIdRule);
|
|
24
|
+
const outputPath = path.join(outputDir, "server-functions/default");
|
|
25
|
+
const cacheHandler = path.join(outputPath, getPackagePath(buildOpts), "cache.cjs");
|
|
26
|
+
contents = patchCode(contents, createCacheHandlerRule(cacheHandler));
|
|
27
|
+
const composableCacheHandler = path.join(outputPath, getPackagePath(buildOpts), "composable-cache.cjs");
|
|
28
|
+
contents = patchCode(contents, createComposableCacheHandlersRule(composableCacheHandler));
|
|
29
|
+
return contents;
|
|
32
30
|
},
|
|
33
31
|
},
|
|
34
32
|
]);
|
|
@@ -12,14 +12,12 @@ export function patchResolveCache(updater, buildOpts) {
|
|
|
12
12
|
const indexPath = path.relative(buildOpts.appBuildOutputPath, path.join(outputPath, packagePath, `index.mjs`));
|
|
13
13
|
return updater.updateContent("patch-resolve-cache", [
|
|
14
14
|
{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return contents;
|
|
22
|
-
},
|
|
15
|
+
filter: getCrossPlatformPathRegex(indexPath),
|
|
16
|
+
contentFilter: /cacheHandlerPath/,
|
|
17
|
+
callback: async ({ contents }) => {
|
|
18
|
+
contents = patchCode(contents, cacheHandlerRule);
|
|
19
|
+
contents = patchCode(contents, compositeCacheHandlerRule);
|
|
20
|
+
return contents;
|
|
23
21
|
},
|
|
24
22
|
},
|
|
25
23
|
]);
|
|
@@ -8,11 +8,9 @@ import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
|
|
|
8
8
|
export function patchDepdDeprecations(updater) {
|
|
9
9
|
return updater.updateContent("patch-depd-deprecations", [
|
|
10
10
|
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
callback: ({ contents }) => patchCode(contents, rule),
|
|
15
|
-
},
|
|
11
|
+
filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/,
|
|
12
|
+
contentFilter: /argument fn must be a function/,
|
|
13
|
+
callback: ({ contents }) => patchCode(contents, rule),
|
|
16
14
|
},
|
|
17
15
|
]);
|
|
18
16
|
}
|
|
@@ -1,44 +1,42 @@
|
|
|
1
1
|
export function fixRequire(updater) {
|
|
2
2
|
return updater.updateContent("fix-require", [
|
|
3
3
|
{
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return contents;
|
|
41
|
-
},
|
|
4
|
+
filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/,
|
|
5
|
+
contentFilter: /.*/,
|
|
6
|
+
callback: ({ contents }) => {
|
|
7
|
+
// `eval(...)` is not supported by workerd.
|
|
8
|
+
contents = contents.replaceAll(`eval("require")`, "require");
|
|
9
|
+
// `@opentelemetry` has a few issues.
|
|
10
|
+
//
|
|
11
|
+
// Next.js has the following code in `next/dist/server/lib/trace/tracer.js`:
|
|
12
|
+
//
|
|
13
|
+
// try {
|
|
14
|
+
// api = require('@opentelemetry/api');
|
|
15
|
+
// } catch (err) {
|
|
16
|
+
// api = require('next/dist/compiled/@opentelemetry/api');
|
|
17
|
+
// }
|
|
18
|
+
//
|
|
19
|
+
// The intent is to allow users to install their own version of `@opentelemetry/api`.
|
|
20
|
+
//
|
|
21
|
+
// The problem is that even when users do not explicitly install `@opentelemetry/api`,
|
|
22
|
+
// `require('@opentelemetry/api')` resolves to the package which is a dependency
|
|
23
|
+
// of Next.
|
|
24
|
+
//
|
|
25
|
+
// The second problem is that when Next traces files, it would not copy the `api/build/esm`
|
|
26
|
+
// folder (used by the `module` conditions in package.json) it would only copy `api/build/src`.
|
|
27
|
+
// This could be solved by updating the next config:
|
|
28
|
+
//
|
|
29
|
+
// const nextConfig: NextConfig = {
|
|
30
|
+
// // ...
|
|
31
|
+
// outputFileTracingIncludes: {
|
|
32
|
+
// "*": ["./node_modules/@opentelemetry/api/build/**/*"],
|
|
33
|
+
// },
|
|
34
|
+
// };
|
|
35
|
+
//
|
|
36
|
+
// We can consider doing that when we want to enable users to install their own version
|
|
37
|
+
// of `@opentelemetry/api`. For now we simply use the pre-compiled version.
|
|
38
|
+
contents = contents.replace(/require\(.@opentelemetry\/api.\)/g, `require("next/dist/compiled/@opentelemetry/api")`);
|
|
39
|
+
return contents;
|
|
42
40
|
},
|
|
43
41
|
},
|
|
44
42
|
]);
|
|
@@ -65,13 +65,11 @@ export const patchResRevalidate = {
|
|
|
65
65
|
patches: [
|
|
66
66
|
{
|
|
67
67
|
versions: ">=14.2.0",
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
patchCode: async ({ code }) => patchCode(code, rule),
|
|
74
|
-
},
|
|
68
|
+
pathFilter: getCrossPlatformPathRegex(String.raw `(pages-api\.runtime\.prod\.js|node/api-resolver\.js)$`, {
|
|
69
|
+
escape: false,
|
|
70
|
+
}),
|
|
71
|
+
contentFilter: /\.trustHostHeader/,
|
|
72
|
+
patchCode: async ({ code }) => patchCode(code, rule),
|
|
75
73
|
},
|
|
76
74
|
],
|
|
77
75
|
};
|
|
@@ -28,13 +28,11 @@ export const patchUseCacheIO = {
|
|
|
28
28
|
patches: [
|
|
29
29
|
{
|
|
30
30
|
versions: ">=15.3.1",
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
patchCode: async ({ code }) => patchCode(code, rule),
|
|
37
|
-
},
|
|
31
|
+
pathFilter: getCrossPlatformPathRegex(String.raw `server/app-render/async-local-storage\.js$`, {
|
|
32
|
+
escape: false,
|
|
33
|
+
}),
|
|
34
|
+
contentFilter: /createSnapshot/,
|
|
35
|
+
patchCode: async ({ code }) => patchCode(code, rule),
|
|
38
36
|
},
|
|
39
37
|
],
|
|
40
38
|
};
|
|
@@ -5,6 +5,8 @@ import logger from "@opennextjs/aws/logger.js";
|
|
|
5
5
|
* @param config OpenNext configuration.
|
|
6
6
|
*/
|
|
7
7
|
export function ensureCloudflareConfig(config) {
|
|
8
|
+
const mwIsMiddlewareExternal = config.middleware?.external === true;
|
|
9
|
+
const mwConfig = mwIsMiddlewareExternal ? config.middleware : undefined;
|
|
8
10
|
const requirements = {
|
|
9
11
|
// Check for the default function
|
|
10
12
|
dftUseCloudflareWrapper: config.default?.override?.wrapper === "cloudflare-node",
|
|
@@ -18,10 +20,10 @@ export function ensureCloudflareConfig(config) {
|
|
|
18
20
|
config.default?.override?.queue === "direct" ||
|
|
19
21
|
typeof config.default?.override?.queue === "function",
|
|
20
22
|
// Check for the middleware function
|
|
21
|
-
mwIsMiddlewareExternal
|
|
22
|
-
mwUseCloudflareWrapper:
|
|
23
|
-
mwUseEdgeConverter:
|
|
24
|
-
mwUseFetchProxy:
|
|
23
|
+
mwIsMiddlewareExternal,
|
|
24
|
+
mwUseCloudflareWrapper: mwConfig?.override?.wrapper === "cloudflare-edge",
|
|
25
|
+
mwUseEdgeConverter: mwConfig?.override?.converter === "edge",
|
|
26
|
+
mwUseFetchProxy: mwConfig?.override?.proxyExternalRequest === "fetch",
|
|
25
27
|
hasCryptoExternal: config.edgeExternals?.includes("node:crypto"),
|
|
26
28
|
};
|
|
27
29
|
if (config.default?.override?.queue === "direct") {
|
|
@@ -28,10 +28,10 @@ import type { WorkerEnvVar } from "./helpers.js";
|
|
|
28
28
|
*
|
|
29
29
|
* @param options Build options
|
|
30
30
|
* @param config OpenNext config
|
|
31
|
-
* @param
|
|
31
|
+
* @param workerEnvVars Worker Environment variables (taken from the wrangler config files)
|
|
32
32
|
* @returns Deployment mapping or undefined
|
|
33
33
|
*/
|
|
34
|
-
export declare function getDeploymentMapping(options: BuildOptions, config: OpenNextConfig,
|
|
34
|
+
export declare function getDeploymentMapping(options: BuildOptions, config: OpenNextConfig, workerEnvVars: WorkerEnvVar): Promise<Record<string, string> | undefined>;
|
|
35
35
|
/**
|
|
36
36
|
* Update an existing deployment mapping:
|
|
37
37
|
* - Replace the "current" version with the latest deployed version
|
|
@@ -36,13 +36,17 @@ const MS_PER_DAY = 24 * 3600 * 1000;
|
|
|
36
36
|
*
|
|
37
37
|
* @param options Build options
|
|
38
38
|
* @param config OpenNext config
|
|
39
|
-
* @param
|
|
39
|
+
* @param workerEnvVars Worker Environment variables (taken from the wrangler config files)
|
|
40
40
|
* @returns Deployment mapping or undefined
|
|
41
41
|
*/
|
|
42
|
-
export async function getDeploymentMapping(options, config,
|
|
42
|
+
export async function getDeploymentMapping(options, config, workerEnvVars) {
|
|
43
43
|
if (config.cloudflare?.skewProtection?.enabled !== true) {
|
|
44
44
|
return undefined;
|
|
45
45
|
}
|
|
46
|
+
// Note that `process.env` is spread after `workerEnvVars` since we do want
|
|
47
|
+
// system environment variables to take precedence over the variables defined
|
|
48
|
+
// in the wrangler config files
|
|
49
|
+
const envVars = { ...workerEnvVars, ...process.env };
|
|
46
50
|
const nextConfig = loadConfig(path.join(options.appBuildOutputPath, ".next"));
|
|
47
51
|
const deploymentId = nextConfig.deploymentId;
|
|
48
52
|
if (!deploymentId) {
|
|
@@ -26,7 +26,7 @@ export declare function matchLocalPattern(pattern: LocalPattern, url: URL): bool
|
|
|
26
26
|
* @param buffer The image bytes
|
|
27
27
|
* @returns a content type of undefined for unsupported content
|
|
28
28
|
*/
|
|
29
|
-
export declare function detectContentType(buffer: Uint8Array): "image/svg+xml" | "image/jpeg" | "image/png" | "image/gif" | "image/webp" | "image/avif" | "image/x-icon" | "image/x-icns" | "image/tiff" | "image/bmp" | undefined;
|
|
29
|
+
export declare function detectContentType(buffer: Uint8Array): "image/svg+xml" | "image/jpeg" | "image/png" | "image/gif" | "image/webp" | "image/avif" | "image/x-icon" | "image/x-icns" | "image/tiff" | "image/bmp" | "image/jxl" | "image/heic" | "application/pdf" | "image/jp2" | undefined;
|
|
30
30
|
declare global {
|
|
31
31
|
var __IMAGES_REMOTE_PATTERNS__: RemotePattern[];
|
|
32
32
|
var __IMAGES_LOCAL_PATTERNS__: LocalPattern[];
|
|
@@ -65,12 +65,15 @@ export async function fetchImage(fetcher, imageUrl, ctx) {
|
|
|
65
65
|
contentType = detectContentType(value);
|
|
66
66
|
}
|
|
67
67
|
if (!contentType) {
|
|
68
|
-
// Fallback to
|
|
68
|
+
// Fallback to upstream header when the type can not be detected
|
|
69
69
|
// https://github.com/vercel/next.js/blob/d76f0b1/packages/next/src/server/image-optimizer.ts#L748
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
contentType = imgResponse.headers.get("content-type") ?? "";
|
|
71
|
+
}
|
|
72
|
+
// Sanitize the content type:
|
|
73
|
+
// - Accept images only
|
|
74
|
+
// - Reject multiple content types
|
|
75
|
+
if (!contentType.startsWith("image/") || contentType.includes(",")) {
|
|
76
|
+
contentType = undefined;
|
|
74
77
|
}
|
|
75
78
|
if (contentType && !(contentType === SVG && !__IMAGES_ALLOW_SVG__)) {
|
|
76
79
|
const headers = new Headers(imgResponse.headers);
|
|
@@ -126,12 +129,17 @@ const AVIF = "image/avif";
|
|
|
126
129
|
const WEBP = "image/webp";
|
|
127
130
|
const PNG = "image/png";
|
|
128
131
|
const JPEG = "image/jpeg";
|
|
132
|
+
const JXL = "image/jxl";
|
|
133
|
+
const JP2 = "image/jp2";
|
|
134
|
+
const HEIC = "image/heic";
|
|
129
135
|
const GIF = "image/gif";
|
|
130
136
|
const SVG = "image/svg+xml";
|
|
131
137
|
const ICO = "image/x-icon";
|
|
132
138
|
const ICNS = "image/x-icns";
|
|
133
139
|
const TIFF = "image/tiff";
|
|
134
140
|
const BMP = "image/bmp";
|
|
141
|
+
// pdf will be rejected (not an `image/...` type)
|
|
142
|
+
const PDF = "application/pdf";
|
|
135
143
|
/**
|
|
136
144
|
* Detects the content type by looking at the first few bytes of a file
|
|
137
145
|
*
|
|
@@ -174,4 +182,19 @@ export function detectContentType(buffer) {
|
|
|
174
182
|
if ([0x42, 0x4d].every((b, i) => buffer[i] === b)) {
|
|
175
183
|
return BMP;
|
|
176
184
|
}
|
|
185
|
+
if ([0xff, 0x0a].every((b, i) => buffer[i] === b)) {
|
|
186
|
+
return JXL;
|
|
187
|
+
}
|
|
188
|
+
if ([0x00, 0x00, 0x00, 0x0c, 0x4a, 0x58, 0x4c, 0x20, 0x0d, 0x0a, 0x87, 0x0a].every((b, i) => buffer[i] === b)) {
|
|
189
|
+
return JXL;
|
|
190
|
+
}
|
|
191
|
+
if ([0, 0, 0, 0, 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63].every((b, i) => !b || buffer[i] === b)) {
|
|
192
|
+
return HEIC;
|
|
193
|
+
}
|
|
194
|
+
if ([0x25, 0x50, 0x44, 0x46, 0x2d].every((b, i) => buffer[i] === b)) {
|
|
195
|
+
return PDF;
|
|
196
|
+
}
|
|
197
|
+
if ([0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a].every((b, i) => buffer[i] === b)) {
|
|
198
|
+
return JP2;
|
|
199
|
+
}
|
|
177
200
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opennextjs/cloudflare",
|
|
3
3
|
"description": "Cloudflare builder for next apps",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"opennextjs-cloudflare": "dist/cli/index.js"
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"homepage": "https://github.com/opennextjs/opennextjs-cloudflare",
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@dotenvx/dotenvx": "1.31.0",
|
|
46
|
-
"@opennextjs/aws": "3.7.
|
|
46
|
+
"@opennextjs/aws": "3.7.2",
|
|
47
47
|
"cloudflare": "^4.4.1",
|
|
48
48
|
"enquirer": "^2.4.1",
|
|
49
49
|
"glob": "^11.0.0",
|