@opennextjs/cloudflare 1.6.2 → 1.6.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.
@@ -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
  }
@@ -74,11 +74,4 @@ function ensureNextjsVersionSupported(options) {
74
74
  logger.error("Next.js version unsupported, please upgrade to version 14.2 or greater.");
75
75
  process.exit(1);
76
76
  }
77
- // TODO: remove when 15.4 is supported
78
- // Note: `e2e/experimental` is on 15.4.0-canary.14 which works
79
- if (!options.appPath.endsWith("opennextjs-cloudflare/examples/e2e/experimental") &&
80
- buildHelper.compareSemver(options.nextVersion, ">=", "15.4.0")) {
81
- logger.error("Next.js version unsupported, the latest supported version is 15.3");
82
- process.exit(1);
83
- }
84
77
  }
@@ -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
  */
@@ -19,6 +19,7 @@ import { patchPagesRouterContext } from "./patches/plugins/pages-router-context.
19
19
  import { patchDepdDeprecations } from "./patches/plugins/patch-depd-deprecations.js";
20
20
  import { fixRequire } from "./patches/plugins/require.js";
21
21
  import { shimRequireHook } from "./patches/plugins/require-hook.js";
22
+ import { patchRouteModules } from "./patches/plugins/route-module.js";
22
23
  import { setWranglerExternal } from "./patches/plugins/wrangler-external.js";
23
24
  import { copyPackageCliFiles, needsExperimentalReact, normalizePath } from "./utils/index.js";
24
25
  /** The dist directory of the Cloudflare adapter package */
@@ -39,7 +40,7 @@ const optionalDependencies = [
39
40
  /**
40
41
  * Bundle the Open Next server.
41
42
  */
42
- export async function bundleServer(buildOpts) {
43
+ export async function bundleServer(buildOpts, projectOpts) {
43
44
  copyPackageCliFiles(packageDistDir, buildOpts);
44
45
  const { appPath, outputDir, monorepoRoot, debug } = buildOpts;
45
46
  const baseManifestPath = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next");
@@ -60,9 +61,9 @@ export async function bundleServer(buildOpts) {
60
61
  format: "esm",
61
62
  target: "esnext",
62
63
  // Minify code as much as possible but stay safe by not renaming identifiers
63
- minifyWhitespace: !debug,
64
+ minifyWhitespace: projectOpts.minify && !debug,
64
65
  minifyIdentifiers: false,
65
- minifySyntax: !debug,
66
+ minifySyntax: projectOpts.minify && !debug,
66
67
  legalComments: "none",
67
68
  metafile: true,
68
69
  // Next traces files using the default conditions from `nft` (`node`, `require`, `import` and `default`)
@@ -86,6 +87,7 @@ export async function bundleServer(buildOpts) {
86
87
  inlineFindDir(updater, buildOpts),
87
88
  inlineLoadManifest(updater, buildOpts),
88
89
  patchNextServer(updater, buildOpts),
90
+ patchRouteModules(updater, buildOpts),
89
91
  patchDepdDeprecations(updater),
90
92
  patchResolveCache(updater, buildOpts),
91
93
  // Apply updater updates, must be the last plugin
@@ -124,6 +126,8 @@ export async function bundleServer(buildOpts) {
124
126
  "process.env.TURBOPACK": "false",
125
127
  // This define should be safe to use for Next 14.2+, earlier versions (13.5 and less) will cause trouble
126
128
  "process.env.__NEXT_EXPERIMENTAL_REACT": `${needsExperimentalReact(nextConfig)}`,
129
+ // Fix `res.validate` in Next 15.4 (together with the `route-module` patch)
130
+ "process.env.__NEXT_TRUST_HOST_HEADER": "true",
127
131
  },
128
132
  banner: {
129
133
  // We need to import them here, assigning them to `globalThis` does not work because node:timers use `globalThis` and thus create an infinite loop
@@ -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
- field: {
41
- filter: getCrossPlatformPathRegex(String.raw `/module-loader/node-module-loader\.js$`, {
42
- escape: false,
43
- }),
44
- contentFilter: /class NodeModuleLoader {/,
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
- field: {
52
- filter: getCrossPlatformPathRegex(String.raw `/next/dist/server/require\.js$`, { escape: false }),
53
- contentFilter: /function requirePage\(/,
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
- field: {
13
- filter: getCrossPlatformPathRegex(String.raw `/next/dist/lib/find-pages-dir\.js$`, { escape: false }),
14
- contentFilter: /function findDir\(/,
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
- field: {
12
- filter: getCrossPlatformPathRegex(String.raw `/server/lib/router-utils/instrumentation-globals.external\.js$`, {
13
- escape: false,
14
- }),
15
- contentFilter: /getInstrumentationModule\(/,
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
- field: {
23
- filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/,
24
- contentFilter: /async loadInstrumentationModule\(/,
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
- field: {
32
- filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/,
33
- contentFilter: /async prepareImpl\(/,
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
- field: {
17
- filter: getCrossPlatformPathRegex(String.raw `/next/dist/server/load-manifest(\.external)?\.js$`, {
18
- escape: false,
19
- }),
20
- contentFilter: /function loadManifest\(/,
21
- callback: async ({ contents }) => {
22
- contents = await patchCode(contents, await getLoadManifestRule(buildOpts));
23
- contents = await patchCode(contents, await getEvalManifestRule(buildOpts));
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
  ]);
@@ -48,8 +46,8 @@ function loadManifest($PATH, $$$ARGS) {
48
46
  fix: `
49
47
  function loadManifest($PATH, $$$ARGS) {
50
48
  $PATH = $PATH.replaceAll(${JSON.stringify(sep)}, ${JSON.stringify(posix.sep)});
51
- if ($PATH === "/.next/BUILD_ID") {
52
- return process.env.NEXT_BUILD_ID;
49
+ if ($PATH.endsWith(".next/BUILD_ID")) {
50
+ return process.env.NEXT_BUILD_ID;
53
51
  }
54
52
  ${returnManifests}
55
53
  throw new Error(\`Unexpected loadManifest(\${$PATH}) call!\`);
@@ -9,6 +9,7 @@
9
9
  import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
10
10
  import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater.js";
11
11
  export declare function patchNextServer(updater: ContentUpdater, buildOpts: BuildOptions): Plugin;
12
+ export declare const disableNodeMiddlewareRule = "\nrule:\n pattern:\n selector: method_definition\n context: \"class { async loadNodeMiddleware($$$PARAMS) { $$$_ } }\"\nfix: |-\n async loadNodeMiddleware($$$PARAMS) {\n // patched by open next\n }\n";
12
13
  export declare const buildIdRule = "\nrule:\n pattern:\n selector: method_definition\n context: \"class { getBuildId($$$PARAMS) { $$$_ } }\"\nfix: |-\n getBuildId($$$PARAMS) {\n return process.env.NEXT_BUILD_ID;\n }\n";
13
14
  /**
14
15
  * The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
@@ -14,25 +14,36 @@ import { normalizePath } from "../../utils/index.js";
14
14
  export function patchNextServer(updater, buildOpts) {
15
15
  return updater.updateContent("next-server", [
16
16
  {
17
- field: {
18
- filter: getCrossPlatformPathRegex(String.raw `/next/dist/server/next-server\.js$`, {
19
- escape: false,
20
- }),
21
- contentFilter: /getBuildId\(/,
22
- callback: async ({ contents }) => {
23
- const { outputDir } = buildOpts;
24
- contents = patchCode(contents, buildIdRule);
25
- const outputPath = path.join(outputDir, "server-functions/default");
26
- const cacheHandler = path.join(outputPath, getPackagePath(buildOpts), "cache.cjs");
27
- contents = patchCode(contents, createCacheHandlerRule(cacheHandler));
28
- const composableCacheHandler = path.join(outputPath, getPackagePath(buildOpts), "composable-cache.cjs");
29
- contents = patchCode(contents, createComposableCacheHandlersRule(composableCacheHandler));
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
+ // Node middleware are not supported on Cloudflare yet
30
+ contents = patchCode(contents, disableNodeMiddlewareRule);
31
+ return contents;
32
32
  },
33
33
  },
34
34
  ]);
35
35
  }
36
+ // Do not try to load Node middlewares
37
+ export const disableNodeMiddlewareRule = `
38
+ rule:
39
+ pattern:
40
+ selector: method_definition
41
+ context: "class { async loadNodeMiddleware($$$PARAMS) { $$$_ } }"
42
+ fix: |-
43
+ async loadNodeMiddleware($$$PARAMS) {
44
+ // patched by open next
45
+ }
46
+ `;
36
47
  export const buildIdRule = `
37
48
  rule:
38
49
  pattern:
@@ -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
- field: {
16
- filter: getCrossPlatformPathRegex(indexPath),
17
- contentFilter: /cacheHandlerPath/,
18
- callback: async ({ contents }) => {
19
- contents = patchCode(contents, cacheHandlerRule);
20
- contents = patchCode(contents, compositeCacheHandlerRule);
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
- field: {
12
- filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/,
13
- contentFilter: /argument fn must be a function/,
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
- field: {
5
- filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/,
6
- contentFilter: /.*/,
7
- callback: ({ contents }) => {
8
- // `eval(...)` is not supported by workerd.
9
- contents = contents.replaceAll(`eval("require")`, "require");
10
- // `@opentelemetry` has a few issues.
11
- //
12
- // Next.js has the following code in `next/dist/server/lib/trace/tracer.js`:
13
- //
14
- // try {
15
- // api = require('@opentelemetry/api');
16
- // } catch (err) {
17
- // api = require('next/dist/compiled/@opentelemetry/api');
18
- // }
19
- //
20
- // The intent is to allow users to install their own version of `@opentelemetry/api`.
21
- //
22
- // The problem is that even when users do not explicitly install `@opentelemetry/api`,
23
- // `require('@opentelemetry/api')` resolves to the package which is a dependency
24
- // of Next.
25
- //
26
- // The second problem is that when Next traces files, it would not copy the `api/build/esm`
27
- // folder (used by the `module` conditions in package.json) it would only copy `api/build/src`.
28
- // This could be solved by updating the next config:
29
- //
30
- // const nextConfig: NextConfig = {
31
- // // ...
32
- // outputFileTracingIncludes: {
33
- // "*": ["./node_modules/@opentelemetry/api/build/**/*"],
34
- // },
35
- // };
36
- //
37
- // We can consider doing that when we want to enable users to install their own version
38
- // of `@opentelemetry/api`. For now we simply use the pre-compiled version.
39
- contents = contents.replace(/require\(.@opentelemetry\/api.\)/g, `require("next/dist/compiled/@opentelemetry/api")`);
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
- field: {
69
- pathFilter: getCrossPlatformPathRegex(String.raw `(pages-api\.runtime\.prod\.js|node/api-resolver\.js)$`, {
70
- escape: false,
71
- }),
72
- contentFilter: /\.trustHostHeader/,
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
  };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Patch for `next/src/server/route-modules/route-module.ts`
3
+ * https://github.com/vercel/next.js/blob/c8c9bef/packages/next/src/server/route-modules/route-module.ts#L389-L437
4
+ *
5
+ * Patch getIncrementalCache to use a string literal for the cache handler path
6
+ *
7
+ */
8
+ import { BuildOptions } from "@opennextjs/aws/build/helper.js";
9
+ import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater.js";
10
+ export declare function patchRouteModules(updater: ContentUpdater, buildOpts: BuildOptions): Plugin;
11
+ /**
12
+ * The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
13
+ * Next.js would then do a dynamic require on a transformed version of the path to retrieve the
14
+ * cache handler and create a new instance of it.
15
+ *
16
+ * This is problematic in workerd due to the dynamic import of the file that is not known from
17
+ * build-time. Therefore, we have to manually override the default way that the cache handler is
18
+ * instantiated with a dynamic require that uses a string literal for the path.
19
+ */
20
+ export declare function getIncrementalCacheRule(handlerPath: string): string;
21
+ /**
22
+ * Force trustHostHeader to be true for revalidation
23
+ */
24
+ export declare const forceTrustHostHeader = "\nrule:\n pattern: async function $FN($$$ARGS) { $$$BODY }\n all:\n - has:\n pattern: if ($CONTEXT.trustHostHeader) { $$$_ }\n stopBy: end\n - has:\n regex: \"^x-vercel-protection-bypass$\"\n stopBy: end\n - has:\n regex: \"Invariant: missing internal\"\n stopBy: end\nfix: |-\n async function $FN($$$ARGS) {\n $CONTEXT.trustHostHeader = true;\n $$$BODY\n }\n";
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Patch for `next/src/server/route-modules/route-module.ts`
3
+ * https://github.com/vercel/next.js/blob/c8c9bef/packages/next/src/server/route-modules/route-module.ts#L389-L437
4
+ *
5
+ * Patch getIncrementalCache to use a string literal for the cache handler path
6
+ *
7
+ */
8
+ import path from "node:path";
9
+ import { getPackagePath } from "@opennextjs/aws/build/helper.js";
10
+ import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
11
+ import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
12
+ import { normalizePath } from "../../utils/index.js";
13
+ export function patchRouteModules(updater, buildOpts) {
14
+ return updater.updateContent("route-module", [
15
+ {
16
+ filter: getCrossPlatformPathRegex(String.raw `/next/dist/compiled/next-server/.*?\.runtime\.prod\.js$`, {
17
+ escape: false,
18
+ }),
19
+ versions: ">=15.4.0",
20
+ contentFilter: /getIncrementalCache\(/,
21
+ callback: async ({ contents }) => {
22
+ const { outputDir } = buildOpts;
23
+ const outputPath = path.join(outputDir, "server-functions/default");
24
+ const cacheHandler = path.join(outputPath, getPackagePath(buildOpts), "cache.cjs");
25
+ contents = patchCode(contents, getIncrementalCacheRule(cacheHandler));
26
+ contents = patchCode(contents, forceTrustHostHeader);
27
+ return contents;
28
+ },
29
+ },
30
+ ]);
31
+ }
32
+ /**
33
+ * The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
34
+ * Next.js would then do a dynamic require on a transformed version of the path to retrieve the
35
+ * cache handler and create a new instance of it.
36
+ *
37
+ * This is problematic in workerd due to the dynamic import of the file that is not known from
38
+ * build-time. Therefore, we have to manually override the default way that the cache handler is
39
+ * instantiated with a dynamic require that uses a string literal for the path.
40
+ */
41
+ export function getIncrementalCacheRule(handlerPath) {
42
+ return `
43
+ rule:
44
+ pattern: "let $CACHE_HANDLER, { cacheHandler: $HANDLER_PATH } = $C"
45
+ inside:
46
+ kind: method_definition
47
+ has:
48
+ field: name
49
+ regex: ^getIncrementalCache$
50
+ stopBy: end
51
+ fix: |-
52
+ const $HANDLER_PATH = null;
53
+ let $CACHE_HANDLER = require('${normalizePath(handlerPath)}').default;
54
+ `;
55
+ }
56
+ /**
57
+ * Force trustHostHeader to be true for revalidation
58
+ */
59
+ export const forceTrustHostHeader = `
60
+ rule:
61
+ pattern: async function $FN($$$ARGS) { $$$BODY }
62
+ all:
63
+ - has:
64
+ pattern: if ($CONTEXT.trustHostHeader) { $$$_ }
65
+ stopBy: end
66
+ - has:
67
+ regex: "^x-vercel-protection-bypass$"
68
+ stopBy: end
69
+ - has:
70
+ regex: "Invariant: missing internal"
71
+ stopBy: end
72
+ fix: |-
73
+ async function $FN($$$ARGS) {
74
+ $CONTEXT.trustHostHeader = true;
75
+ $$$BODY
76
+ }
77
+ `;
@@ -28,13 +28,11 @@ export const patchUseCacheIO = {
28
28
  patches: [
29
29
  {
30
30
  versions: ">=15.3.1",
31
- field: {
32
- pathFilter: getCrossPlatformPathRegex(String.raw `server/app-render/async-local-storage\.js$`, {
33
- escape: false,
34
- }),
35
- contentFilter: /createSnapshot/,
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
  };
@@ -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 the sanitized upstream header when the type can not be detected
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
- const header = imgResponse.headers.get("content-type") ?? "";
71
- if (header.startsWith("image/") && !header.includes(",")) {
72
- contentType = header;
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.2",
4
+ "version": "1.6.4",
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.1",
46
+ "@opennextjs/aws": "3.7.4",
47
47
  "cloudflare": "^4.4.1",
48
48
  "enquirer": "^2.4.1",
49
49
  "glob": "^11.0.0",