···11+import type { InternalEvent, InternalResult } from "@opennextjs/aws/types/open-next";
22+import type { AssetResolver } from "@opennextjs/aws/types/overrides";
33+44+import { getCloudflareContext } from "../../cloudflare-context.js";
55+66+/**
77+ * Serves assets when `run_worker_first` is set to true.
88+ *
99+ * When `run_worker_first` is `false`, the assets are served directly bypassing Next routing.
1010+ *
1111+ * When it is `true`, assets are served from the routing layer. It should be used when assets
1212+ * should be behind the middleware or when skew protection is enabled.
1313+ *
1414+ * See https://developers.cloudflare.com/workers/static-assets/binding/#run_worker_first
1515+ */
1616+const resolver: AssetResolver = {
1717+ name: "cloudflare-asset-resolver",
1818+ async maybeGetAssetResult(event: InternalEvent) {
1919+ const { ASSETS } = getCloudflareContext().env;
2020+2121+ if (!ASSETS || !isUserWorkerFirst(globalThis.__ASSETS_RUN_WORKER_FIRST__, event.rawPath)) {
2222+ // Only handle assets when the user worker runs first for the path
2323+ return undefined;
2424+ }
2525+2626+ const { method, headers } = event;
2727+2828+ if (method !== "GET" && method != "HEAD") {
2929+ return undefined;
3030+ }
3131+3232+ const url = new URL(event.rawPath, "https://assets.local");
3333+ const response = await ASSETS.fetch(url, {
3434+ headers,
3535+ method,
3636+ });
3737+3838+ if (response.status === 404) {
3939+ return undefined;
4040+ }
4141+4242+ return {
4343+ type: "core",
4444+ statusCode: response.status,
4545+ headers: Object.fromEntries(response.headers.entries()),
4646+ // Workers and Node types differ.
4747+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4848+ body: response.body || (new ReadableStream() as any),
4949+ isBase64Encoded: false,
5050+ } satisfies InternalResult;
5151+ },
5252+};
5353+5454+/**
5555+ * @param runWorkerFirst `run_worker_first` config
5656+ * @param pathname pathname of the request
5757+ * @returns Whether the user worker runs first
5858+ */
5959+export function isUserWorkerFirst(runWorkerFirst: boolean | string[] | undefined, pathname: string): boolean {
6060+ if (!Array.isArray(runWorkerFirst)) {
6161+ return runWorkerFirst ?? false;
6262+ }
6363+6464+ let hasPositiveMatch = false;
6565+6666+ for (let rule of runWorkerFirst) {
6767+ let isPositiveRule = true;
6868+6969+ if (rule.startsWith("!")) {
7070+ rule = rule.slice(1);
7171+ isPositiveRule = false;
7272+ } else if (hasPositiveMatch) {
7373+ // Do not look for more positive rules once we have a match
7474+ continue;
7575+ }
7676+7777+ // - Escapes special characters
7878+ // - Replaces * with .*
7979+ const match = new RegExp(`^${rule.replace(/([[\]().*+?^$|{}\\])/g, "\\$1").replace("\\*", ".*")}$`).test(
8080+ pathname
8181+ );
8282+8383+ if (match) {
8484+ if (isPositiveRule) {
8585+ hasPositiveMatch = true;
8686+ } else {
8787+ // Exit early when there is a negative match
8888+ return false;
8989+ }
9090+ }
9191+ }
9292+9393+ return hasPositiveMatch;
9494+}
9595+9696+export default resolver;
+4-3
packages/cloudflare/src/cli/build/build.ts
···55import * as buildHelper from "@opennextjs/aws/build/helper.js";
66import { printHeader } from "@opennextjs/aws/build/utils.js";
77import logger from "@opennextjs/aws/logger.js";
88+import type { Unstable_Config } from "wrangler";
89910import { OpenNextConfig } from "../../api/config.js";
1011import type { ProjectOptions } from "../project-options.js";
···3031export async function build(
3132 options: buildHelper.BuildOptions,
3233 config: OpenNextConfig,
3333- projectOpts: ProjectOptions
3434+ projectOpts: ProjectOptions,
3535+ wranglerConfig: Unstable_Config
3436): Promise<void> {
3537 // Do not minify the code so that we can apply string replacement patch.
3636- // Note that wrangler will still minify the bundle.
3738 options.minify = false;
38393940 // Pre-build validation
···6566 compileEnvFiles(options);
66676768 // Compile workerd init
6868- compileInit(options);
6969+ compileInit(options, wranglerConfig);
69707071 // Compile image helpers
7172 compileImages(options);
···44import { loadConfig } from "@opennextjs/aws/adapters/config/util.js";
55import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
66import { build } from "esbuild";
77+import type { Unstable_Config } from "wrangler";
7889/**
910 * Compiles the initialization code for the workerd runtime
1011 */
1111-export async function compileInit(options: BuildOptions) {
1212+export async function compileInit(options: BuildOptions, wranglerConfig: Unstable_Config) {
1213 const currentDir = path.join(path.dirname(fileURLToPath(import.meta.url)));
1314 const templatesDir = path.join(currentDir, "../../templates");
1415 const initPath = path.join(templatesDir, "init.js");
···2728 define: {
2829 __BUILD_TIMESTAMP_MS__: JSON.stringify(Date.now()),
2930 __NEXT_BASE_PATH__: JSON.stringify(basePath),
3131+ __ASSETS_RUN_WORKER_FIRST__: JSON.stringify(wranglerConfig.assets?.run_worker_first ?? false),
3032 },
3133 });
3234}
+10-2
packages/cloudflare/src/cli/index.ts
···66import { normalizeOptions } from "@opennextjs/aws/build/helper.js";
77import { printHeader, showWarningOnWindows } from "@opennextjs/aws/build/utils.js";
88import logger from "@opennextjs/aws/logger.js";
99+import { unstable_readConfig } from "wrangler";
9101011import { Arguments, getArgs } from "./args.js";
1112import { build } from "./build/build.js";
···1415import { populateCache } from "./commands/populate-cache.js";
1516import { preview } from "./commands/preview.js";
1617import { upload } from "./commands/upload.js";
1818+import { getWranglerConfigFlag, getWranglerEnvironmentFlag } from "./utils/run-wrangler.js";
17191820const nextAppDir = process.cwd();
1921···3840 logger.setLevel(options.debug ? "debug" : "info");
39414042 switch (args.command) {
4141- case "build":
4242- return build(options, config, { ...args, sourceDir: baseDir });
4343+ case "build": {
4444+ const argv = process.argv.slice(2);
4545+ const wranglerEnv = getWranglerEnvironmentFlag(argv);
4646+ const wranglerConfigFile = getWranglerConfigFlag(argv);
4747+ const wranglerConfig = unstable_readConfig({ env: wranglerEnv, config: wranglerConfigFile });
4848+4949+ return build(options, config, { ...args, sourceDir: baseDir }, wranglerConfig);
5050+ }
4351 case "preview":
4452 return preview(options, config, args);
4553 case "deploy":
+5-2
packages/cloudflare/src/cli/templates/init.ts
···94949595 Object.assign(globalThis, {
9696 Request: CustomRequest,
9797- __BUILD_TIMESTAMP_MS__: __BUILD_TIMESTAMP_MS__,
9898- __NEXT_BASE_PATH__: __NEXT_BASE_PATH__,
9797+ __BUILD_TIMESTAMP_MS__,
9898+ __NEXT_BASE_PATH__,
9999+ __ASSETS_RUN_WORKER_FIRST__,
99100 // The external middleware will use the convertTo function of the `edge` converter
100101 // by default it will try to fetch the request, but since we are running everything in the same worker
101102 // we need to use the request as is.
···146147 var __BUILD_TIMESTAMP_MS__: number;
147148 // Next basePath
148149 var __NEXT_BASE_PATH__: string;
150150+ // Value of `run_worker_first` for the asset binding
151151+ var __ASSETS_RUN_WORKER_FIRST__: boolean | string[] | undefined;
149152}
150153/* eslint-enable no-var */
···9191}
92929393/**
9494- * Find the value of the environment flag (`--env` / `-e`) used by Wrangler.
9494+ * Returns the value of the flag.
9595+ *
9696+ * The value is retrieved for `<argName> value` or `<argName>=value`.
9597 *
9696- * @param args - CLI arguments.
9797- * @returns Value of the environment flag.
9898+ * @param args List of args
9999+ * @param argName The arg name with leading dashes, i.e. `--env` or `-e`
100100+ * @returns The value or undefined when not found
98101 */
9999-export function getWranglerEnvironmentFlag(args: string[]) {
100100- for (let i = 0; i <= args.length; i++) {
101101- const arg = args[i];
102102- if (!arg) continue;
102102+export function getFlagValue(args: string[], ...argNames: string[]): string | undefined {
103103+ if (argNames.some((name) => !name.startsWith("-"))) {
104104+ // Names should start with "-" or "--"
105105+ throw new Error(`Invalid arg names: ${argNames}`);
106106+ }
103107104104- if (arg === "--env" || arg === "-e") {
105105- return args[i + 1];
106106- }
108108+ for (const argName of argNames) {
109109+ for (let i = 0; i <= args.length; i++) {
110110+ const arg = args[i];
111111+ if (!arg) continue;
107112108108- if (arg.startsWith("--env=") || arg.startsWith("-e=")) {
109109- return arg.split("=")[1];
113113+ if (arg === argName) {
114114+ return args[i + 1];
115115+ }
116116+117117+ if (arg.startsWith(argName)) {
118118+ return arg.split("=")[1];
119119+ }
110120 }
111121 }
112122}
123123+124124+/**
125125+ * Find the value of the environment flag (`--env` / `-e`) used by Wrangler.
126126+ *
127127+ * @param args - CLI arguments.
128128+ * @returns Value of the environment flag or undefined when not found
129129+ */
130130+export function getWranglerEnvironmentFlag(args: string[]): string | undefined {
131131+ return getFlagValue(args, "--env", "-e");
132132+}
133133+134134+/**
135135+ * Find the value of the config flag (`--config` / `-c`) used by Wrangler.
136136+ *
137137+ * @param args - CLI arguments.
138138+ * @returns Value of the config flag or undefined when not found
139139+ */
140140+export function getWranglerConfigFlag(args: string[]): string | undefined {
141141+ return getFlagValue(args, "--config", "-c");
142142+}