webtools

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 7eb7070c8701b397a42b155c462b1e68740ca305
parent c61b300b7b8469a8e9df6bd931d37a41996218b0
Author: Minerva_juppiter <94231606+minerva-jupiter@users.noreply.github.com>
Date:   Tue, 28 Jul 2026 22:14:29 +0900

feat(deps): update dependencies and add build script for Cloudflare P… (#89)

* feat(deps): update dependencies and add build script for Cloudflare Pages

Update core dependencies including Next.js, React, Sentry, TypeScript, and Biome, add the @cloudflare/next-on-pages package along with the build:pages script, and update trusted dependencies.

* feat(config): migrate from next-on-pages to opennextjs-cloudflare

* feat(config): add Wrangler configuration file for OpenNext deployment

* chore(deps): update typescript dependency
Diffstat:
A.open-next/.build/open-next.config.edge.mjs | 215+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.open-next/.build/open-next.config.mjs | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aopen-next.config.ts | 3+++
Mpackage.json | 25++++++++++++++++---------
Awrangler.jsonc | 13+++++++++++++
5 files changed, 464 insertions(+), 9 deletions(-)

diff --git a/.open-next/.build/open-next.config.edge.mjs b/.open-next/.build/open-next.config.edge.mjs @@ -0,0 +1,215 @@ +// node_modules/@opennextjs/cloudflare/dist/api/cloudflare-context.js +var cloudflareContextSymbol = Symbol.for("__cloudflare-context__"); +function getCloudflareContext(options = { async: false }) { + return options.async ? getCloudflareContextAsync() : getCloudflareContextSync(); +} +function getCloudflareContextFromGlobalScope() { + const global = globalThis; + return global[cloudflareContextSymbol]; +} +function inSSG() { + const global = globalThis; + return global.__NEXT_DATA__?.nextExport === true; +} +function getCloudflareContextSync() { + const cloudflareContext = getCloudflareContextFromGlobalScope(); + if (cloudflareContext) { + return cloudflareContext; + } + if (inSSG()) { + throw new Error(` + +ERROR: \`getCloudflareContext\` has been called in sync mode in either a static route or at the top level of a non-static one, both cases are not allowed but can be solved by either: + - make sure that the call is not at the top level and that the route is not static + - call \`getCloudflareContext({async: true})\` to use the \`async\` mode + - avoid calling \`getCloudflareContext\` in the route +`); + } + throw new Error(initOpenNextCloudflareForDevErrorMsg); +} +async function getCloudflareContextAsync() { + const cloudflareContext = getCloudflareContextFromGlobalScope(); + if (cloudflareContext) { + return cloudflareContext; + } + const inNodejsRuntime = process.env.NEXT_RUNTIME === "nodejs"; + if (inNodejsRuntime || inSSG()) { + const cloudflareContext2 = await getCloudflareContextFromWrangler(); + addCloudflareContextToNodejsGlobal(cloudflareContext2); + return cloudflareContext2; + } + throw new Error(initOpenNextCloudflareForDevErrorMsg); +} +function addCloudflareContextToNodejsGlobal(cloudflareContext) { + const global = globalThis; + global[cloudflareContextSymbol] = cloudflareContext; +} +async function getCloudflareContextFromWrangler(options) { + const { getPlatformProxy } = await import( + /* webpackIgnore: true */ + `${"__wrangler".replaceAll("_", "")}` + ); + const environment = options?.environment ?? process.env.NEXT_DEV_WRANGLER_ENV; + const { env, cf, ctx } = await getPlatformProxy({ + ...options, + // The `env` passed to the fetch handler does not contain variables from `.env*` files. + // because we invoke wrangler with `CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV`=`"false"`. + // Initializing `envFiles` with an empty list is the equivalent for this API call. + envFiles: [], + environment + }); + return { + env, + cf, + ctx + }; +} +var initOpenNextCloudflareForDevErrorMsg = ` + +ERROR: \`getCloudflareContext\` has been called without having called \`initOpenNextCloudflareForDev\` from the Next.js config file. +You should update your Next.js config file as shown below: + + \`\`\` + // next.config.mjs + + import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare"; + + initOpenNextCloudflareForDev(); + + const nextConfig = { ... }; + export default nextConfig; + \`\`\` + +`; + +// node_modules/@opennextjs/cloudflare/dist/api/overrides/asset-resolver/index.js +var resolver = { + name: "cloudflare-asset-resolver", + async maybeGetAssetResult(event) { + const { ASSETS } = getCloudflareContext().env; + if (!ASSETS || !isUserWorkerFirst(globalThis.__ASSETS_RUN_WORKER_FIRST__, event.rawPath)) { + return void 0; + } + const { method, headers } = event; + if (method !== "GET" && method != "HEAD") { + return void 0; + } + const url = new URL(event.rawPath, "https://assets.local"); + const response = await ASSETS.fetch(url, { + headers, + method + }); + if (response.status === 404) { + await response.body?.cancel(); + return void 0; + } + return { + type: "core", + statusCode: response.status, + headers: Object.fromEntries(response.headers.entries()), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + body: getResponseBody(method, response), + isBase64Encoded: false + }; + } +}; +function getResponseBody(method, response) { + if (method === "HEAD") { + return null; + } + return response.body || new ReadableStream(); +} +function isUserWorkerFirst(runWorkerFirst, pathname) { + if (!Array.isArray(runWorkerFirst)) { + return runWorkerFirst ?? false; + } + let hasPositiveMatch = false; + for (let rule of runWorkerFirst) { + let isPositiveRule = true; + if (rule.startsWith("!")) { + rule = rule.slice(1); + isPositiveRule = false; + } else if (hasPositiveMatch) { + continue; + } + const match = new RegExp(`^${rule.replace(/([[\]().*+?^$|{}\\])/g, "\\$1").replace("\\*", ".*")}$`).test(pathname); + if (match) { + if (isPositiveRule) { + hasPositiveMatch = true; + } else { + return false; + } + } + } + return hasPositiveMatch; +} +var asset_resolver_default = resolver; + +// node_modules/@opennextjs/cloudflare/dist/api/config.js +function defineCloudflareConfig(config = {}) { + const { incrementalCache, tagCache, queue, cachePurge, enableCacheInterception = false, routePreloadingBehavior = "none" } = config; + return { + default: { + override: { + wrapper: "cloudflare-node", + converter: "edge", + proxyExternalRequest: "fetch", + incrementalCache: resolveIncrementalCache(incrementalCache), + tagCache: resolveTagCache(tagCache), + queue: resolveQueue(queue), + cdnInvalidation: resolveCdnInvalidation(cachePurge) + }, + routePreloadingBehavior + }, + // node:crypto is used to compute cache keys + edgeExternals: ["node:crypto"], + cloudflare: { + useWorkerdCondition: true + }, + dangerous: { + enableCacheInterception + }, + middleware: { + external: true, + override: { + wrapper: "cloudflare-edge", + converter: "edge", + proxyExternalRequest: "fetch", + incrementalCache: resolveIncrementalCache(incrementalCache), + tagCache: resolveTagCache(tagCache), + queue: resolveQueue(queue) + }, + assetResolver: () => asset_resolver_default + } + }; +} +function resolveIncrementalCache(value = "dummy") { + if (typeof value === "string") { + return value; + } + return typeof value === "function" ? value : () => value; +} +function resolveTagCache(value = "dummy") { + if (typeof value === "string") { + return value; + } + return typeof value === "function" ? value : () => value; +} +function resolveQueue(value = "dummy") { + if (typeof value === "string") { + return value; + } + return typeof value === "function" ? value : () => value; +} +function resolveCdnInvalidation(value = "dummy") { + if (typeof value === "string") { + return value; + } + return typeof value === "function" ? value : () => value; +} + +// open-next.config.ts +var open_next_config_default = defineCloudflareConfig(); +export { + open_next_config_default as default +}; diff --git a/.open-next/.build/open-next.config.mjs b/.open-next/.build/open-next.config.mjs @@ -0,0 +1,217 @@ +import { createRequire as topLevelCreateRequire } from 'module';const require = topLevelCreateRequire(import.meta.url);import bannerUrl from 'url';const __dirname = bannerUrl.fileURLToPath(new URL('.', import.meta.url)); + +// node_modules/@opennextjs/cloudflare/dist/api/cloudflare-context.js +var cloudflareContextSymbol = Symbol.for("__cloudflare-context__"); +function getCloudflareContext(options = { async: false }) { + return options.async ? getCloudflareContextAsync() : getCloudflareContextSync(); +} +function getCloudflareContextFromGlobalScope() { + const global = globalThis; + return global[cloudflareContextSymbol]; +} +function inSSG() { + const global = globalThis; + return global.__NEXT_DATA__?.nextExport === true; +} +function getCloudflareContextSync() { + const cloudflareContext = getCloudflareContextFromGlobalScope(); + if (cloudflareContext) { + return cloudflareContext; + } + if (inSSG()) { + throw new Error(` + +ERROR: \`getCloudflareContext\` has been called in sync mode in either a static route or at the top level of a non-static one, both cases are not allowed but can be solved by either: + - make sure that the call is not at the top level and that the route is not static + - call \`getCloudflareContext({async: true})\` to use the \`async\` mode + - avoid calling \`getCloudflareContext\` in the route +`); + } + throw new Error(initOpenNextCloudflareForDevErrorMsg); +} +async function getCloudflareContextAsync() { + const cloudflareContext = getCloudflareContextFromGlobalScope(); + if (cloudflareContext) { + return cloudflareContext; + } + const inNodejsRuntime = process.env.NEXT_RUNTIME === "nodejs"; + if (inNodejsRuntime || inSSG()) { + const cloudflareContext2 = await getCloudflareContextFromWrangler(); + addCloudflareContextToNodejsGlobal(cloudflareContext2); + return cloudflareContext2; + } + throw new Error(initOpenNextCloudflareForDevErrorMsg); +} +function addCloudflareContextToNodejsGlobal(cloudflareContext) { + const global = globalThis; + global[cloudflareContextSymbol] = cloudflareContext; +} +async function getCloudflareContextFromWrangler(options) { + const { getPlatformProxy } = await import( + /* webpackIgnore: true */ + `${"__wrangler".replaceAll("_", "")}` + ); + const environment = options?.environment ?? process.env.NEXT_DEV_WRANGLER_ENV; + const { env, cf, ctx } = await getPlatformProxy({ + ...options, + // The `env` passed to the fetch handler does not contain variables from `.env*` files. + // because we invoke wrangler with `CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV`=`"false"`. + // Initializing `envFiles` with an empty list is the equivalent for this API call. + envFiles: [], + environment + }); + return { + env, + cf, + ctx + }; +} +var initOpenNextCloudflareForDevErrorMsg = ` + +ERROR: \`getCloudflareContext\` has been called without having called \`initOpenNextCloudflareForDev\` from the Next.js config file. +You should update your Next.js config file as shown below: + + \`\`\` + // next.config.mjs + + import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare"; + + initOpenNextCloudflareForDev(); + + const nextConfig = { ... }; + export default nextConfig; + \`\`\` + +`; + +// node_modules/@opennextjs/cloudflare/dist/api/overrides/asset-resolver/index.js +var resolver = { + name: "cloudflare-asset-resolver", + async maybeGetAssetResult(event) { + const { ASSETS } = getCloudflareContext().env; + if (!ASSETS || !isUserWorkerFirst(globalThis.__ASSETS_RUN_WORKER_FIRST__, event.rawPath)) { + return void 0; + } + const { method, headers } = event; + if (method !== "GET" && method != "HEAD") { + return void 0; + } + const url = new URL(event.rawPath, "https://assets.local"); + const response = await ASSETS.fetch(url, { + headers, + method + }); + if (response.status === 404) { + await response.body?.cancel(); + return void 0; + } + return { + type: "core", + statusCode: response.status, + headers: Object.fromEntries(response.headers.entries()), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + body: getResponseBody(method, response), + isBase64Encoded: false + }; + } +}; +function getResponseBody(method, response) { + if (method === "HEAD") { + return null; + } + return response.body || new ReadableStream(); +} +function isUserWorkerFirst(runWorkerFirst, pathname) { + if (!Array.isArray(runWorkerFirst)) { + return runWorkerFirst ?? false; + } + let hasPositiveMatch = false; + for (let rule of runWorkerFirst) { + let isPositiveRule = true; + if (rule.startsWith("!")) { + rule = rule.slice(1); + isPositiveRule = false; + } else if (hasPositiveMatch) { + continue; + } + const match = new RegExp(`^${rule.replace(/([[\]().*+?^$|{}\\])/g, "\\$1").replace("\\*", ".*")}$`).test(pathname); + if (match) { + if (isPositiveRule) { + hasPositiveMatch = true; + } else { + return false; + } + } + } + return hasPositiveMatch; +} +var asset_resolver_default = resolver; + +// node_modules/@opennextjs/cloudflare/dist/api/config.js +function defineCloudflareConfig(config = {}) { + const { incrementalCache, tagCache, queue, cachePurge, enableCacheInterception = false, routePreloadingBehavior = "none" } = config; + return { + default: { + override: { + wrapper: "cloudflare-node", + converter: "edge", + proxyExternalRequest: "fetch", + incrementalCache: resolveIncrementalCache(incrementalCache), + tagCache: resolveTagCache(tagCache), + queue: resolveQueue(queue), + cdnInvalidation: resolveCdnInvalidation(cachePurge) + }, + routePreloadingBehavior + }, + // node:crypto is used to compute cache keys + edgeExternals: ["node:crypto"], + cloudflare: { + useWorkerdCondition: true + }, + dangerous: { + enableCacheInterception + }, + middleware: { + external: true, + override: { + wrapper: "cloudflare-edge", + converter: "edge", + proxyExternalRequest: "fetch", + incrementalCache: resolveIncrementalCache(incrementalCache), + tagCache: resolveTagCache(tagCache), + queue: resolveQueue(queue) + }, + assetResolver: () => asset_resolver_default + } + }; +} +function resolveIncrementalCache(value = "dummy") { + if (typeof value === "string") { + return value; + } + return typeof value === "function" ? value : () => value; +} +function resolveTagCache(value = "dummy") { + if (typeof value === "string") { + return value; + } + return typeof value === "function" ? value : () => value; +} +function resolveQueue(value = "dummy") { + if (typeof value === "string") { + return value; + } + return typeof value === "function" ? value : () => value; +} +function resolveCdnInvalidation(value = "dummy") { + if (typeof value === "string") { + return value; + } + return typeof value === "function" ? value : () => value; +} + +// open-next.config.ts +var open_next_config_default = defineCloudflareConfig(); +export { + open_next_config_default as default +}; diff --git a/open-next.config.ts b/open-next.config.ts @@ -0,0 +1,3 @@ +import { defineCloudflareConfig } from "@opennextjs/cloudflare"; + +export default defineCloudflareConfig(); diff --git a/package.json b/package.json @@ -5,26 +5,33 @@ "scripts": { "dev": "next dev --turbopack", "build": "next build --turbopack", + "build:pages": "opennextjs-cloudflare build", "testbuild": "cd ./wasm-webtools && wasm-pack build --release --target web && cd .. && next build --turbopack", "start": "next start", "lint": "biome check", - "format": "biome format --write" + "format": "biome format --write", + "preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview", + "deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy" }, "dependencies": { - "@sentry/nextjs": "^10.52.0", - "next": "16.2.10", - "react": "19.2.7", - "react-dom": "19.2.7" + "@sentry/nextjs": "^10.68.0", + "next": "16.2.12", + "react": "19.2.8", + "react-dom": "19.2.8", + "@opennextjs/cloudflare": "^1.20.2" }, "devDependencies": { "typescript": "^6.0.3", - "@types/node": "^26.0.0", - "@types/react": "^19.2.14", + "@types/node": "^26.1.2", + "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", - "@biomejs/biome": "2.5.2" + "@biomejs/biome": "2.5.6", + "wrangler": "^4.114.0" }, "trustedDependencies": [ "@sentry/cli", - "sharp" + "esbuild", + "sharp", + "workerd" ] } diff --git a/wrangler.jsonc b/wrangler.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "./node_modules/wrangler/config-schema.json", + "main": ".open-next/worker.js", + "name": "webtools", + "compatibility_date": "2026-07-28", + "compatibility_flags": [ + "nodejs_compat" + ], + "assets": { + "directory": ".open-next/assets", + "binding": "ASSETS" + } +}