@opennextjs/cloudflare 1.3.0 → 1.3.1
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/cli/args.d.ts +2 -0
- package/dist/cli/args.js +37 -19
- package/dist/cli/build/build.d.ts +2 -2
- package/dist/cli/build/build.js +3 -0
- package/dist/cli/build/open-next/compile-images.d.ts +5 -0
- package/dist/cli/build/open-next/compile-images.js +29 -0
- package/dist/cli/build/open-next/compile-init.js +0 -21
- package/dist/cli/build/open-next/createServerBundle.js +0 -1
- package/dist/cli/build/patches/plugins/next-server.d.ts +0 -2
- package/dist/cli/build/patches/plugins/next-server.js +0 -19
- package/dist/cli/build/utils/workerd.js +3 -1
- package/dist/cli/templates/images.d.ts +24 -0
- package/dist/cli/templates/images.js +82 -0
- package/dist/cli/templates/init.d.ts +0 -17
- package/dist/cli/templates/init.js +0 -76
- package/dist/cli/templates/worker.js +3 -1
- package/package.json +2 -2
- package/dist/api/durable-objects/bucket-cache-purge.spec.d.ts +0 -1
- package/dist/api/durable-objects/bucket-cache-purge.spec.js +0 -121
- package/dist/api/durable-objects/queue.spec.d.ts +0 -1
- package/dist/api/durable-objects/queue.spec.js +0 -287
- package/dist/api/durable-objects/sharded-tag-cache.spec.d.ts +0 -1
- package/dist/api/durable-objects/sharded-tag-cache.spec.js +0 -37
- package/dist/api/overrides/queue/memory-queue.spec.d.ts +0 -1
- package/dist/api/overrides/queue/memory-queue.spec.js +0 -76
- package/dist/api/overrides/queue/queue-cache.spec.d.ts +0 -1
- package/dist/api/overrides/queue/queue-cache.spec.js +0 -92
- package/dist/api/overrides/tag-cache/do-sharded-tag-cache.spec.d.ts +0 -1
- package/dist/api/overrides/tag-cache/do-sharded-tag-cache.spec.js +0 -413
- package/dist/api/overrides/tag-cache/tag-cache-filter.spec.d.ts +0 -1
- package/dist/api/overrides/tag-cache/tag-cache-filter.spec.js +0 -97
- package/dist/cli/build/patches/ast/patch-vercel-og-library.spec.d.ts +0 -1
- package/dist/cli/build/patches/ast/patch-vercel-og-library.spec.js +0 -50
- package/dist/cli/build/patches/ast/vercel-og.spec.d.ts +0 -1
- package/dist/cli/build/patches/ast/vercel-og.spec.js +0 -22
- package/dist/cli/build/patches/ast/webpack-runtime.spec.d.ts +0 -1
- package/dist/cli/build/patches/ast/webpack-runtime.spec.js +0 -102
- package/dist/cli/build/patches/plugins/instrumentation.spec.d.ts +0 -1
- package/dist/cli/build/patches/plugins/instrumentation.spec.js +0 -91
- package/dist/cli/build/patches/plugins/next-server.spec.d.ts +0 -1
- package/dist/cli/build/patches/plugins/next-server.spec.js +0 -216
- package/dist/cli/build/patches/plugins/patch-depd-deprecations.spec.d.ts +0 -1
- package/dist/cli/build/patches/plugins/patch-depd-deprecations.spec.js +0 -29
- package/dist/cli/build/patches/plugins/res-revalidate.spec.d.ts +0 -1
- package/dist/cli/build/patches/plugins/res-revalidate.spec.js +0 -99
- package/dist/cli/build/patches/plugins/use-cache.spec.d.ts +0 -1
- package/dist/cli/build/patches/plugins/use-cache.spec.js +0 -101
- package/dist/cli/build/utils/extract-project-env-vars.spec.d.ts +0 -1
- package/dist/cli/build/utils/extract-project-env-vars.spec.js +0 -67
- package/dist/cli/build/utils/workerd.spec.d.ts +0 -1
- package/dist/cli/build/utils/workerd.spec.js +0 -188
- package/dist/cli/commands/populate-cache.spec.d.ts +0 -1
- package/dist/cli/commands/populate-cache.spec.js +0 -61
package/dist/cli/args.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ParseArgsConfig } from "node:util";
|
|
1
2
|
import type { WranglerTarget } from "./utils/run-wrangler.js";
|
|
2
3
|
export type Arguments = ({
|
|
3
4
|
command: "build";
|
|
@@ -17,3 +18,4 @@ export type Arguments = ({
|
|
|
17
18
|
outputDir?: string;
|
|
18
19
|
};
|
|
19
20
|
export declare function getArgs(): Arguments;
|
|
21
|
+
export declare function getPassthroughArgs<T extends ParseArgsConfig>(args: string[], { options }: T): string[];
|
package/dist/cli/args.js
CHANGED
|
@@ -2,28 +2,30 @@ import { mkdirSync, statSync } from "node:fs";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { getWranglerEnvironmentFlag, isWranglerTarget } from "./utils/run-wrangler.js";
|
|
5
|
+
// Config for parsing CLI arguments
|
|
6
|
+
const config = {
|
|
7
|
+
allowPositionals: true,
|
|
8
|
+
strict: false,
|
|
9
|
+
options: {
|
|
10
|
+
skipBuild: { type: "boolean", short: "s", default: false },
|
|
11
|
+
output: { type: "string", short: "o" },
|
|
12
|
+
noMinify: { type: "boolean", default: false },
|
|
13
|
+
skipWranglerConfigCheck: { type: "boolean", default: false },
|
|
14
|
+
cacheChunkSize: { type: "string" },
|
|
15
|
+
},
|
|
16
|
+
};
|
|
5
17
|
export function getArgs() {
|
|
6
|
-
const { positionals, values } = parseArgs(
|
|
7
|
-
|
|
8
|
-
skipBuild: { type: "boolean", short: "s", default: false },
|
|
9
|
-
output: { type: "string", short: "o" },
|
|
10
|
-
noMinify: { type: "boolean", default: false },
|
|
11
|
-
skipWranglerConfigCheck: { type: "boolean", default: false },
|
|
12
|
-
cacheChunkSize: { type: "string" },
|
|
13
|
-
},
|
|
14
|
-
allowPositionals: true,
|
|
15
|
-
});
|
|
16
|
-
const outputDir = values.output ? resolve(values.output) : undefined;
|
|
18
|
+
const { positionals, values } = parseArgs(config);
|
|
19
|
+
const outputDir = typeof values.output === "string" ? resolve(values.output) : undefined;
|
|
17
20
|
if (outputDir)
|
|
18
21
|
assertDirArg(outputDir, "output", true);
|
|
19
|
-
const passthroughArgs = getPassthroughArgs();
|
|
20
22
|
switch (positionals[0]) {
|
|
21
23
|
case "build":
|
|
22
24
|
return {
|
|
23
25
|
command: "build",
|
|
24
26
|
outputDir,
|
|
25
|
-
skipNextBuild: values.skipBuild || ["1", "true", "yes"].includes(String(process.env.SKIP_NEXT_APP_BUILD)),
|
|
26
|
-
skipWranglerConfigCheck: values.skipWranglerConfigCheck ||
|
|
27
|
+
skipNextBuild: !!values.skipBuild || ["1", "true", "yes"].includes(String(process.env.SKIP_NEXT_APP_BUILD)),
|
|
28
|
+
skipWranglerConfigCheck: !!values.skipWranglerConfigCheck ||
|
|
27
29
|
["1", "true", "yes"].includes(String(process.env.SKIP_WRANGLER_CONFIG_CHECK)),
|
|
28
30
|
minify: !values.noMinify,
|
|
29
31
|
};
|
|
@@ -33,7 +35,7 @@ export function getArgs() {
|
|
|
33
35
|
return {
|
|
34
36
|
command: positionals[0],
|
|
35
37
|
outputDir,
|
|
36
|
-
passthroughArgs,
|
|
38
|
+
passthroughArgs: getPassthroughArgs(process.argv, config),
|
|
37
39
|
...(values.cacheChunkSize && { cacheChunkSize: Number(values.cacheChunkSize) }),
|
|
38
40
|
};
|
|
39
41
|
case "populateCache":
|
|
@@ -44,16 +46,32 @@ export function getArgs() {
|
|
|
44
46
|
command: "populateCache",
|
|
45
47
|
outputDir,
|
|
46
48
|
target: positionals[1],
|
|
47
|
-
environment: getWranglerEnvironmentFlag(
|
|
49
|
+
environment: getWranglerEnvironmentFlag(process.argv),
|
|
48
50
|
...(values.cacheChunkSize && { cacheChunkSize: Number(values.cacheChunkSize) }),
|
|
49
51
|
};
|
|
50
52
|
default:
|
|
51
53
|
throw new Error("Error: invalid command, expected 'build' | 'preview' | 'deploy' | 'upload' | 'populateCache'");
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
|
-
function getPassthroughArgs() {
|
|
55
|
-
const
|
|
56
|
-
|
|
56
|
+
export function getPassthroughArgs(args, { options = {} }) {
|
|
57
|
+
const passthroughArgs = [];
|
|
58
|
+
for (let i = 0; i < args.length; i++) {
|
|
59
|
+
if (args[i] === "--") {
|
|
60
|
+
passthroughArgs.push(...args.slice(i + 1));
|
|
61
|
+
return passthroughArgs;
|
|
62
|
+
}
|
|
63
|
+
// look for `--arg(=value)`, `-arg(=value)`
|
|
64
|
+
const [, name] = /^--?(\w[\w-]*)(=.+)?$/.exec(args[i]) ?? [];
|
|
65
|
+
if (name && !(name in options)) {
|
|
66
|
+
passthroughArgs.push(args[i]);
|
|
67
|
+
// Array args can have multiple values
|
|
68
|
+
// ref https://github.com/yargs/yargs-parser/blob/main/README.md#greedy-arrays
|
|
69
|
+
while (i < args.length - 1 && !args[i + 1]?.startsWith("-")) {
|
|
70
|
+
passthroughArgs.push(args[++i]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return passthroughArgs;
|
|
57
75
|
}
|
|
58
76
|
function assertDirArg(path, argName, make) {
|
|
59
77
|
let dirStats;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as buildHelper from "@opennextjs/aws/build/helper.js";
|
|
2
2
|
import { OpenNextConfig } from "../../api/config.js";
|
|
3
3
|
import type { ProjectOptions } from "../project-options.js";
|
|
4
4
|
/**
|
|
@@ -10,4 +10,4 @@ import type { ProjectOptions } from "../project-options.js";
|
|
|
10
10
|
* @param config The OpenNext config
|
|
11
11
|
* @param projectOpts The options for the project
|
|
12
12
|
*/
|
|
13
|
-
export declare function build(options: BuildOptions, config: OpenNextConfig, projectOpts: ProjectOptions): Promise<void>;
|
|
13
|
+
export declare function build(options: buildHelper.BuildOptions, config: OpenNextConfig, projectOpts: ProjectOptions): Promise<void>;
|
package/dist/cli/build/build.js
CHANGED
|
@@ -8,6 +8,7 @@ import logger from "@opennextjs/aws/logger.js";
|
|
|
8
8
|
import { bundleServer } from "./bundle-server.js";
|
|
9
9
|
import { compileCacheAssetsManifestSqlFile } from "./open-next/compile-cache-assets-manifest.js";
|
|
10
10
|
import { compileEnvFiles } from "./open-next/compile-env-files.js";
|
|
11
|
+
import { compileImages } from "./open-next/compile-images.js";
|
|
11
12
|
import { compileInit } from "./open-next/compile-init.js";
|
|
12
13
|
import { compileDurableObjects } from "./open-next/compileDurableObjects.js";
|
|
13
14
|
import { createServerBundle } from "./open-next/createServerBundle.js";
|
|
@@ -52,6 +53,8 @@ export async function build(options, config, projectOpts) {
|
|
|
52
53
|
compileEnvFiles(options);
|
|
53
54
|
// Compile workerd init
|
|
54
55
|
compileInit(options);
|
|
56
|
+
// Compile image helpers
|
|
57
|
+
compileImages(options);
|
|
55
58
|
// Compile middleware
|
|
56
59
|
await createMiddleware(options, { forceOnlyBuildOnce: true });
|
|
57
60
|
createStaticAssets(options, { useBasePath: true });
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { build } from "esbuild";
|
|
5
|
+
/**
|
|
6
|
+
* Compiles the initialization code for the workerd runtime
|
|
7
|
+
*/
|
|
8
|
+
export async function compileImages(options) {
|
|
9
|
+
const currentDir = path.join(path.dirname(fileURLToPath(import.meta.url)));
|
|
10
|
+
const templatesDir = path.join(currentDir, "../../templates");
|
|
11
|
+
const imagesPath = path.join(templatesDir, "images.js");
|
|
12
|
+
const imagesManifestPath = path.join(options.appBuildOutputPath, ".next/images-manifest.json");
|
|
13
|
+
const imagesManifest = fs.existsSync(imagesManifestPath)
|
|
14
|
+
? JSON.parse(fs.readFileSync(imagesManifestPath, { encoding: "utf-8" }))
|
|
15
|
+
: {};
|
|
16
|
+
await build({
|
|
17
|
+
entryPoints: [imagesPath],
|
|
18
|
+
outdir: path.join(options.outputDir, "cloudflare"),
|
|
19
|
+
bundle: false,
|
|
20
|
+
minify: false,
|
|
21
|
+
format: "esm",
|
|
22
|
+
target: "esnext",
|
|
23
|
+
platform: "node",
|
|
24
|
+
define: {
|
|
25
|
+
__IMAGES_REMOTE_PATTERNS__: JSON.stringify(imagesManifest?.images?.remotePatterns ?? []),
|
|
26
|
+
__IMAGES_LOCAL_PATTERNS__: JSON.stringify(imagesManifest?.images?.localPatterns ?? []),
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { fileURLToPath } from "node:url";
|
|
4
3
|
import { loadConfig } from "@opennextjs/aws/adapters/config/util.js";
|
|
5
4
|
import { build } from "esbuild";
|
|
6
|
-
import pm from "picomatch";
|
|
7
5
|
/**
|
|
8
6
|
* Compiles the initialization code for the workerd runtime
|
|
9
7
|
*/
|
|
@@ -13,23 +11,6 @@ export async function compileInit(options) {
|
|
|
13
11
|
const initPath = path.join(templatesDir, "init.js");
|
|
14
12
|
const nextConfig = loadConfig(path.join(options.appBuildOutputPath, ".next"));
|
|
15
13
|
const basePath = nextConfig.basePath ?? "";
|
|
16
|
-
// https://github.com/vercel/next.js/blob/d76f0b13/packages/next/src/build/index.ts#L573
|
|
17
|
-
const nextRemotePatterns = nextConfig.images?.remotePatterns ?? [];
|
|
18
|
-
const remotePatterns = nextRemotePatterns.map((p) => ({
|
|
19
|
-
protocol: p.protocol,
|
|
20
|
-
hostname: p.hostname ? pm.makeRe(p.hostname).source : undefined,
|
|
21
|
-
port: p.port,
|
|
22
|
-
pathname: pm.makeRe(p.pathname ?? "**", { dot: true }).source,
|
|
23
|
-
// search is canary only as of June 2025
|
|
24
|
-
search: p.search,
|
|
25
|
-
}));
|
|
26
|
-
// Local patterns are only in canary as of June 2025
|
|
27
|
-
const nextLocalPatterns = nextConfig.images?.localPatterns ?? [];
|
|
28
|
-
// https://github.com/vercel/next.js/blob/d76f0b13/packages/next/src/build/index.ts#L573
|
|
29
|
-
const localPatterns = nextLocalPatterns.map((p) => ({
|
|
30
|
-
pathname: pm.makeRe(p.pathname ?? "**", { dot: true }).source,
|
|
31
|
-
search: p.search,
|
|
32
|
-
}));
|
|
33
14
|
await build({
|
|
34
15
|
entryPoints: [initPath],
|
|
35
16
|
outdir: path.join(options.outputDir, "cloudflare"),
|
|
@@ -41,8 +22,6 @@ export async function compileInit(options) {
|
|
|
41
22
|
define: {
|
|
42
23
|
__BUILD_TIMESTAMP_MS__: JSON.stringify(Date.now()),
|
|
43
24
|
__NEXT_BASE_PATH__: JSON.stringify(basePath),
|
|
44
|
-
__IMAGES_REMOTE_PATTERNS__: JSON.stringify(remotePatterns),
|
|
45
|
-
__IMAGES_LOCAL_PATTERNS__: JSON.stringify(localPatterns),
|
|
46
25
|
},
|
|
47
26
|
});
|
|
48
27
|
}
|
|
@@ -138,7 +138,6 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
|
|
|
138
138
|
awsPatches.patchNextServer,
|
|
139
139
|
awsPatches.patchEnvVars,
|
|
140
140
|
awsPatches.patchBackgroundRevalidation,
|
|
141
|
-
awsPatches.patchDropBabel,
|
|
142
141
|
// Cloudflare specific patches
|
|
143
142
|
patchResRevalidate,
|
|
144
143
|
patchUseCacheIO,
|
|
@@ -4,14 +4,12 @@
|
|
|
4
4
|
* Note: we will probably need to revisit the patches when the Next adapter API lands
|
|
5
5
|
*
|
|
6
6
|
* - Inline `getBuildId` as it relies on `readFileSync` that is not supported by workerd
|
|
7
|
-
* - Inline the middleware manifest
|
|
8
7
|
* - Override the cache and composable cache handlers
|
|
9
8
|
*/
|
|
10
9
|
import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
|
|
11
10
|
import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater.js";
|
|
12
11
|
export declare function patchNextServer(updater: ContentUpdater, buildOpts: BuildOptions): Plugin;
|
|
13
12
|
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";
|
|
14
|
-
export declare function createMiddlewareManifestRule(manifest: unknown): string;
|
|
15
13
|
/**
|
|
16
14
|
* The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
|
|
17
15
|
* Next.js would then do a dynamic require on a transformed version of the path to retrieve the
|
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
* Note: we will probably need to revisit the patches when the Next adapter API lands
|
|
5
5
|
*
|
|
6
6
|
* - Inline `getBuildId` as it relies on `readFileSync` that is not supported by workerd
|
|
7
|
-
* - Inline the middleware manifest
|
|
8
7
|
* - Override the cache and composable cache handlers
|
|
9
8
|
*/
|
|
10
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
11
9
|
import path from "node:path";
|
|
12
10
|
import { getPackagePath } from "@opennextjs/aws/build/helper.js";
|
|
13
11
|
import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
|
|
@@ -24,11 +22,6 @@ export function patchNextServer(updater, buildOpts) {
|
|
|
24
22
|
callback: async ({ contents }) => {
|
|
25
23
|
const { outputDir } = buildOpts;
|
|
26
24
|
contents = patchCode(contents, buildIdRule);
|
|
27
|
-
const manifestPath = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next/server/middleware-manifest.json");
|
|
28
|
-
const manifest = existsSync(manifestPath)
|
|
29
|
-
? JSON.parse(await readFileSync(manifestPath, "utf-8"))
|
|
30
|
-
: {};
|
|
31
|
-
contents = patchCode(contents, createMiddlewareManifestRule(manifest));
|
|
32
25
|
const outputPath = path.join(outputDir, "server-functions/default");
|
|
33
26
|
const cacheHandler = path.join(outputPath, getPackagePath(buildOpts), "cache.cjs");
|
|
34
27
|
contents = patchCode(contents, createCacheHandlerRule(cacheHandler));
|
|
@@ -50,18 +43,6 @@ fix: |-
|
|
|
50
43
|
return process.env.NEXT_BUILD_ID;
|
|
51
44
|
}
|
|
52
45
|
`;
|
|
53
|
-
export function createMiddlewareManifestRule(manifest) {
|
|
54
|
-
return `
|
|
55
|
-
rule:
|
|
56
|
-
pattern:
|
|
57
|
-
selector: method_definition
|
|
58
|
-
context: "class { getMiddlewareManifest($$$PARAMS) { $$$_ } }"
|
|
59
|
-
fix: |-
|
|
60
|
-
getMiddlewareManifest($$$PARAMS) {
|
|
61
|
-
return ${JSON.stringify(manifest)};
|
|
62
|
-
}
|
|
63
|
-
`;
|
|
64
|
-
}
|
|
65
46
|
/**
|
|
66
47
|
* The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
|
|
67
48
|
* Next.js would then do a dynamic require on a transformed version of the path to retrieve the
|
|
@@ -60,7 +60,9 @@ export async function copyWorkerdPackages(options, nodePackages) {
|
|
|
60
60
|
const isNodeModuleRegex = getCrossPlatformPathRegex(`.*/node_modules/(?<pkg>.*)`, { escape: false });
|
|
61
61
|
// Copy full external packages when they use "workerd" build condition
|
|
62
62
|
const nextConfig = loadConfig(path.join(options.appBuildOutputPath, ".next"));
|
|
63
|
-
const externalPackages =
|
|
63
|
+
const externalPackages =
|
|
64
|
+
// @ts-expect-error In Next 14 its under experimental.serverComponentsExternalPackages
|
|
65
|
+
nextConfig.serverExternalPackages ?? nextConfig.experimental.serverComponentsExternalPackages ?? [];
|
|
64
66
|
for (const [src, dst] of nodePackages.entries()) {
|
|
65
67
|
try {
|
|
66
68
|
const pkgJson = JSON.parse(await fs.readFile(path.join(src, "package.json"), "utf8"));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type RemotePattern = {
|
|
2
|
+
protocol?: "http" | "https";
|
|
3
|
+
hostname: string;
|
|
4
|
+
port?: string;
|
|
5
|
+
pathname: string;
|
|
6
|
+
search?: string;
|
|
7
|
+
};
|
|
8
|
+
export type LocalPattern = {
|
|
9
|
+
pathname: string;
|
|
10
|
+
search?: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Fetches an images.
|
|
14
|
+
*
|
|
15
|
+
* Local images (starting with a '/' as fetched using the passed fetcher).
|
|
16
|
+
* Remote images should match the configured remote patterns or a 404 response is returned.
|
|
17
|
+
*/
|
|
18
|
+
export declare function fetchImage(fetcher: Fetcher | undefined, imageUrl: string): Response | Promise<Response> | undefined;
|
|
19
|
+
export declare function matchRemotePattern(pattern: RemotePattern, url: URL): boolean;
|
|
20
|
+
export declare function matchLocalPattern(pattern: LocalPattern, url: URL): boolean;
|
|
21
|
+
declare global {
|
|
22
|
+
var __IMAGES_REMOTE_PATTERNS__: RemotePattern[];
|
|
23
|
+
var __IMAGES_LOCAL_PATTERNS__: LocalPattern[];
|
|
24
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches an images.
|
|
3
|
+
*
|
|
4
|
+
* Local images (starting with a '/' as fetched using the passed fetcher).
|
|
5
|
+
* Remote images should match the configured remote patterns or a 404 response is returned.
|
|
6
|
+
*/
|
|
7
|
+
export function fetchImage(fetcher, imageUrl) {
|
|
8
|
+
// https://github.com/vercel/next.js/blob/d76f0b1/packages/next/src/server/image-optimizer.ts#L208
|
|
9
|
+
if (!imageUrl || imageUrl.length > 3072 || imageUrl.startsWith("//")) {
|
|
10
|
+
return getUrlErrorResponse();
|
|
11
|
+
}
|
|
12
|
+
// Local
|
|
13
|
+
if (imageUrl.startsWith("/")) {
|
|
14
|
+
let pathname;
|
|
15
|
+
let url;
|
|
16
|
+
try {
|
|
17
|
+
// We only need pathname and search
|
|
18
|
+
url = new URL(imageUrl, "http://n");
|
|
19
|
+
pathname = decodeURIComponent(url.pathname);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return getUrlErrorResponse();
|
|
23
|
+
}
|
|
24
|
+
if (/\/_next\/image($|\/)/.test(pathname)) {
|
|
25
|
+
return getUrlErrorResponse();
|
|
26
|
+
}
|
|
27
|
+
// If localPatterns are not defined all local images are allowed.
|
|
28
|
+
if (__IMAGES_LOCAL_PATTERNS__.length > 0 &&
|
|
29
|
+
!__IMAGES_LOCAL_PATTERNS__.some((p) => matchLocalPattern(p, url))) {
|
|
30
|
+
return getUrlErrorResponse();
|
|
31
|
+
}
|
|
32
|
+
return fetcher?.fetch(`http://assets.local${imageUrl}`);
|
|
33
|
+
}
|
|
34
|
+
// Remote
|
|
35
|
+
let url;
|
|
36
|
+
try {
|
|
37
|
+
url = new URL(imageUrl);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return getUrlErrorResponse();
|
|
41
|
+
}
|
|
42
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
43
|
+
return getUrlErrorResponse();
|
|
44
|
+
}
|
|
45
|
+
// The remotePatterns is used to allow images from specific remote external paths and block all others.
|
|
46
|
+
if (!__IMAGES_REMOTE_PATTERNS__.some((p) => matchRemotePattern(p, url))) {
|
|
47
|
+
return getUrlErrorResponse();
|
|
48
|
+
}
|
|
49
|
+
return fetch(imageUrl, { cf: { cacheEverything: true } });
|
|
50
|
+
}
|
|
51
|
+
export function matchRemotePattern(pattern, url) {
|
|
52
|
+
// https://github.com/vercel/next.js/blob/d76f0b1/packages/next/src/shared/lib/match-remote-pattern.ts
|
|
53
|
+
if (pattern.protocol !== undefined &&
|
|
54
|
+
pattern.protocol.replace(/:$/, "") !== url.protocol.replace(/:$/, "")) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (pattern.port !== undefined && pattern.port !== url.port) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (pattern.hostname === undefined || !new RegExp(pattern.hostname).test(url.hostname)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (pattern.search !== undefined && pattern.search !== url.search) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
// Should be the same as writeImagesManifest()
|
|
67
|
+
return new RegExp(pattern.pathname).test(url.pathname);
|
|
68
|
+
}
|
|
69
|
+
export function matchLocalPattern(pattern, url) {
|
|
70
|
+
// https://github.com/vercel/next.js/blob/d76f0b1/packages/next/src/shared/lib/match-local-pattern.ts
|
|
71
|
+
if (pattern.search !== undefined && pattern.search !== url.search) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return new RegExp(pattern.pathname).test(url.pathname);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* @returns same error as Next.js when the url query parameter is not accepted.
|
|
78
|
+
*/
|
|
79
|
+
function getUrlErrorResponse() {
|
|
80
|
+
return new Response(`"url" parameter is not allowed`, { status: 400 });
|
|
81
|
+
}
|
|
82
|
+
/* eslint-enable no-var */
|
|
@@ -7,24 +7,7 @@
|
|
|
7
7
|
* Executes the handler with the Cloudflare context.
|
|
8
8
|
*/
|
|
9
9
|
export declare function runWithCloudflareRequestContext(request: Request, env: CloudflareEnv, ctx: ExecutionContext, handler: () => Promise<Response>): Promise<Response>;
|
|
10
|
-
export type RemotePattern = {
|
|
11
|
-
protocol?: "http" | "https";
|
|
12
|
-
hostname: string;
|
|
13
|
-
port?: string;
|
|
14
|
-
pathname: string;
|
|
15
|
-
search?: string;
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Fetches an images.
|
|
19
|
-
*
|
|
20
|
-
* Local images (starting with a '/' as fetched using the passed fetcher).
|
|
21
|
-
* Remote images should match the configured remote patterns or a 404 response is returned.
|
|
22
|
-
*/
|
|
23
|
-
export declare function fetchImage(fetcher: Fetcher | undefined, url: string): Response | Promise<Response> | undefined;
|
|
24
|
-
export declare function matchRemotePattern(pattern: RemotePattern, url: URL): boolean;
|
|
25
10
|
declare global {
|
|
26
11
|
var __BUILD_TIMESTAMP_MS__: number;
|
|
27
12
|
var __NEXT_BASE_PATH__: string;
|
|
28
|
-
var __IMAGES_REMOTE_PATTERNS__: RemotePattern[];
|
|
29
|
-
var __IMAGES_LOCAL_PATTERNS__: unknown[];
|
|
30
13
|
}
|
|
@@ -114,80 +114,4 @@ function populateProcessEnv(url, env) {
|
|
|
114
114
|
*/
|
|
115
115
|
process.env.__NEXT_PRIVATE_ORIGIN = url.origin;
|
|
116
116
|
}
|
|
117
|
-
const imgRemotePatterns = __IMAGES_REMOTE_PATTERNS__;
|
|
118
|
-
/**
|
|
119
|
-
* Fetches an images.
|
|
120
|
-
*
|
|
121
|
-
* Local images (starting with a '/' as fetched using the passed fetcher).
|
|
122
|
-
* Remote images should match the configured remote patterns or a 404 response is returned.
|
|
123
|
-
*/
|
|
124
|
-
export function fetchImage(fetcher, url) {
|
|
125
|
-
// https://github.com/vercel/next.js/blob/d76f0b1/packages/next/src/server/image-optimizer.ts#L208
|
|
126
|
-
if (!url || url.length > 3072 || url.startsWith("//")) {
|
|
127
|
-
return new Response("Not Found", { status: 404 });
|
|
128
|
-
}
|
|
129
|
-
// Local
|
|
130
|
-
if (url.startsWith("/")) {
|
|
131
|
-
if (/\/_next\/image($|\/)/.test(decodeURIComponent(parseUrl(url)?.pathname ?? ""))) {
|
|
132
|
-
return new Response("Not Found", { status: 404 });
|
|
133
|
-
}
|
|
134
|
-
return fetcher?.fetch(`http://assets.local${url}`);
|
|
135
|
-
}
|
|
136
|
-
// Remote
|
|
137
|
-
let hrefParsed;
|
|
138
|
-
try {
|
|
139
|
-
hrefParsed = new URL(url);
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
return new Response("Not Found", { status: 404 });
|
|
143
|
-
}
|
|
144
|
-
if (!["http:", "https:"].includes(hrefParsed.protocol)) {
|
|
145
|
-
return new Response("Not Found", { status: 404 });
|
|
146
|
-
}
|
|
147
|
-
if (!imgRemotePatterns.some((p) => matchRemotePattern(p, hrefParsed))) {
|
|
148
|
-
return new Response("Not Found", { status: 404 });
|
|
149
|
-
}
|
|
150
|
-
return fetch(url, { cf: { cacheEverything: true } });
|
|
151
|
-
}
|
|
152
|
-
export function matchRemotePattern(pattern, url) {
|
|
153
|
-
// https://github.com/vercel/next.js/blob/d76f0b1/packages/next/src/shared/lib/match-remote-pattern.ts
|
|
154
|
-
if (pattern.protocol !== undefined) {
|
|
155
|
-
if (pattern.protocol.replace(/:$/, "") !== url.protocol.replace(/:$/, "")) {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (pattern.port !== undefined) {
|
|
160
|
-
if (pattern.port !== url.port) {
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
if (pattern.hostname === undefined) {
|
|
165
|
-
throw new Error(`Pattern should define hostname but found\n${JSON.stringify(pattern)}`);
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
if (!new RegExp(pattern.hostname).test(url.hostname)) {
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
if (pattern.search !== undefined) {
|
|
173
|
-
if (pattern.search !== url.search) {
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
// Should be the same as writeImagesManifest()
|
|
178
|
-
if (!new RegExp(pattern.pathname).test(url.pathname)) {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
function parseUrl(url) {
|
|
184
|
-
let parsed = undefined;
|
|
185
|
-
try {
|
|
186
|
-
parsed = new URL(url, "http://n");
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
// empty
|
|
190
|
-
}
|
|
191
|
-
return parsed;
|
|
192
|
-
}
|
|
193
117
|
/* eslint-enable no-var */
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
//@ts-expect-error: Will be resolved by wrangler build
|
|
2
|
-
import { fetchImage
|
|
2
|
+
import { fetchImage } from "./cloudflare/images.js";
|
|
3
|
+
//@ts-expect-error: Will be resolved by wrangler build
|
|
4
|
+
import { runWithCloudflareRequestContext } from "./cloudflare/init.js";
|
|
3
5
|
// @ts-expect-error: Will be resolved by wrangler build
|
|
4
6
|
import { handler as middlewareHandler } from "./middleware/handler.mjs";
|
|
5
7
|
//@ts-expect-error: Will be resolved by wrangler build
|
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.3.
|
|
4
|
+
"version": "1.3.1",
|
|
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.6.
|
|
46
|
+
"@opennextjs/aws": "3.6.6",
|
|
47
47
|
"enquirer": "^2.4.1",
|
|
48
48
|
"glob": "^11.0.0",
|
|
49
49
|
"ts-tqdm": "^0.8.6"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import * as internal from "../overrides/internal";
|
|
3
|
-
import { BucketCachePurge } from "./bucket-cache-purge";
|
|
4
|
-
vi.mock("cloudflare:workers", () => ({
|
|
5
|
-
DurableObject: class {
|
|
6
|
-
ctx;
|
|
7
|
-
env;
|
|
8
|
-
constructor(ctx, env) {
|
|
9
|
-
this.ctx = ctx;
|
|
10
|
-
this.env = env;
|
|
11
|
-
}
|
|
12
|
-
},
|
|
13
|
-
}));
|
|
14
|
-
const createBucketCachePurge = () => {
|
|
15
|
-
const mockState = {
|
|
16
|
-
waitUntil: vi.fn(),
|
|
17
|
-
blockConcurrencyWhile: vi.fn().mockImplementation(async (fn) => fn()),
|
|
18
|
-
storage: {
|
|
19
|
-
setAlarm: vi.fn(),
|
|
20
|
-
getAlarm: vi.fn(),
|
|
21
|
-
sql: {
|
|
22
|
-
exec: vi.fn().mockImplementation(() => ({
|
|
23
|
-
one: vi.fn(),
|
|
24
|
-
toArray: vi.fn().mockReturnValue([]),
|
|
25
|
-
})),
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
-
return new BucketCachePurge(mockState, {});
|
|
31
|
-
};
|
|
32
|
-
describe("BucketCachePurge", () => {
|
|
33
|
-
it("should block concurrency while creating the table", async () => {
|
|
34
|
-
const cache = createBucketCachePurge();
|
|
35
|
-
// @ts-expect-error - testing private method
|
|
36
|
-
expect(cache.ctx.blockConcurrencyWhile).toHaveBeenCalled();
|
|
37
|
-
// @ts-expect-error - testing private method
|
|
38
|
-
expect(cache.ctx.storage.sql.exec).toHaveBeenCalledWith(expect.stringContaining("CREATE TABLE IF NOT EXISTS cache_purge"));
|
|
39
|
-
});
|
|
40
|
-
describe("purgeCacheByTags", () => {
|
|
41
|
-
it("should insert tags into the sql table", async () => {
|
|
42
|
-
const cache = createBucketCachePurge();
|
|
43
|
-
const tags = ["tag1", "tag2"];
|
|
44
|
-
await cache.purgeCacheByTags(tags);
|
|
45
|
-
// @ts-expect-error - testing private method
|
|
46
|
-
expect(cache.ctx.storage.sql.exec).toHaveBeenCalledWith(expect.stringContaining("INSERT OR REPLACE INTO cache_purge"), [tags[0]]);
|
|
47
|
-
// @ts-expect-error - testing private method
|
|
48
|
-
expect(cache.ctx.storage.sql.exec).toHaveBeenCalledWith(expect.stringContaining("INSERT OR REPLACE INTO cache_purge"), [tags[1]]);
|
|
49
|
-
});
|
|
50
|
-
it("should set an alarm if no alarm is set", async () => {
|
|
51
|
-
const cache = createBucketCachePurge();
|
|
52
|
-
// @ts-expect-error - testing private method
|
|
53
|
-
cache.ctx.storage.getAlarm.mockResolvedValueOnce(null);
|
|
54
|
-
await cache.purgeCacheByTags(["tag"]);
|
|
55
|
-
// @ts-expect-error - testing private method
|
|
56
|
-
expect(cache.ctx.storage.setAlarm).toHaveBeenCalled();
|
|
57
|
-
});
|
|
58
|
-
it("should not set an alarm if one is already set", async () => {
|
|
59
|
-
const cache = createBucketCachePurge();
|
|
60
|
-
// @ts-expect-error - testing private method
|
|
61
|
-
cache.ctx.storage.getAlarm.mockResolvedValueOnce(true);
|
|
62
|
-
await cache.purgeCacheByTags(["tag"]);
|
|
63
|
-
// @ts-expect-error - testing private method
|
|
64
|
-
expect(cache.ctx.storage.setAlarm).not.toHaveBeenCalled();
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
describe("alarm", () => {
|
|
68
|
-
it("should purge cache by tags and delete them from the sql table", async () => {
|
|
69
|
-
const cache = createBucketCachePurge();
|
|
70
|
-
// @ts-expect-error - testing private method
|
|
71
|
-
cache.ctx.storage.sql.exec.mockReturnValueOnce({
|
|
72
|
-
toArray: () => [{ tag: "tag1" }, { tag: "tag2" }],
|
|
73
|
-
});
|
|
74
|
-
await cache.alarm();
|
|
75
|
-
// @ts-expect-error - testing private method
|
|
76
|
-
expect(cache.ctx.storage.sql.exec).toHaveBeenCalledWith(expect.stringContaining("DELETE FROM cache_purge"), ["tag1", "tag2"]);
|
|
77
|
-
});
|
|
78
|
-
it("should not purge cache if no tags are found", async () => {
|
|
79
|
-
const cache = createBucketCachePurge();
|
|
80
|
-
// @ts-expect-error - testing private method
|
|
81
|
-
cache.ctx.storage.sql.exec.mockReturnValueOnce({
|
|
82
|
-
toArray: () => [],
|
|
83
|
-
});
|
|
84
|
-
await cache.alarm();
|
|
85
|
-
// @ts-expect-error - testing private method
|
|
86
|
-
expect(cache.ctx.storage.sql.exec).not.toHaveBeenCalledWith(expect.stringContaining("DELETE FROM cache_purge"), []);
|
|
87
|
-
});
|
|
88
|
-
it("should call internalPurgeCacheByTags with the correct tags", async () => {
|
|
89
|
-
const cache = createBucketCachePurge();
|
|
90
|
-
const tags = ["tag1", "tag2"];
|
|
91
|
-
// @ts-expect-error - testing private method
|
|
92
|
-
cache.ctx.storage.sql.exec.mockReturnValueOnce({
|
|
93
|
-
toArray: () => tags.map((tag) => ({ tag })),
|
|
94
|
-
});
|
|
95
|
-
const internalPurgeCacheByTagsSpy = vi.spyOn(internal, "internalPurgeCacheByTags");
|
|
96
|
-
await cache.alarm();
|
|
97
|
-
expect(internalPurgeCacheByTagsSpy).toHaveBeenCalledWith(
|
|
98
|
-
// @ts-expect-error - testing private method
|
|
99
|
-
cache.env, tags);
|
|
100
|
-
// @ts-expect-error - testing private method 1st is constructor, 2nd is to get the tags and 3rd is to delete them
|
|
101
|
-
expect(cache.ctx.storage.sql.exec).toHaveBeenCalledTimes(3);
|
|
102
|
-
});
|
|
103
|
-
it("should continue until all tags are purged", async () => {
|
|
104
|
-
const cache = createBucketCachePurge();
|
|
105
|
-
const tags = Array.from({ length: 100 }, (_, i) => `tag${i}`);
|
|
106
|
-
// @ts-expect-error - testing private method
|
|
107
|
-
cache.ctx.storage.sql.exec.mockReturnValueOnce({
|
|
108
|
-
toArray: () => tags.map((tag) => ({ tag })),
|
|
109
|
-
});
|
|
110
|
-
const internalPurgeCacheByTagsSpy = vi.spyOn(internal, "internalPurgeCacheByTags");
|
|
111
|
-
await cache.alarm();
|
|
112
|
-
expect(internalPurgeCacheByTagsSpy).toHaveBeenCalledWith(
|
|
113
|
-
// @ts-expect-error - testing private method
|
|
114
|
-
cache.env, tags);
|
|
115
|
-
// @ts-expect-error - testing private method 1st is constructor, 2nd is to get the tags and 3rd is to delete them, 4th is to get the next 100 tags
|
|
116
|
-
expect(cache.ctx.storage.sql.exec).toHaveBeenCalledTimes(4);
|
|
117
|
-
// @ts-expect-error - testing private method
|
|
118
|
-
expect(cache.ctx.storage.sql.exec).toHaveBeenLastCalledWith(expect.stringContaining("SELECT * FROM cache_purge LIMIT 100"));
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|