this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

use `wrangler r2 bulk put` for R2 cache population (#991)

authored by

Victor Berchet and committed by
GitHub
b95e7cad 7064bcb1

+176 -546
+5
.changeset/beige-aliens-tickle.md
··· 1 + --- 2 + "@opennextjs/cloudflare": minor 3 + --- 4 + 5 + use `wrangler r2 bulk put` for R2 cache population
-27
packages/cloudflare/README.md
··· 55 55 # or 56 56 bun opennextjs-cloudflare build && bun opennextjs-cloudflare deploy 57 57 ``` 58 - 59 - ### Batch Cache Population (Optional, Recommended) 60 - 61 - For improved performance with large caches, you can enable batch upload by providing R2 credentials via .env or environment variables. 62 - 63 - Create a `.env` file in your project root (automatically loaded by the CLI): 64 - 65 - ```bash 66 - R2_ACCESS_KEY_ID=your_access_key_id 67 - R2_SECRET_ACCESS_KEY=your_secret_access_key 68 - CF_ACCOUNT_ID=your_account_id 69 - ``` 70 - 71 - You can also set the environment variables for CI builds. 72 - 73 - **Note:** 74 - 75 - You can follow documentation https://developers.cloudflare.com/r2/api/tokens/ for creating API tokens with appropriate permissions for R2 access. 76 - 77 - **Benefits:** 78 - 79 - - Significantly faster uploads for large caches using parallel transfers 80 - - Reduced API calls to Cloudflare 81 - - Automatically enabled when credentials are provided 82 - 83 - **Fallback:** 84 - If these environment variables are not set, the CLI will use standard Wrangler uploads. Both methods work correctly - batch upload is simply faster for large caches.
-2
packages/cloudflare/package.json
··· 55 55 "@ast-grep/napi": "0.40.0", 56 56 "@dotenvx/dotenvx": "catalog:", 57 57 "@opennextjs/aws": "3.8.5", 58 - "@types/rclone.js": "^0.6.3", 59 58 "cloudflare": "^4.4.1", 60 59 "enquirer": "^2.4.1", 61 60 "glob": "catalog:", 62 - "rclone.js": "^0.6.6", 63 61 "ts-tqdm": "^0.8.6", 64 62 "yargs": "catalog:" 65 63 },
+1 -6
packages/cloudflare/src/api/cloudflare-context.ts
··· 79 79 CF_PREVIEW_DOMAIN?: string; 80 80 // Should have the `Workers Scripts:Read` permission 81 81 CF_WORKERS_SCRIPTS_API_TOKEN?: string; 82 - 83 - // Cloudflare account id - needed for skew protection and R2 batch population 82 + // Cloudflare account id - needed for skew protection 84 83 CF_ACCOUNT_ID?: string; 85 - 86 - // R2 API credentials for batch cache population (optional, enables faster uploads) 87 - R2_ACCESS_KEY_ID?: string; 88 - R2_SECRET_ACCESS_KEY?: string; 89 84 } 90 85 } 91 86
+40 -182
packages/cloudflare/src/cli/commands/populate-cache.spec.ts
··· 78 78 quoteShellMeta: vi.fn((s) => s), 79 79 })); 80 80 81 - // Mock rclone.js promises API to simulate successful copy operations by default 82 - vi.mock("rclone.js", () => ({ 83 - default: { 84 - promises: { 85 - copy: vi.fn(() => Promise.resolve("")), 86 - }, 87 - }, 88 - })); 89 - 90 81 describe("populateCache", () => { 91 - // Test fixtures 92 - const createTestBuildOptions = (): BuildOptions => 93 - ({ 94 - outputDir: "/test/output", 95 - }) as BuildOptions; 96 - 97 - const createTestOpenNextConfig = () => ({ 98 - default: { 99 - override: { 100 - incrementalCache: "cf-r2-incremental-cache", 101 - }, 102 - }, 103 - }); 104 - 105 - const createTestWranglerConfig = () => ({ 106 - r2_buckets: [ 107 - { 108 - binding: "NEXT_INC_CACHE_R2_BUCKET", 109 - bucket_name: "test-bucket", 110 - }, 111 - ], 112 - }); 113 - 114 - const createTestPopulateCacheOptions = () => ({ 115 - target: "local" as const, 116 - shouldUsePreviewId: false, 117 - }); 118 - 119 82 const setupMockFileSystem = () => { 120 83 mockFs({ 121 84 "/test/output": { ··· 132 95 }); 133 96 }; 134 97 135 - describe("R2 incremental cache", () => { 136 - afterEach(() => { 137 - mockFs.restore(); 138 - vi.unstubAllEnvs(); 139 - }); 140 - 141 - test("uses sequential upload for local target (skips batch upload)", async () => { 142 - const { runWrangler } = await import("../utils/run-wrangler.js"); 143 - const rcloneModule = (await import("rclone.js")).default; 144 - 145 - setupMockFileSystem(); 146 - vi.mocked(runWrangler).mockClear(); 147 - vi.mocked(rcloneModule.promises.copy).mockClear(); 148 - 149 - // Test with local target - should skip batch upload even with credentials 150 - await populateCache( 151 - createTestBuildOptions(), 152 - createTestOpenNextConfig() as any, // eslint-disable-line @typescript-eslint/no-explicit-any 153 - createTestWranglerConfig() as any, // eslint-disable-line @typescript-eslint/no-explicit-any 154 - { target: "local" as const, shouldUsePreviewId: false }, 155 - { 156 - R2_ACCESS_KEY_ID: "test_access_key", 157 - R2_SECRET_ACCESS_KEY: "test_secret_key", 158 - CF_ACCOUNT_ID: "test_account_id", 159 - } as any // eslint-disable-line @typescript-eslint/no-explicit-any 160 - ); 161 - 162 - // Should use sequential upload (runWrangler), not batch upload (rclone.js) 163 - expect(runWrangler).toHaveBeenCalled(); 164 - expect(rcloneModule.promises.copy).not.toHaveBeenCalled(); 165 - }); 166 - 167 - test("uses sequential upload when R2 credentials are not provided", async () => { 168 - const { runWrangler } = await import("../utils/run-wrangler.js"); 169 - const rcloneModule = (await import("rclone.js")).default; 170 - 171 - setupMockFileSystem(); 172 - vi.mocked(runWrangler).mockClear(); 173 - vi.mocked(rcloneModule.promises.copy).mockClear(); 174 - 175 - // Test uses partial types for simplicity - full config not needed 176 - // Pass empty envVars to simulate no R2 credentials 177 - await populateCache( 178 - createTestBuildOptions(), 179 - createTestOpenNextConfig() as any, // eslint-disable-line @typescript-eslint/no-explicit-any 180 - createTestWranglerConfig() as any, // eslint-disable-line @typescript-eslint/no-explicit-any 181 - createTestPopulateCacheOptions(), 182 - {} as any // eslint-disable-line @typescript-eslint/no-explicit-any 183 - ); 184 - 185 - expect(runWrangler).toHaveBeenCalled(); 186 - expect(rcloneModule.promises.copy).not.toHaveBeenCalled(); 187 - }); 188 - 189 - test("uses batch upload with temporary config for remote target when R2 credentials are provided", async () => { 190 - const rcloneModule = (await import("rclone.js")).default; 191 - 192 - setupMockFileSystem(); 193 - vi.mocked(rcloneModule.promises.copy).mockClear(); 194 - 195 - // Test uses partial types for simplicity - full config not needed 196 - // Pass envVars with R2 credentials and remote target to enable batch upload 197 - await populateCache( 198 - createTestBuildOptions(), 199 - createTestOpenNextConfig() as any, // eslint-disable-line @typescript-eslint/no-explicit-any 200 - createTestWranglerConfig() as any, // eslint-disable-line @typescript-eslint/no-explicit-any 201 - { target: "remote" as const, shouldUsePreviewId: false }, 202 - { 203 - R2_ACCESS_KEY_ID: "test_access_key", 204 - R2_SECRET_ACCESS_KEY: "test_secret_key", 205 - CF_ACCOUNT_ID: "test_account_id", 206 - } as any // eslint-disable-line @typescript-eslint/no-explicit-any 207 - ); 208 - 209 - // Verify batch upload was used with correct parameters and temporary config 210 - expect(rcloneModule.promises.copy).toHaveBeenCalledWith( 211 - expect.any(String), // staging directory 212 - "r2:test-bucket", 213 - expect.objectContaining({ 214 - progress: true, 215 - transfers: 16, 216 - checkers: 8, 217 - env: expect.objectContaining({ 218 - RCLONE_CONFIG: expect.stringMatching(/rclone-config-\d+\.conf$/), 219 - }), 220 - }) 221 - ); 222 - }); 98 + describe.each([{ target: "local" as const }, { target: "remote" as const }])( 99 + "R2 incremental cache", 100 + ({ target }) => { 101 + afterEach(() => { 102 + mockFs.restore(); 103 + }); 223 104 224 - test("handles rclone errors with status > 0 for remote target", async () => { 225 - const { runWrangler } = await import("../utils/run-wrangler.js"); 226 - const rcloneModule = (await import("rclone.js")).default; 105 + test(target, async () => { 106 + const { runWrangler } = await import("../utils/run-wrangler.js"); 227 107 228 - setupMockFileSystem(); 108 + setupMockFileSystem(); 109 + vi.mocked(runWrangler).mockClear(); 229 110 230 - // Mock rclone failure - Promise rejection 231 - vi.mocked(rcloneModule.promises.copy).mockRejectedValueOnce( 232 - new Error("rclone copy failed with exit code 7") 233 - ); 111 + await populateCache( 112 + { 113 + outputDir: "/test/output", 114 + } as BuildOptions, 115 + { 116 + default: { 117 + override: { 118 + incrementalCache: "cf-r2-incremental-cache", 119 + }, 120 + }, 121 + } as any, // eslint-disable-line @typescript-eslint/no-explicit-any 122 + { 123 + r2_buckets: [ 124 + { 125 + binding: "NEXT_INC_CACHE_R2_BUCKET", 126 + bucket_name: "test-bucket", 127 + }, 128 + ], 129 + } as any, // eslint-disable-line @typescript-eslint/no-explicit-any 130 + { target, shouldUsePreviewId: false }, 131 + {} as any // eslint-disable-line @typescript-eslint/no-explicit-any 132 + ); 234 133 235 - vi.mocked(runWrangler).mockClear(); 236 - 237 - // Pass envVars with R2 credentials and remote target to enable batch upload (which will fail) 238 - await populateCache( 239 - createTestBuildOptions(), 240 - createTestOpenNextConfig() as any, // eslint-disable-line @typescript-eslint/no-explicit-any 241 - createTestWranglerConfig() as any, // eslint-disable-line @typescript-eslint/no-explicit-any 242 - { target: "remote" as const, shouldUsePreviewId: false }, 243 - { 244 - R2_ACCESS_KEY_ID: "test_access_key", 245 - R2_SECRET_ACCESS_KEY: "test_secret_key", 246 - CF_ACCOUNT_ID: "test_account_id", 247 - } as any // eslint-disable-line @typescript-eslint/no-explicit-any 248 - ); 249 - 250 - // Should fall back to sequential upload when batch upload fails 251 - expect(runWrangler).toHaveBeenCalled(); 252 - }); 253 - 254 - test("handles rclone errors with stderr output for remote target", async () => { 255 - const { runWrangler } = await import("../utils/run-wrangler.js"); 256 - const rcloneModule = (await import("rclone.js")).default; 257 - 258 - setupMockFileSystem(); 259 - 260 - // Mock rclone error - Promise rejection with stderr message 261 - vi.mocked(rcloneModule.promises.copy).mockRejectedValueOnce( 262 - new Error("ERROR : Failed to copy: AccessDenied: Access Denied (403)") 263 - ); 264 - 265 - vi.mocked(runWrangler).mockClear(); 266 - 267 - // Pass envVars with R2 credentials and remote target to enable batch upload (which will fail) 268 - await populateCache( 269 - createTestBuildOptions(), 270 - createTestOpenNextConfig() as any, // eslint-disable-line @typescript-eslint/no-explicit-any 271 - createTestWranglerConfig() as any, // eslint-disable-line @typescript-eslint/no-explicit-any 272 - { target: "remote" as const, shouldUsePreviewId: false }, 273 - { 274 - R2_ACCESS_KEY_ID: "test_access_key", 275 - R2_SECRET_ACCESS_KEY: "test_secret_key", 276 - CF_ACCOUNT_ID: "test_account_id", 277 - } as any // eslint-disable-line @typescript-eslint/no-explicit-any 278 - ); 279 - 280 - // Should fall back to standard upload when batch upload fails 281 - expect(runWrangler).toHaveBeenCalled(); 282 - }); 283 - }); 134 + expect(runWrangler).toHaveBeenCalledWith( 135 + expect.anything(), 136 + expect.arrayContaining(["r2 bulk put", "test-bucket"]), 137 + expect.objectContaining({ target }) 138 + ); 139 + }); 140 + } 141 + ); 284 142 });
+51 -200
packages/cloudflare/src/cli/commands/populate-cache.ts
··· 1 - import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; 2 - import { tmpdir } from "node:os"; 1 + import fs from "node:fs"; 2 + import fsp from "node:fs/promises"; 3 + import os from "node:os"; 3 4 import path from "node:path"; 4 5 5 6 import type { BuildOptions } from "@opennextjs/aws/build/helper.js"; ··· 12 13 } from "@opennextjs/aws/types/open-next.js"; 13 14 import type { IncrementalCache, TagCache } from "@opennextjs/aws/types/overrides.js"; 14 15 import { globSync } from "glob"; 15 - import rclone from "rclone.js"; 16 16 import { tqdm } from "ts-tqdm"; 17 17 import type { Unstable_Config as WranglerConfig } from "wrangler"; 18 18 import type yargs from "yargs"; ··· 91 91 ) { 92 92 const { incrementalCache, tagCache } = config.default.override ?? {}; 93 93 94 - if (!existsSync(buildOpts.outputDir)) { 94 + if (!fs.existsSync(buildOpts.outputDir)) { 95 95 logger.error("Unable to populate cache: Open Next build not found"); 96 96 process.exit(1); 97 97 } ··· 143 143 windowsPathsNoEscape: true, 144 144 }).filter((f) => f.isFile()); 145 145 146 + const baseCacheDir = path.join(opts.outputDir, "cache"); 146 147 const assets: CacheAsset[] = []; 147 148 148 149 for (const file of allFiles) { 149 150 const fullPath = file.fullpath(); 150 - const relativePath = normalizePath(path.relative(path.join(opts.outputDir, "cache"), fullPath)); 151 + const relativePath = normalizePath(path.relative(baseCacheDir, fullPath)); 151 152 152 153 if (relativePath.startsWith("__fetch")) { 153 154 const [__fetch, buildId, ...keyParts] = relativePath.split("/"); ··· 197 198 /** 198 199 * Chunk sizes to use when populating KV cache. Ignored for R2. 199 200 * 200 - * @default 25 for KV 201 + * @default 25 for KV, 50 for R2 201 202 */ 202 203 cacheChunkSize?: number; 203 204 /** ··· 206 207 shouldUsePreviewId: boolean; 207 208 }; 208 209 209 - /** 210 - * Create a temporary configuration file for batch upload from environment variables 211 - * @returns Path to the temporary config file or null if env vars not available 212 - */ 213 - function createTempRcloneConfig(accessKey: string, secretKey: string, accountId: string): string | null { 214 - const tempDir = tmpdir(); 215 - const tempConfigPath = path.join(tempDir, `rclone-config-${Date.now()}.conf`); 216 - 217 - const configContent = `[r2] 218 - type = s3 219 - provider = Cloudflare 220 - access_key_id = ${accessKey} 221 - secret_access_key = ${secretKey} 222 - endpoint = https://${accountId}.r2.cloudflarestorage.com 223 - acl = private 224 - `; 225 - 226 - /** 227 - * 0o600 is an octal number (the 0o prefix indicates octal in JavaScript) 228 - * that represents Unix file permissions: 229 - * 230 - * - 6 (owner): read (4) + write (2) = readable and writable by the file owner 231 - * - 0 (group): no permissions for the group 232 - * - 0 (others): no permissions for anyone else 233 - * 234 - * In symbolic notation, this is: rw------- 235 - */ 236 - writeFileSync(tempConfigPath, configContent, { mode: 0o600 }); 237 - return tempConfigPath; 238 - } 239 - 240 - /** 241 - * Populate R2 incremental cache using batch upload for better performance 242 - * Uses parallel transfers to significantly speed up cache population 243 - */ 244 - async function populateR2IncrementalCacheWithBatchUpload( 245 - bucket: string, 246 - prefix: string | undefined, 247 - assets: CacheAsset[], 248 - envVars: WorkerEnvVar 249 - ) { 250 - const accessKey = envVars.R2_ACCESS_KEY_ID || null; 251 - const secretKey = envVars.R2_SECRET_ACCESS_KEY || null; 252 - const accountId = envVars.CF_ACCOUNT_ID || null; 253 - 254 - // Ensure all required env vars are set correctly 255 - if (!accessKey || !secretKey || !accountId) { 256 - throw new Error( 257 - "Please set R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, and CF_ACCOUNT_ID environment variables to enable faster batch upload for remote R2." 258 - ); 259 - } 260 - 261 - logger.info("\nPopulating remote R2 incremental cache using batch upload..."); 262 - 263 - // Create temporary config from env vars - required for batch upload 264 - const tempConfigPath = createTempRcloneConfig(accessKey, secretKey, accountId); 265 - if (!tempConfigPath) { 266 - throw new Error("Failed to create temporary rclone config for R2 batch upload."); 267 - } 268 - 269 - const env = { 270 - ...process.env, 271 - RCLONE_CONFIG: tempConfigPath, 272 - }; 273 - 274 - logger.info("Using batch upload with R2 credentials from environment variables"); 275 - 276 - // Create a staging dir in temp directory with proper key paths 277 - const tempDir = tmpdir(); 278 - const stagingDir = path.join(tempDir, `.r2-staging-${Date.now()}`); 279 - 280 - // Track success to ensure cleanup happens correctly 281 - let success = null; 282 - 283 - try { 284 - mkdirSync(stagingDir, { recursive: true }); 285 - 286 - for (const { fullPath, key, buildId, isFetch } of assets) { 287 - const cacheKey = computeCacheKey(key, { 288 - prefix, 289 - buildId, 290 - cacheType: isFetch ? "fetch" : "cache", 291 - }); 292 - const destPath = path.join(stagingDir, cacheKey); 293 - mkdirSync(path.dirname(destPath), { recursive: true }); 294 - copyFileSync(fullPath, destPath); 295 - } 296 - 297 - // Use rclone.js to sync the R2 298 - const remote = `r2:${bucket}`; 299 - 300 - // Using rclone.js Promise-based API for the copy operation 301 - await rclone.promises.copy(stagingDir, remote, { 302 - progress: true, 303 - transfers: 16, 304 - checkers: 8, 305 - env, 306 - }); 307 - 308 - logger.info(`Successfully uploaded ${assets.length} assets to R2 using batch upload`); 309 - success = true; 310 - } finally { 311 - try { 312 - // Cleanup temporary staging directory 313 - rmSync(stagingDir, { recursive: true, force: true }); 314 - } catch { 315 - console.warn(`Failed to remove temporary staging directory at ${stagingDir}`); 316 - } 317 - 318 - try { 319 - // Cleanup temporary config file 320 - rmSync(tempConfigPath); 321 - } catch { 322 - console.warn(`Failed to remove temporary config at ${tempConfigPath}`); 323 - } 324 - } 325 - 326 - if (!success) { 327 - throw new Error("R2 batch upload failed, falling back to sequential uploads..."); 328 - } 329 - } 330 - 331 - /** 332 - * Populate R2 incremental cache using sequential Wrangler uploads 333 - * Falls back to this method when batch upload is not available or fails 334 - */ 335 - async function populateR2IncrementalCacheWithSequentialUpload( 336 - buildOpts: BuildOptions, 337 - bucket: string, 338 - prefix: string | undefined, 339 - assets: CacheAsset[], 340 - populateCacheOptions: PopulateCacheOptions 341 - ) { 342 - logger.info("Using sequential cache uploads."); 343 - 344 - for (const { fullPath, key, buildId, isFetch } of tqdm(assets)) { 345 - const cacheKey = computeCacheKey(key, { 346 - prefix, 347 - buildId, 348 - cacheType: isFetch ? "fetch" : "cache", 349 - }); 350 - runWrangler( 351 - buildOpts, 352 - [ 353 - "r2 object put", 354 - quoteShellMeta(normalizePath(path.join(bucket, cacheKey))), 355 - `--file ${quoteShellMeta(fullPath)}`, 356 - ], 357 - { 358 - target: populateCacheOptions.target, 359 - configPath: populateCacheOptions.wranglerConfigPath, 360 - // R2 does not support the environment flag and results in the following error: 361 - // Incorrect type for the 'cacheExpiry' field on 'HttpMetadata': the provided value is not of type 'date'. 362 - environment: undefined, 363 - logging: "error", 364 - } 365 - ); 366 - } 367 - logger.info(`Successfully populated cache with ${assets.length} assets`); 368 - } 369 - 370 210 async function populateR2IncrementalCache( 371 211 buildOpts: BuildOptions, 372 212 config: WranglerConfig, ··· 375 215 ) { 376 216 logger.info("\nPopulating R2 incremental cache..."); 377 217 378 - const binding = config.r2_buckets.find(({ binding }) => binding === R2_CACHE_BINDING_NAME); 218 + const binding = config.r2_buckets.find( 219 + ({ binding }: { binding: string }) => binding === R2_CACHE_BINDING_NAME 220 + ); 379 221 if (!binding) { 380 222 throw new Error(`No R2 binding ${JSON.stringify(R2_CACHE_BINDING_NAME)} found!`); 381 223 } ··· 389 231 390 232 const assets = getCacheAssets(buildOpts); 391 233 392 - // Force sequential upload for local target 393 - if (populateCacheOptions.target === "local") { 394 - logger.info("Using sequential upload for local R2 (batch upload only works with remote R2)"); 395 - return await populateR2IncrementalCacheWithSequentialUpload( 396 - buildOpts, 397 - bucket, 234 + const objectList = assets.map(({ fullPath, key, buildId, isFetch }) => ({ 235 + key: computeCacheKey(key, { 398 236 prefix, 399 - assets, 400 - populateCacheOptions 401 - ); 402 - } 237 + buildId, 238 + cacheType: isFetch ? "fetch" : "cache", 239 + }), 240 + file: fullPath, 241 + })); 403 242 404 - try { 405 - // Attempt batch upload first (using rclone) - only for remote target 406 - return await populateR2IncrementalCacheWithBatchUpload(bucket, prefix, assets, envVars); 407 - } catch (error) { 408 - logger.warn(`Batch upload failed: ${error instanceof Error ? error.message : error}`); 409 - logger.info("Falling back to sequential uploads..."); 243 + const tempDir = await fsp.mkdtemp(path.join(os.tmpdir(), "open-next-")); 244 + const listFile = path.join(tempDir, `r2-bulk-list.json`); 245 + fs.writeFileSync(listFile, JSON.stringify(objectList)); 246 + 247 + const concurrency = Math.max(1, populateCacheOptions.cacheChunkSize ?? 50); 410 248 411 - // Sequential upload fallback (using Wrangler) 412 - return await populateR2IncrementalCacheWithSequentialUpload( 413 - buildOpts, 414 - bucket, 415 - prefix, 416 - assets, 417 - populateCacheOptions 418 - ); 419 - } 249 + runWrangler( 250 + buildOpts, 251 + ["r2 bulk put", bucket, `--filename ${quoteShellMeta(listFile)}`, `--concurrency ${concurrency}`], 252 + { 253 + target: populateCacheOptions.target, 254 + configPath: populateCacheOptions.wranglerConfigPath, 255 + // R2 does not support the environment flag and results in the following error: 256 + // Incorrect type for the 'cacheExpiry' field on 'HttpMetadata': the provided value is not of type 'date'. 257 + environment: undefined, 258 + logging: "error", 259 + } 260 + ); 261 + 262 + fs.rmSync(listFile, { force: true }); 263 + 264 + logger.info(`Successfully populated cache with ${assets.length} assets`); 420 265 } 421 266 422 267 async function populateKVIncrementalCache( ··· 427 272 ) { 428 273 logger.info("\nPopulating KV incremental cache..."); 429 274 430 - const binding = config.kv_namespaces.find(({ binding }) => binding === KV_CACHE_BINDING_NAME); 275 + const binding = config.kv_namespaces.find( 276 + ({ binding }: { binding: string }) => binding === KV_CACHE_BINDING_NAME 277 + ); 431 278 if (!binding) { 432 279 throw new Error(`No KV binding ${JSON.stringify(KV_CACHE_BINDING_NAME)} found!`); 433 280 } ··· 441 288 442 289 logger.info(`Inserting ${assets.length} assets to KV in chunks of ${chunkSize}`); 443 290 291 + const tempDir = await fsp.mkdtemp(path.join(os.tmpdir(), "open-next-")); 292 + 444 293 for (const i of tqdm(Array.from({ length: totalChunks }, (_, i) => i))) { 445 - const chunkPath = path.join(buildOpts.outputDir, "cloudflare", `cache-chunk-${i}.json`); 294 + const chunkPath = path.join(tempDir, `cache-chunk-${i}.json`); 446 295 447 296 const kvMapping = assets 448 297 .slice(i * chunkSize, (i + 1) * chunkSize) ··· 452 301 buildId, 453 302 cacheType: isFetch ? "fetch" : "cache", 454 303 }), 455 - value: readFileSync(fullPath, "utf8"), 304 + value: fs.readFileSync(fullPath, "utf8"), 456 305 })); 457 306 458 - writeFileSync(chunkPath, JSON.stringify(kvMapping)); 307 + fs.writeFileSync(chunkPath, JSON.stringify(kvMapping)); 459 308 460 309 runWrangler( 461 310 buildOpts, ··· 473 322 } 474 323 ); 475 324 476 - rmSync(chunkPath); 325 + fs.rmSync(chunkPath, { force: true }); 477 326 } 478 327 479 328 logger.info(`Successfully populated cache with ${assets.length} assets`); ··· 486 335 ) { 487 336 logger.info("\nCreating D1 table if necessary..."); 488 337 489 - const binding = config.d1_databases.find(({ binding }) => binding === D1_TAG_BINDING_NAME); 338 + const binding = config.d1_databases.find( 339 + ({ binding }: { binding: string }) => binding === D1_TAG_BINDING_NAME 340 + ); 490 341 if (!binding) { 491 342 throw new Error(`No D1 binding ${JSON.stringify(D1_TAG_BINDING_NAME)} found!`); 492 343 } ··· 513 364 function populateStaticAssetsIncrementalCache(options: BuildOptions) { 514 365 logger.info("\nPopulating Workers static assets..."); 515 366 516 - cpSync( 367 + fs.cpSync( 517 368 path.join(options.outputDir, "cache"), 518 369 path.join(options.outputDir, "assets", STATIC_ASSETS_CACHE_DIR), 519 370 { recursive: true }
+78 -128
pnpm-lock.yaml
··· 85 85 specifier: ^2.1.1 86 86 version: 2.1.1 87 87 wrangler: 88 - specifier: ^4.38.0 89 - version: 4.38.0 88 + specifier: ^4.49.0 89 + version: 4.49.0 90 90 yargs: 91 91 specifier: ^18.0.0 92 92 version: 18.0.0 ··· 198 198 version: 5.7.3 199 199 wrangler: 200 200 specifier: 'catalog:' 201 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 201 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 202 202 203 203 examples/bugs/gh-219: 204 204 dependencies: ··· 325 325 version: 39.4.2(rollup@4.40.1) 326 326 wrangler: 327 327 specifier: 'catalog:' 328 - version: 4.38.0(@cloudflare/workers-types@4.20250109.0) 328 + version: 4.49.0(@cloudflare/workers-types@4.20250109.0) 329 329 330 330 examples/bugs/gh-223: 331 331 dependencies: ··· 380 380 version: 5.7.3 381 381 wrangler: 382 382 specifier: 'catalog:' 383 - version: 4.38.0(@cloudflare/workers-types@4.20250109.0) 383 + version: 4.49.0(@cloudflare/workers-types@4.20250109.0) 384 384 385 385 examples/create-next-app: 386 386 dependencies: ··· 426 426 version: 5.9.3 427 427 wrangler: 428 428 specifier: 'catalog:' 429 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 429 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 430 430 431 431 examples/e2e/app-pages-router: 432 432 dependencies: ··· 472 472 version: 5.9.3 473 473 wrangler: 474 474 specifier: 'catalog:' 475 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 475 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 476 476 477 477 examples/e2e/app-router: 478 478 dependencies: ··· 518 518 version: 5.9.3 519 519 wrangler: 520 520 specifier: 'catalog:' 521 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 521 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 522 522 523 523 examples/e2e/experimental: 524 524 dependencies: ··· 552 552 version: 5.9.3 553 553 wrangler: 554 554 specifier: 'catalog:' 555 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 555 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 556 556 557 557 examples/e2e/pages-router: 558 558 dependencies: ··· 598 598 version: 5.9.3 599 599 wrangler: 600 600 specifier: 'catalog:' 601 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 601 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 602 602 603 603 examples/e2e/shared: 604 604 dependencies: ··· 657 657 version: 5.9.3 658 658 wrangler: 659 659 specifier: 'catalog:' 660 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 660 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 661 661 662 662 examples/next-partial-prerendering: 663 663 dependencies: ··· 718 718 version: 5.5.3 719 719 wrangler: 720 720 specifier: 'catalog:' 721 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 721 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 722 722 723 723 examples/overrides/d1-tag-next: 724 724 dependencies: ··· 752 752 version: 5.9.3 753 753 wrangler: 754 754 specifier: 'catalog:' 755 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 755 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 756 756 757 757 examples/overrides/kv-tag-next: 758 758 dependencies: ··· 786 786 version: 5.9.3 787 787 wrangler: 788 788 specifier: 'catalog:' 789 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 789 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 790 790 791 791 examples/overrides/memory-queue: 792 792 dependencies: ··· 820 820 version: 5.9.3 821 821 wrangler: 822 822 specifier: 'catalog:' 823 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 823 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 824 824 825 825 examples/overrides/r2-incremental-cache: 826 826 dependencies: ··· 854 854 version: 5.9.3 855 855 wrangler: 856 856 specifier: 'catalog:' 857 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 857 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 858 858 859 859 examples/overrides/static-assets-incremental-cache: 860 860 dependencies: ··· 888 888 version: 5.9.3 889 889 wrangler: 890 890 specifier: 'catalog:' 891 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 891 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 892 892 893 893 examples/playground14: 894 894 dependencies: ··· 913 913 version: 22.2.0 914 914 wrangler: 915 915 specifier: 'catalog:' 916 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 916 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 917 917 918 918 examples/playground15: 919 919 dependencies: ··· 938 938 version: 22.2.0 939 939 wrangler: 940 940 specifier: 'catalog:' 941 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 941 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 942 942 943 943 examples/prisma: 944 944 dependencies: ··· 978 978 version: 5.9.3 979 979 wrangler: 980 980 specifier: 'catalog:' 981 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 981 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 982 982 983 983 examples/ssg-app: 984 984 dependencies: ··· 1012 1012 version: 5.9.3 1013 1013 wrangler: 1014 1014 specifier: 'catalog:' 1015 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 1015 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 1016 1016 1017 1017 examples/vercel-blog-starter: 1018 1018 dependencies: ··· 1067 1067 version: 5.9.3 1068 1068 wrangler: 1069 1069 specifier: 'catalog:' 1070 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 1070 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 1071 1071 1072 1072 packages/cloudflare: 1073 1073 dependencies: ··· 1080 1080 '@opennextjs/aws': 1081 1081 specifier: 3.8.5 1082 1082 version: 3.8.5 1083 - '@types/rclone.js': 1084 - specifier: ^0.6.3 1085 - version: 0.6.3 1086 1083 cloudflare: 1087 1084 specifier: ^4.4.1 1088 1085 version: 4.4.1 ··· 1092 1089 glob: 1093 1090 specifier: 'catalog:' 1094 1091 version: 12.0.0 1095 - rclone.js: 1096 - specifier: ^0.6.6 1097 - version: 0.6.6 1098 1092 ts-tqdm: 1099 1093 specifier: ^0.8.6 1100 1094 version: 0.8.6 1101 1095 wrangler: 1102 1096 specifier: 'catalog:' 1103 - version: 4.38.0(@cloudflare/workers-types@4.20250924.0) 1097 + version: 4.49.0(@cloudflare/workers-types@4.20250924.0) 1104 1098 yargs: 1105 1099 specifier: 'catalog:' 1106 1100 version: 18.0.0 ··· 1824 1818 resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==} 1825 1819 engines: {node: '>=18.0.0'} 1826 1820 1827 - '@cloudflare/unenv-preset@2.7.4': 1828 - resolution: {integrity: sha512-KIjbu/Dt50zseJIoOOK5y4eYpSojD9+xxkePYVK1Rg9k/p/st4YyMtz1Clju/zrenJHrOH+AAcjNArOPMwH4Bw==} 1821 + '@cloudflare/unenv-preset@2.7.10': 1822 + resolution: {integrity: sha512-mvsNAiJSduC/9yxv1ZpCxwgAXgcuoDvkl8yaHjxoLpFxXy2ugc6TZK20EKgv4yO0vZhAEKwqJm+eGOzf8Oc45w==} 1829 1823 peerDependencies: 1830 - unenv: 2.0.0-rc.21 1831 - workerd: ^1.20250912.0 1824 + unenv: 2.0.0-rc.24 1825 + workerd: ^1.20251106.1 1832 1826 peerDependenciesMeta: 1833 1827 workerd: 1834 1828 optional: true 1835 1829 1836 - '@cloudflare/workerd-darwin-64@1.20250917.0': 1837 - resolution: {integrity: sha512-0kL/kFnKUSycoo7b3PgM0nRyZ+1MGQAKaXtE6a2+SAeUkZ2FLnuFWmASi0s4rlWGsf/rlTw4AwXROePir9dUcQ==} 1830 + '@cloudflare/workerd-darwin-64@1.20251113.0': 1831 + resolution: {integrity: sha512-GBlun9onB69rDMZIJ9TdvQ9d3KS5L2FXwlzJ9+AjrbHqYnIif8eH5wyLPIR5wfkXNyrEFiWtEYses+aUQZQLWQ==} 1838 1832 engines: {node: '>=16'} 1839 1833 cpu: [x64] 1840 1834 os: [darwin] 1841 1835 1842 - '@cloudflare/workerd-darwin-arm64@1.20250917.0': 1843 - resolution: {integrity: sha512-3/N1QmEJsC8Byxt1SGgVp5o0r+eKjuUEMbIL2yzLk/jrMdErPXy/DGf/tXZoACU68a/gMEbbT1itkYrm85iQHg==} 1836 + '@cloudflare/workerd-darwin-arm64@1.20251113.0': 1837 + resolution: {integrity: sha512-CFakh0Plmkwe8BHuc3iQ0rSIMttvJ3XpWYPjhWUL6KxA2zL6FvYUiWG0hpaEW60KPqgFMisBoPzH0kzyzSKMhg==} 1844 1838 engines: {node: '>=16'} 1845 1839 cpu: [arm64] 1846 1840 os: [darwin] 1847 1841 1848 - '@cloudflare/workerd-linux-64@1.20250917.0': 1849 - resolution: {integrity: sha512-E7sEow7CErbWY3olMmlbj6iss9r7Xb2uMyc+MKzYC9/J6yFlJd/dNHvjey9QIdxzbkC9qGe90a+KxQrjs+fspA==} 1842 + '@cloudflare/workerd-linux-64@1.20251113.0': 1843 + resolution: {integrity: sha512-6JqK+Csie47656Kr+48GHl8v1zK940ruOczY4Da67xvsUAsFkUEOQkes4rW5i3I3XjNbcnyf96l0gnI6lzTKlQ==} 1850 1844 engines: {node: '>=16'} 1851 1845 cpu: [x64] 1852 1846 os: [linux] 1853 1847 1854 - '@cloudflare/workerd-linux-arm64@1.20250917.0': 1855 - resolution: {integrity: sha512-roOnRjxut2FUxo6HA9spbfs32naXAsnSQqsgku3iq6BYKv1QqGiFoY5bReK72N5uxmhxo7+RiTo8ZEkxA/vMIQ==} 1848 + '@cloudflare/workerd-linux-arm64@1.20251113.0': 1849 + resolution: {integrity: sha512-Ye9I2AeXZ2IPcWOYD9xIrjcFrnH0UkS/ijvgI81mJCr8w7dMd4ONb+BK5oXUJtBW8IEAV3yRU7Mz7DBLaXe/AQ==} 1856 1850 engines: {node: '>=16'} 1857 1851 cpu: [arm64] 1858 1852 os: [linux] 1859 1853 1860 - '@cloudflare/workerd-windows-64@1.20250917.0': 1861 - resolution: {integrity: sha512-gslh6Ou9+kshHjR1BJX47OsbPw3/cZCvGDompvaW/URCgr7aMzljbgmBb7p0uhwGy1qCXcIt31St6pd3IEcLng==} 1854 + '@cloudflare/workerd-windows-64@1.20251113.0': 1855 + resolution: {integrity: sha512-H2sq6H2sMhjt/72S3ZAsZV+GC3chRJkwwGq7AGVjIehR66e/z5KKhxOX8u29iqtShyERvFUVHyfCzRBKfmJwfw==} 1862 1856 engines: {node: '>=16'} 1863 1857 cpu: [x64] 1864 1858 os: [win32] ··· 5021 5015 '@types/range-parser@1.2.7': 5022 5016 resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} 5023 5017 5024 - '@types/rclone.js@0.6.3': 5025 - resolution: {integrity: sha512-BssKAAVRY//fxGKso8SatyOwiD7X0toDofNnVxZlIXmN7UHrn2UBTxldNAjgUvWA91qJyeEPfKmeJpZVhLugXg==} 5026 - 5027 5018 '@types/react-dom@18.3.0': 5028 5019 resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} 5029 5020 ··· 5327 5318 engines: {node: '>=0.4.0'} 5328 5319 hasBin: true 5329 5320 5330 - adm-zip@0.5.16: 5331 - resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} 5332 - engines: {node: '>=12.0'} 5333 - 5334 5321 agent-base@6.0.2: 5335 5322 resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} 5336 5323 engines: {node: '>= 6.0.0'} ··· 5927 5914 define-properties@1.2.1: 5928 5915 resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} 5929 5916 engines: {node: '>= 0.4'} 5930 - 5931 - defu@6.1.4: 5932 - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} 5933 5917 5934 5918 delayed-stream@1.0.0: 5935 5919 resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} ··· 6690 6674 resolution: {integrity: sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==} 6691 6675 engines: {node: '>= 18'} 6692 6676 6693 - exsolve@1.0.7: 6694 - resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} 6695 - 6696 6677 extend-shallow@2.0.1: 6697 6678 resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} 6698 6679 engines: {node: '>=0.10.0'} ··· 7843 7824 resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} 7844 7825 hasBin: true 7845 7826 7846 - miniflare@4.20250917.0: 7847 - resolution: {integrity: sha512-A7kYEc/Y6ohiiTji4W/qGJj3aJNc/9IMj/6wLy2phD/iMjcoY8t35654gR5mHbMx0AgUolDdr3HOsHB0cYBf+Q==} 7827 + miniflare@4.20251113.0: 7828 + resolution: {integrity: sha512-uaNiBJu8JQY27pcM7Q/E+hLggClmJCQji9uOOBdlZV+aJ7rk7tFnyIvjpLxYJRpryrMjm3aKNeMvE8ham01DtA==} 7848 7829 engines: {node: '>=18.0.0'} 7849 7830 hasBin: true 7850 7831 ··· 8302 8283 obliterator@1.6.1: 8303 8284 resolution: {integrity: sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==} 8304 8285 8305 - ohash@2.0.11: 8306 - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} 8307 - 8308 8286 oidc-token-hash@5.0.3: 8309 8287 resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==} 8310 8288 engines: {node: ^10.13.0 || >=12.0.0} ··· 8767 8745 resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} 8768 8746 hasBin: true 8769 8747 8770 - rclone.js@0.6.6: 8771 - resolution: {integrity: sha512-Dxh34cab/fNjFq5SSm0fYLNkGzG2cQSBy782UW9WwxJCEiVO4cGXkvaXcNlgv817dK8K8PuQ+NHUqSAMMhWujQ==} 8772 - engines: {node: '>=12'} 8773 - cpu: [arm, arm64, mips, mipsel, x32, x64] 8774 - os: [darwin, freebsd, linux, openbsd, sunos, win32] 8775 - hasBin: true 8776 - 8777 8748 react-dom@18.3.1: 8778 8749 resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} 8779 8750 peerDependencies: ··· 9650 9621 resolution: {integrity: sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==} 9651 9622 engines: {node: '>=20.18.1'} 9652 9623 9653 - unenv@2.0.0-rc.21: 9654 - resolution: {integrity: sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==} 9624 + unenv@2.0.0-rc.24: 9625 + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} 9655 9626 9656 9627 unified@11.0.5: 9657 9628 resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} ··· 9890 9861 resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} 9891 9862 engines: {node: '>=0.10.0'} 9892 9863 9893 - workerd@1.20250917.0: 9894 - resolution: {integrity: sha512-0D+wWaccyYQb2Zx2DZDC77YDn9kOpkpGMCgyKgIHilghut5hBQ/adUIEseS4iuIZxBPeFSn6zFtICP0SxZ3z0g==} 9864 + workerd@1.20251113.0: 9865 + resolution: {integrity: sha512-XLQKbzzn6DndOosyCviYc0ExQVKPem5KAqvsgY+yrsJXvQnKXojIG8rtI0JQgKflHD6sHwjjg17P+q4lPvaLGQ==} 9895 9866 engines: {node: '>=16'} 9896 9867 hasBin: true 9897 9868 9898 - wrangler@4.38.0: 9899 - resolution: {integrity: sha512-ITL4VZ4KWs8LMDEttDTrAKLktwtv1NxHBd5QIqHOczvcjnAQr+GQoE6XYQws+w8jlOjDV7KyvbFqAdyRh5om3g==} 9900 - engines: {node: '>=18.0.0'} 9869 + wrangler@4.49.0: 9870 + resolution: {integrity: sha512-AptJADXtZwDiYS5b0G3kYNYnW4fsoInMBxw++eCs5wot1h0Q6B7HXNaoizD11rLGkU03Tpsh49hYqYyT5qkIvg==} 9871 + engines: {node: '>=20.0.0'} 9901 9872 hasBin: true 9902 9873 peerDependencies: 9903 - '@cloudflare/workers-types': ^4.20250917.0 9874 + '@cloudflare/workers-types': ^4.20251113.0 9904 9875 peerDependenciesMeta: 9905 9876 '@cloudflare/workers-types': 9906 9877 optional: true ··· 11691 11662 dependencies: 11692 11663 mime: 3.0.0 11693 11664 11694 - '@cloudflare/unenv-preset@2.7.4(unenv@2.0.0-rc.21)(workerd@1.20250917.0)': 11665 + '@cloudflare/unenv-preset@2.7.10(unenv@2.0.0-rc.24)(workerd@1.20251113.0)': 11695 11666 dependencies: 11696 - unenv: 2.0.0-rc.21 11667 + unenv: 2.0.0-rc.24 11697 11668 optionalDependencies: 11698 - workerd: 1.20250917.0 11669 + workerd: 1.20251113.0 11699 11670 11700 - '@cloudflare/workerd-darwin-64@1.20250917.0': 11671 + '@cloudflare/workerd-darwin-64@1.20251113.0': 11701 11672 optional: true 11702 11673 11703 - '@cloudflare/workerd-darwin-arm64@1.20250917.0': 11674 + '@cloudflare/workerd-darwin-arm64@1.20251113.0': 11704 11675 optional: true 11705 11676 11706 - '@cloudflare/workerd-linux-64@1.20250917.0': 11677 + '@cloudflare/workerd-linux-64@1.20251113.0': 11707 11678 optional: true 11708 11679 11709 - '@cloudflare/workerd-linux-arm64@1.20250917.0': 11680 + '@cloudflare/workerd-linux-arm64@1.20251113.0': 11710 11681 optional: true 11711 11682 11712 - '@cloudflare/workerd-windows-64@1.20250917.0': 11683 + '@cloudflare/workerd-windows-64@1.20251113.0': 11713 11684 optional: true 11714 11685 11715 11686 '@cloudflare/workers-types@4.20250109.0': {} ··· 13525 13496 13526 13497 '@prisma/config@6.7.0': 13527 13498 dependencies: 13528 - esbuild: 0.25.4 13529 - esbuild-register: 3.6.0(esbuild@0.25.4) 13499 + esbuild: 0.27.0 13500 + esbuild-register: 3.6.0(esbuild@0.27.0) 13530 13501 transitivePeerDependencies: 13531 13502 - supports-color 13532 13503 ··· 14707 14678 14708 14679 '@types/range-parser@1.2.7': {} 14709 14680 14710 - '@types/rclone.js@0.6.3': 14711 - dependencies: 14712 - '@types/node': 20.14.10 14713 - 14714 14681 '@types/react-dom@18.3.0': 14715 14682 dependencies: 14716 14683 '@types/react': 19.0.8 ··· 15367 15334 acorn@8.14.1: {} 15368 15335 15369 15336 acorn@8.15.0: {} 15370 - 15371 - adm-zip@0.5.16: {} 15372 15337 15373 15338 agent-base@6.0.2: 15374 15339 dependencies: ··· 15967 15932 has-property-descriptors: 1.0.2 15968 15933 object-keys: 1.1.1 15969 15934 15970 - defu@6.1.4: {} 15971 - 15972 15935 delayed-stream@1.0.0: {} 15973 15936 15974 15937 depd@1.1.2: {} ··· 16318 16281 transitivePeerDependencies: 16319 16282 - supports-color 16320 16283 16321 - esbuild-register@3.6.0(esbuild@0.25.4): 16284 + esbuild-register@3.6.0(esbuild@0.27.0): 16322 16285 dependencies: 16323 16286 debug: 4.4.0 16324 - esbuild: 0.25.4 16287 + esbuild: 0.27.0 16325 16288 transitivePeerDependencies: 16326 16289 - supports-color 16327 16290 ··· 17405 17368 transitivePeerDependencies: 17406 17369 - supports-color 17407 17370 17408 - exsolve@1.0.7: {} 17409 - 17410 17371 extend-shallow@2.0.1: 17411 17372 dependencies: 17412 17373 is-extendable: 0.1.1 ··· 18774 18735 18775 18736 mini-svg-data-uri@1.4.4: {} 18776 18737 18777 - miniflare@4.20250917.0: 18738 + miniflare@4.20251113.0: 18778 18739 dependencies: 18779 18740 '@cspotcode/source-map-support': 0.8.1 18780 18741 acorn: 8.14.0 ··· 18784 18745 sharp: 0.33.5 18785 18746 stoppable: 1.1.0 18786 18747 undici: 7.14.0 18787 - workerd: 1.20250917.0 18748 + workerd: 1.20251113.0 18788 18749 ws: 8.18.0 18789 18750 youch: 4.1.0-beta.10 18790 18751 zod: 3.22.3 ··· 19286 19247 es-object-atoms: 1.1.1 19287 19248 19288 19249 obliterator@1.6.1: {} 19289 - 19290 - ohash@2.0.11: {} 19291 19250 19292 19251 oidc-token-hash@5.0.3: {} 19293 19252 ··· 19810 19769 ini: 1.3.8 19811 19770 minimist: 1.2.8 19812 19771 strip-json-comments: 2.0.1 19813 - 19814 - rclone.js@0.6.6: 19815 - dependencies: 19816 - adm-zip: 0.5.16 19817 - mri: 1.2.0 19818 19772 19819 19773 react-dom@18.3.1(react@18.3.1): 19820 19774 dependencies: ··· 21079 21033 21080 21034 undici@7.14.0: {} 21081 21035 21082 - unenv@2.0.0-rc.21: 21036 + unenv@2.0.0-rc.24: 21083 21037 dependencies: 21084 - defu: 6.1.4 21085 - exsolve: 1.0.7 21086 - ohash: 2.0.11 21087 21038 pathe: 2.0.3 21088 - ufo: 1.6.1 21089 21039 21090 21040 unified@11.0.5: 21091 21041 dependencies: ··· 21368 21318 21369 21319 word-wrap@1.2.5: {} 21370 21320 21371 - workerd@1.20250917.0: 21321 + workerd@1.20251113.0: 21372 21322 optionalDependencies: 21373 - '@cloudflare/workerd-darwin-64': 1.20250917.0 21374 - '@cloudflare/workerd-darwin-arm64': 1.20250917.0 21375 - '@cloudflare/workerd-linux-64': 1.20250917.0 21376 - '@cloudflare/workerd-linux-arm64': 1.20250917.0 21377 - '@cloudflare/workerd-windows-64': 1.20250917.0 21323 + '@cloudflare/workerd-darwin-64': 1.20251113.0 21324 + '@cloudflare/workerd-darwin-arm64': 1.20251113.0 21325 + '@cloudflare/workerd-linux-64': 1.20251113.0 21326 + '@cloudflare/workerd-linux-arm64': 1.20251113.0 21327 + '@cloudflare/workerd-windows-64': 1.20251113.0 21378 21328 21379 - wrangler@4.38.0(@cloudflare/workers-types@4.20250109.0): 21329 + wrangler@4.49.0(@cloudflare/workers-types@4.20250109.0): 21380 21330 dependencies: 21381 21331 '@cloudflare/kv-asset-handler': 0.4.0 21382 - '@cloudflare/unenv-preset': 2.7.4(unenv@2.0.0-rc.21)(workerd@1.20250917.0) 21332 + '@cloudflare/unenv-preset': 2.7.10(unenv@2.0.0-rc.24)(workerd@1.20251113.0) 21383 21333 blake3-wasm: 2.1.5 21384 21334 esbuild: 0.25.4 21385 - miniflare: 4.20250917.0 21335 + miniflare: 4.20251113.0 21386 21336 path-to-regexp: 6.3.0 21387 - unenv: 2.0.0-rc.21 21388 - workerd: 1.20250917.0 21337 + unenv: 2.0.0-rc.24 21338 + workerd: 1.20251113.0 21389 21339 optionalDependencies: 21390 21340 '@cloudflare/workers-types': 4.20250109.0 21391 21341 fsevents: 2.3.3 ··· 21393 21343 - bufferutil 21394 21344 - utf-8-validate 21395 21345 21396 - wrangler@4.38.0(@cloudflare/workers-types@4.20250924.0): 21346 + wrangler@4.49.0(@cloudflare/workers-types@4.20250924.0): 21397 21347 dependencies: 21398 21348 '@cloudflare/kv-asset-handler': 0.4.0 21399 - '@cloudflare/unenv-preset': 2.7.4(unenv@2.0.0-rc.21)(workerd@1.20250917.0) 21349 + '@cloudflare/unenv-preset': 2.7.10(unenv@2.0.0-rc.24)(workerd@1.20251113.0) 21400 21350 blake3-wasm: 2.1.5 21401 21351 esbuild: 0.25.4 21402 - miniflare: 4.20250917.0 21352 + miniflare: 4.20251113.0 21403 21353 path-to-regexp: 6.3.0 21404 - unenv: 2.0.0-rc.21 21405 - workerd: 1.20250917.0 21354 + unenv: 2.0.0-rc.24 21355 + workerd: 1.20251113.0 21406 21356 optionalDependencies: 21407 21357 '@cloudflare/workers-types': 4.20250924.0 21408 21358 fsevents: 2.3.3
+1 -1
pnpm-workspace.yaml
··· 35 35 typescript-eslint: ^8.37.0 36 36 typescript: ^5.9.3 37 37 vitest: ^2.1.1 38 - wrangler: ^4.38.0 38 + wrangler: ^4.49.0 39 39 yargs: ^18.0.0 40 40 41 41 # e2e tests