@opennextjs/cloudflare 1.0.0-beta.3 → 1.0.0

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.
Files changed (56) hide show
  1. package/dist/api/cloudflare-context.d.ts +6 -3
  2. package/dist/api/config.d.ts +28 -1
  3. package/dist/api/config.js +15 -1
  4. package/dist/api/durable-objects/sharded-tag-cache.d.ts +1 -0
  5. package/dist/api/durable-objects/sharded-tag-cache.js +16 -0
  6. package/dist/api/index.d.ts +1 -1
  7. package/dist/api/overrides/incremental-cache/kv-incremental-cache.d.ts +8 -9
  8. package/dist/api/overrides/incremental-cache/kv-incremental-cache.js +14 -14
  9. package/dist/api/overrides/incremental-cache/r2-incremental-cache.d.ts +4 -11
  10. package/dist/api/overrides/incremental-cache/r2-incremental-cache.js +8 -15
  11. package/dist/api/overrides/incremental-cache/regional-cache.d.ts +7 -7
  12. package/dist/api/overrides/incremental-cache/regional-cache.js +16 -13
  13. package/dist/api/overrides/incremental-cache/static-assets-incremental-cache.d.ts +3 -3
  14. package/dist/api/overrides/incremental-cache/static-assets-incremental-cache.js +9 -4
  15. package/dist/api/overrides/internal.d.ts +10 -3
  16. package/dist/api/overrides/internal.js +7 -0
  17. package/dist/api/overrides/tag-cache/d1-next-tag-cache.d.ts +1 -0
  18. package/dist/api/overrides/tag-cache/d1-next-tag-cache.js +20 -0
  19. package/dist/api/overrides/tag-cache/do-sharded-tag-cache.d.ts +10 -4
  20. package/dist/api/overrides/tag-cache/do-sharded-tag-cache.js +58 -17
  21. package/dist/api/overrides/tag-cache/do-sharded-tag-cache.spec.js +9 -7
  22. package/dist/api/overrides/tag-cache/tag-cache-filter.d.ts +26 -0
  23. package/dist/api/overrides/tag-cache/tag-cache-filter.js +48 -0
  24. package/dist/api/overrides/tag-cache/tag-cache-filter.spec.d.ts +1 -0
  25. package/dist/api/overrides/tag-cache/tag-cache-filter.spec.js +97 -0
  26. package/dist/cli/args.d.ts +2 -0
  27. package/dist/cli/args.js +3 -0
  28. package/dist/cli/build/build.d.ts +1 -1
  29. package/dist/cli/build/bundle-server.js +18 -4
  30. package/dist/cli/build/open-next/createServerBundle.js +15 -2
  31. package/dist/cli/build/patches/investigated/patch-cache.d.ts +1 -0
  32. package/dist/cli/build/patches/investigated/patch-cache.js +16 -0
  33. package/dist/cli/build/patches/plugins/eval-manifest.js +1 -1
  34. package/dist/cli/build/patches/plugins/load-manifest.js +1 -1
  35. package/dist/cli/build/patches/plugins/pages-router-context.d.ts +11 -0
  36. package/dist/cli/build/patches/plugins/pages-router-context.js +32 -0
  37. package/dist/cli/build/patches/plugins/wrangler-external.js +2 -1
  38. package/dist/cli/build/utils/ensure-cf-config.d.ts +1 -1
  39. package/dist/cli/build/utils/workerd.d.ts +38 -0
  40. package/dist/cli/build/utils/workerd.js +80 -0
  41. package/dist/cli/build/utils/workerd.spec.d.ts +1 -0
  42. package/dist/cli/build/utils/workerd.spec.js +188 -0
  43. package/dist/cli/commands/deploy.d.ts +2 -1
  44. package/dist/cli/commands/deploy.js +1 -0
  45. package/dist/cli/commands/populate-cache.d.ts +1 -0
  46. package/dist/cli/commands/populate-cache.js +56 -24
  47. package/dist/cli/commands/preview.d.ts +2 -1
  48. package/dist/cli/commands/preview.js +1 -0
  49. package/dist/cli/commands/upload.d.ts +2 -1
  50. package/dist/cli/commands/upload.js +1 -0
  51. package/dist/cli/templates/init.js +3 -0
  52. package/dist/cli/templates/worker.d.ts +3 -1
  53. package/dist/cli/utils/run-wrangler.d.ts +0 -1
  54. package/dist/cli/utils/run-wrangler.js +1 -1
  55. package/package.json +3 -3
  56. package/templates/wrangler.jsonc +1 -1
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ESBuild plugin to handle pages router context.
3
+ *
4
+ * We need to change the import path for the pages router context to use the one provided in `pages-runtime.prod.js`
5
+ */
6
+ import { compareSemver } from "@opennextjs/aws/build/helper.js";
7
+ export function patchPagesRouterContext(buildOpts) {
8
+ const pathRegex = /^.*\/(?<CONTEXT>.*)\.shared-runtime$/;
9
+ const isAfter15 = compareSemver(buildOpts.nextVersion, ">=", "15.0.0");
10
+ const isAfter153 = compareSemver(buildOpts.nextVersion, ">=", "15.3.0");
11
+ const basePath = `next/dist/server/${isAfter15 ? "" : "future/"}route-modules/pages/vendored/contexts/`;
12
+ return {
13
+ name: "pages-router-context",
14
+ setup: (build) => {
15
+ // If we are after 15.3, we don't need to patch the context anymore
16
+ if (isAfter153) {
17
+ return;
18
+ }
19
+ // We need to modify some imports (i.e. https://github.com/vercel/next.js/blob/48540b836642525b38a2cba40a92b4532c553a52/packages/next/src/server/require-hook.ts#L59-L68)
20
+ build.onResolve({ filter: /.*shared-runtime/ }, async ({ path, resolveDir, ...options }) => {
21
+ const match = path.match(pathRegex);
22
+ if (match && match.groups?.CONTEXT) {
23
+ const newPath = `${basePath}${match.groups.CONTEXT}.js`;
24
+ return await build.resolve(newPath, {
25
+ resolveDir,
26
+ ...options,
27
+ });
28
+ }
29
+ });
30
+ },
31
+ };
32
+ }
@@ -19,7 +19,8 @@ export function setWranglerExternal() {
19
19
  name: "wrangler-externals",
20
20
  setup: async (build) => {
21
21
  const namespace = "wrangler-externals-plugin";
22
- build.onResolve({ filter: /(\.bin|\.wasm\?module)$/ }, ({ path, importer }) => {
22
+ //TODO: Ideally in the future we would like to analyze the files in case they are using wasm in a Node way (i.e. WebAssembly.instantiate)
23
+ build.onResolve({ filter: /(\.bin|\.wasm(\?module)?)$/ }, ({ path, importer }) => {
23
24
  return {
24
25
  path: resolve(dirname(importer), path),
25
26
  namespace,
@@ -1,4 +1,4 @@
1
- import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
1
+ import type { OpenNextConfig } from "../../../api/config.js";
2
2
  /**
3
3
  * Ensures open next is configured for cloudflare.
4
4
  *
@@ -0,0 +1,38 @@
1
+ import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
2
+ /**
3
+ * This function transforms the exports (or imports) object from the package.json
4
+ * to only include the build condition if found (e.g. "workerd") and remove everything else.
5
+ * If no build condition is found, it keeps everything as is.
6
+ * It also returns a boolean indicating if the build condition was found.
7
+ * @param conditionMap The exports (or imports) object from the package.json
8
+ * @param condition The build condition to look for
9
+ * @returns An object with the transformed exports and a boolean indicating if the build condition was found
10
+ */
11
+ export declare function transformBuildCondition(conditionMap: {
12
+ [key: string]: unknown;
13
+ }, condition: string): {
14
+ transformedExports: {
15
+ [key: string]: unknown;
16
+ };
17
+ hasBuildCondition: boolean;
18
+ };
19
+ interface PackageJson {
20
+ name: string;
21
+ exports?: {
22
+ [key: string]: unknown;
23
+ };
24
+ imports?: {
25
+ [key: string]: unknown;
26
+ };
27
+ }
28
+ /**
29
+ *
30
+ * @param json The package.json object
31
+ * @returns An object with the transformed package.json and a boolean indicating if the build condition was found
32
+ */
33
+ export declare function transformPackageJson(json: PackageJson): {
34
+ transformed: PackageJson;
35
+ hasBuildCondition: boolean;
36
+ };
37
+ export declare function copyWorkerdPackages(options: BuildOptions, nodePackages: Map<string, string>): Promise<void>;
38
+ export {};
@@ -0,0 +1,80 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { loadConfig } from "@opennextjs/aws/adapters/config/util.js";
4
+ import logger from "@opennextjs/aws/logger.js";
5
+ import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
6
+ /**
7
+ * This function transforms the exports (or imports) object from the package.json
8
+ * to only include the build condition if found (e.g. "workerd") and remove everything else.
9
+ * If no build condition is found, it keeps everything as is.
10
+ * It also returns a boolean indicating if the build condition was found.
11
+ * @param conditionMap The exports (or imports) object from the package.json
12
+ * @param condition The build condition to look for
13
+ * @returns An object with the transformed exports and a boolean indicating if the build condition was found
14
+ */
15
+ export function transformBuildCondition(conditionMap, condition) {
16
+ const transformed = {};
17
+ const hasTopLevelBuildCondition = Object.keys(conditionMap).some((key) => key === condition && typeof conditionMap[key] === "string");
18
+ let hasBuildCondition = hasTopLevelBuildCondition;
19
+ for (const [key, value] of Object.entries(conditionMap)) {
20
+ if (typeof value === "object" && value != null) {
21
+ const { transformedExports, hasBuildCondition: innerBuildCondition } = transformBuildCondition(value, condition);
22
+ transformed[key] = transformedExports;
23
+ hasBuildCondition ||= innerBuildCondition;
24
+ }
25
+ else {
26
+ // If it doesn't have the build condition, we need to keep everything as is
27
+ // If it has the build condition, we need to keep only the build condition
28
+ // and remove everything else
29
+ if (!hasTopLevelBuildCondition) {
30
+ transformed[key] = value;
31
+ }
32
+ else if (key === condition) {
33
+ transformed[key] = value;
34
+ }
35
+ }
36
+ }
37
+ return { transformedExports: transformed, hasBuildCondition };
38
+ }
39
+ /**
40
+ *
41
+ * @param json The package.json object
42
+ * @returns An object with the transformed package.json and a boolean indicating if the build condition was found
43
+ */
44
+ export function transformPackageJson(json) {
45
+ const transformed = structuredClone(json);
46
+ let hasBuildCondition = false;
47
+ if (json.exports) {
48
+ const exp = transformBuildCondition(json.exports, "workerd");
49
+ transformed.exports = exp.transformedExports;
50
+ hasBuildCondition ||= exp.hasBuildCondition;
51
+ }
52
+ if (json.imports) {
53
+ const imp = transformBuildCondition(json.imports, "workerd");
54
+ transformed.imports = imp.transformedExports;
55
+ hasBuildCondition ||= imp.hasBuildCondition;
56
+ }
57
+ return { transformed, hasBuildCondition };
58
+ }
59
+ export async function copyWorkerdPackages(options, nodePackages) {
60
+ const isNodeModuleRegex = getCrossPlatformPathRegex(`.*/node_modules/(?<pkg>.*)`, { escape: false });
61
+ // Copy full external packages when they use "workerd" build condition
62
+ const nextConfig = loadConfig(path.join(options.appBuildOutputPath, ".next"));
63
+ const externalPackages = nextConfig.serverExternalPackages ?? [];
64
+ for (const [src, dst] of nodePackages.entries()) {
65
+ try {
66
+ const pkgJson = JSON.parse(await fs.readFile(path.join(src, "package.json"), "utf8"));
67
+ const { transformed, hasBuildCondition } = transformPackageJson(pkgJson);
68
+ const match = src.match(isNodeModuleRegex);
69
+ if (match?.groups?.pkg && externalPackages.includes(match.groups.pkg) && hasBuildCondition) {
70
+ logger.debug(`Copying package using a workerd condition: ${path.relative(options.appPath, src)} -> ${path.relative(options.appPath, dst)}`);
71
+ await fs.cp(src, dst, { recursive: true, force: true });
72
+ // Overwrite with the transformed package.json
73
+ await fs.writeFile(path.join(dst, "package.json"), JSON.stringify(transformed), "utf8");
74
+ }
75
+ }
76
+ catch {
77
+ logger.error(`Failed to copy ${src}`);
78
+ }
79
+ }
80
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,188 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { transformBuildCondition, transformPackageJson } from "./workerd";
3
+ describe("transformBuildCondition", () => {
4
+ test("top level", () => {
5
+ const exports = {
6
+ workerd: "./path/to/workerd.js",
7
+ default: "./path/to/default.js",
8
+ };
9
+ const workerd = transformBuildCondition(exports, "workerd");
10
+ const defaultExport = transformBuildCondition(exports, "default");
11
+ const moduleExport = transformBuildCondition(exports, "module");
12
+ expect(workerd.hasBuildCondition).toBe(true);
13
+ expect(workerd.transformedExports).toEqual({
14
+ workerd: "./path/to/workerd.js",
15
+ });
16
+ expect(defaultExport.hasBuildCondition).toBe(true);
17
+ expect(defaultExport.transformedExports).toEqual({
18
+ default: "./path/to/default.js",
19
+ });
20
+ expect(moduleExport.hasBuildCondition).toBe(false);
21
+ expect(moduleExport.transformedExports).toEqual({
22
+ workerd: "./path/to/workerd.js",
23
+ default: "./path/to/default.js",
24
+ });
25
+ });
26
+ test("nested", () => {
27
+ const exports = {
28
+ ".": "/path/to/index.js",
29
+ "./server": {
30
+ "react-server": {
31
+ workerd: "./server.edge.js",
32
+ other: "./server.js",
33
+ },
34
+ default: "./server.js",
35
+ },
36
+ };
37
+ const workerd = transformBuildCondition(exports, "workerd");
38
+ const defaultExport = transformBuildCondition(exports, "default");
39
+ const moduleExport = transformBuildCondition(exports, "module");
40
+ expect(workerd.hasBuildCondition).toBe(true);
41
+ expect(workerd.transformedExports).toEqual({
42
+ ".": "/path/to/index.js",
43
+ "./server": {
44
+ "react-server": {
45
+ workerd: "./server.edge.js",
46
+ },
47
+ default: "./server.js",
48
+ },
49
+ });
50
+ expect(defaultExport.hasBuildCondition).toBe(true);
51
+ expect(defaultExport.transformedExports).toEqual({
52
+ ".": "/path/to/index.js",
53
+ "./server": {
54
+ "react-server": {
55
+ workerd: "./server.edge.js",
56
+ other: "./server.js",
57
+ },
58
+ default: "./server.js",
59
+ },
60
+ });
61
+ expect(moduleExport.hasBuildCondition).toBe(false);
62
+ expect(moduleExport.transformedExports).toEqual({
63
+ ".": "/path/to/index.js",
64
+ "./server": {
65
+ "react-server": {
66
+ workerd: "./server.edge.js",
67
+ other: "./server.js",
68
+ },
69
+ default: "./server.js",
70
+ },
71
+ });
72
+ });
73
+ test("only consider leaves", () => {
74
+ const exports = {
75
+ ".": "/path/to/index.js",
76
+ "./server": {
77
+ workerd: {
78
+ default: "./server.edge.js",
79
+ },
80
+ },
81
+ };
82
+ const workerd = transformBuildCondition(exports, "workerd");
83
+ expect(workerd.hasBuildCondition).toBe(false);
84
+ expect(workerd.transformedExports).toEqual({
85
+ ".": "/path/to/index.js",
86
+ "./server": {
87
+ workerd: {
88
+ default: "./server.edge.js",
89
+ },
90
+ },
91
+ });
92
+ });
93
+ });
94
+ describe("transformPackageJson", () => {
95
+ test("no exports nor imports", () => {
96
+ const json = {
97
+ name: "test",
98
+ main: "index.js",
99
+ version: "1.0.0",
100
+ description: "test package",
101
+ };
102
+ const { transformed, hasBuildCondition } = transformPackageJson(json);
103
+ expect(transformed).toEqual(json);
104
+ expect(hasBuildCondition).toBe(false);
105
+ });
106
+ test("exports only with no workerd condition", () => {
107
+ const json = {
108
+ name: "test",
109
+ exports: {
110
+ ".": "./index.js",
111
+ "./server": "./server.js",
112
+ },
113
+ };
114
+ const { transformed, hasBuildCondition } = transformPackageJson(json);
115
+ expect(transformed).toEqual(json);
116
+ expect(hasBuildCondition).toBe(false);
117
+ });
118
+ test("exports only with nested workerd condition", () => {
119
+ const json = {
120
+ name: "test",
121
+ exports: {
122
+ ".": "./index.js",
123
+ "./server": {
124
+ workerd: "./server.edge.js",
125
+ other: "./server.js",
126
+ },
127
+ },
128
+ };
129
+ const { transformed, hasBuildCondition } = transformPackageJson(json);
130
+ expect(transformed).toEqual({
131
+ name: "test",
132
+ exports: {
133
+ ".": "./index.js",
134
+ "./server": {
135
+ workerd: "./server.edge.js",
136
+ },
137
+ },
138
+ });
139
+ expect(hasBuildCondition).toBe(true);
140
+ });
141
+ test("imports only with top level workerd condition", () => {
142
+ const json = {
143
+ name: "test",
144
+ imports: {
145
+ workerd: "./server.edge.js",
146
+ default: "./server.js",
147
+ },
148
+ };
149
+ const { transformed, hasBuildCondition } = transformPackageJson(json);
150
+ expect(transformed).toEqual({
151
+ name: "test",
152
+ imports: {
153
+ workerd: "./server.edge.js",
154
+ },
155
+ });
156
+ expect(hasBuildCondition).toBe(true);
157
+ });
158
+ test("exports and imports with workerd condition both nested and top level", () => {
159
+ const json = {
160
+ name: "test",
161
+ exports: {
162
+ ".": "./index.js",
163
+ "./server": {
164
+ workerd: "./server.edge.js",
165
+ other: "./server.js",
166
+ },
167
+ },
168
+ imports: {
169
+ workerd: "./server.edge.js",
170
+ default: "./server.js",
171
+ },
172
+ };
173
+ const { transformed, hasBuildCondition } = transformPackageJson(json);
174
+ expect(transformed).toEqual({
175
+ name: "test",
176
+ exports: {
177
+ ".": "./index.js",
178
+ "./server": {
179
+ workerd: "./server.edge.js",
180
+ },
181
+ },
182
+ imports: {
183
+ workerd: "./server.edge.js",
184
+ },
185
+ });
186
+ expect(hasBuildCondition).toBe(true);
187
+ });
188
+ });
@@ -1,5 +1,6 @@
1
1
  import { BuildOptions } from "@opennextjs/aws/build/helper.js";
2
- import { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
2
+ import type { OpenNextConfig } from "../../api/config.js";
3
3
  export declare function deploy(options: BuildOptions, config: OpenNextConfig, deployOptions: {
4
4
  passthroughArgs: string[];
5
+ cacheChunkSize?: number;
5
6
  }): Promise<void>;
@@ -4,6 +4,7 @@ export async function deploy(options, config, deployOptions) {
4
4
  await populateCache(options, config, {
5
5
  target: "remote",
6
6
  environment: getWranglerEnvironmentFlag(deployOptions.passthroughArgs),
7
+ cacheChunkSize: deployOptions.cacheChunkSize,
7
8
  });
8
9
  runWrangler(options, ["deploy", ...deployOptions.passthroughArgs], { logging: "all" });
9
10
  }
@@ -11,4 +11,5 @@ export declare function getCacheAssets(opts: BuildOptions): CacheAsset[];
11
11
  export declare function populateCache(options: BuildOptions, config: OpenNextConfig, populateCacheOptions: {
12
12
  target: WranglerTarget;
13
13
  environment?: string;
14
+ cacheChunkSize?: number;
14
15
  }): Promise<void>;
@@ -1,12 +1,13 @@
1
- import { cpSync, existsSync } from "node:fs";
1
+ import { cpSync, existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import logger from "@opennextjs/aws/logger.js";
4
4
  import { globSync } from "glob";
5
5
  import { tqdm } from "ts-tqdm";
6
- import { unstable_readConfig } from "wrangler";
7
- import { BINDING_NAME as KV_CACHE_BINDING_NAME, computeCacheKey as computeKVCacheKey, NAME as KV_CACHE_NAME, } from "../../api/overrides/incremental-cache/kv-incremental-cache.js";
8
- import { BINDING_NAME as R2_CACHE_BINDING_NAME, computeCacheKey as computeR2CacheKey, NAME as R2_CACHE_NAME, PREFIX_ENV_NAME as R2_CACHE_PREFIX_ENV_NAME, } from "../../api/overrides/incremental-cache/r2-incremental-cache.js";
6
+ import { getPlatformProxy, unstable_readConfig } from "wrangler";
7
+ import { BINDING_NAME as KV_CACHE_BINDING_NAME, NAME as KV_CACHE_NAME, PREFIX_ENV_NAME as KV_CACHE_PREFIX_ENV_NAME, } from "../../api/overrides/incremental-cache/kv-incremental-cache.js";
8
+ import { BINDING_NAME as R2_CACHE_BINDING_NAME, NAME as R2_CACHE_NAME, PREFIX_ENV_NAME as R2_CACHE_PREFIX_ENV_NAME, } from "../../api/overrides/incremental-cache/r2-incremental-cache.js";
9
9
  import { CACHE_DIR as STATIC_ASSETS_CACHE_DIR, NAME as STATIC_ASSETS_CACHE_NAME, } from "../../api/overrides/incremental-cache/static-assets-incremental-cache.js";
10
+ import { computeCacheKey } from "../../api/overrides/internal.js";
10
11
  import { BINDING_NAME as D1_TAG_BINDING_NAME, NAME as D1_TAG_NAME, } from "../../api/overrides/tag-cache/d1-next-tag-cache.js";
11
12
  import { runWrangler } from "../utils/run-wrangler.js";
12
13
  async function resolveCacheName(value) {
@@ -48,9 +49,10 @@ export function getCacheAssets(opts) {
48
49
  }
49
50
  return assets;
50
51
  }
51
- function populateR2IncrementalCache(options, populateCacheOptions) {
52
+ async function populateR2IncrementalCache(options, populateCacheOptions) {
52
53
  logger.info("\nPopulating R2 incremental cache...");
53
54
  const config = unstable_readConfig({ env: populateCacheOptions.environment });
55
+ const proxy = await getPlatformProxy(populateCacheOptions);
54
56
  const binding = config.r2_buckets.find(({ binding }) => binding === R2_CACHE_BINDING_NAME);
55
57
  if (!binding) {
56
58
  throw new Error(`No R2 binding ${JSON.stringify(R2_CACHE_BINDING_NAME)} found!`);
@@ -61,37 +63,48 @@ function populateR2IncrementalCache(options, populateCacheOptions) {
61
63
  }
62
64
  const assets = getCacheAssets(options);
63
65
  for (const { fullPath, key, buildId, isFetch } of tqdm(assets)) {
64
- const cacheKey = computeR2CacheKey(key, {
65
- directory: process.env[R2_CACHE_PREFIX_ENV_NAME],
66
+ const cacheKey = computeCacheKey(key, {
67
+ prefix: proxy.env[R2_CACHE_PREFIX_ENV_NAME],
66
68
  buildId,
67
- isFetch,
69
+ cacheType: isFetch ? "fetch" : "cache",
68
70
  });
69
- runWrangler(options, ["r2 object put", JSON.stringify(path.join(bucket, cacheKey)), `--file ${JSON.stringify(fullPath)}`],
71
+ runWrangler(options, ["r2 object put", quoteShellMeta(path.join(bucket, cacheKey)), `--file ${quoteShellMeta(fullPath)}`],
70
72
  // NOTE: R2 does not support the environment flag and results in the following error:
71
73
  // Incorrect type for the 'cacheExpiry' field on 'HttpMetadata': the provided value is not of type 'date'.
72
- { target: populateCacheOptions.target, excludeRemoteFlag: true, logging: "error" });
74
+ { target: populateCacheOptions.target, logging: "error" });
73
75
  }
74
76
  logger.info(`Successfully populated cache with ${assets.length} assets`);
75
77
  }
76
- function populateKVIncrementalCache(options, populateCacheOptions) {
78
+ async function populateKVIncrementalCache(options, populateCacheOptions) {
77
79
  logger.info("\nPopulating KV incremental cache...");
78
80
  const config = unstable_readConfig({ env: populateCacheOptions.environment });
81
+ const proxy = await getPlatformProxy(populateCacheOptions);
79
82
  const binding = config.kv_namespaces.find(({ binding }) => binding === KV_CACHE_BINDING_NAME);
80
83
  if (!binding) {
81
84
  throw new Error(`No KV binding ${JSON.stringify(KV_CACHE_BINDING_NAME)} found!`);
82
85
  }
83
86
  const assets = getCacheAssets(options);
84
- for (const { fullPath, key, buildId, isFetch } of tqdm(assets)) {
85
- const cacheKey = computeKVCacheKey(key, {
86
- buildId,
87
- isFetch,
87
+ const chunkSize = Math.max(1, populateCacheOptions.cacheChunkSize ?? 25);
88
+ const totalChunks = Math.ceil(assets.length / chunkSize);
89
+ logger.info(`Inserting ${assets.length} assets to KV in chunks of ${chunkSize}`);
90
+ for (const i of tqdm(Array.from({ length: totalChunks }, (_, i) => i))) {
91
+ const chunkPath = path.join(options.outputDir, "cloudflare", `cache-chunk-${i}.json`);
92
+ const kvMapping = assets
93
+ .slice(i * chunkSize, (i + 1) * chunkSize)
94
+ .map(({ fullPath, key, buildId, isFetch }) => ({
95
+ key: computeCacheKey(key, {
96
+ prefix: proxy.env[KV_CACHE_PREFIX_ENV_NAME],
97
+ buildId,
98
+ cacheType: isFetch ? "fetch" : "cache",
99
+ }),
100
+ value: readFileSync(fullPath, "utf8"),
101
+ }));
102
+ writeFileSync(chunkPath, JSON.stringify(kvMapping));
103
+ runWrangler(options, ["kv bulk put", quoteShellMeta(chunkPath), `--binding ${KV_CACHE_BINDING_NAME}`], {
104
+ ...populateCacheOptions,
105
+ logging: "error",
88
106
  });
89
- runWrangler(options, [
90
- "kv key put",
91
- JSON.stringify(cacheKey),
92
- `--binding ${JSON.stringify(KV_CACHE_BINDING_NAME)}`,
93
- `--path ${JSON.stringify(fullPath)}`,
94
- ], { ...populateCacheOptions, logging: "error" });
107
+ rmSync(chunkPath);
95
108
  }
96
109
  logger.info(`Successfully populated cache with ${assets.length} assets`);
97
110
  }
@@ -104,7 +117,7 @@ function populateD1TagCache(options, populateCacheOptions) {
104
117
  }
105
118
  runWrangler(options, [
106
119
  "d1 execute",
107
- JSON.stringify(D1_TAG_BINDING_NAME),
120
+ D1_TAG_BINDING_NAME,
108
121
  `--command "CREATE TABLE IF NOT EXISTS revalidations (tag TEXT NOT NULL, revalidatedAt INTEGER NOT NULL, UNIQUE(tag) ON CONFLICT REPLACE);"`,
109
122
  ], { ...populateCacheOptions, logging: "error" });
110
123
  logger.info("\nSuccessfully created D1 table");
@@ -124,10 +137,10 @@ export async function populateCache(options, config, populateCacheOptions) {
124
137
  const name = await resolveCacheName(incrementalCache);
125
138
  switch (name) {
126
139
  case R2_CACHE_NAME:
127
- populateR2IncrementalCache(options, populateCacheOptions);
140
+ await populateR2IncrementalCache(options, populateCacheOptions);
128
141
  break;
129
142
  case KV_CACHE_NAME:
130
- populateKVIncrementalCache(options, populateCacheOptions);
143
+ await populateKVIncrementalCache(options, populateCacheOptions);
131
144
  break;
132
145
  case STATIC_ASSETS_CACHE_NAME:
133
146
  populateStaticAssetsIncrementalCache(options);
@@ -147,3 +160,22 @@ export async function populateCache(options, config, populateCacheOptions) {
147
160
  }
148
161
  }
149
162
  }
163
+ /**
164
+ * Escape shell metacharacters.
165
+ *
166
+ * When `spawnSync` is invoked with `shell: true`, metacharacters need to be escaped.
167
+ *
168
+ * Based on https://github.com/ljharb/shell-quote/blob/main/quote.js
169
+ *
170
+ * @param arg
171
+ * @returns escaped arg
172
+ */
173
+ function quoteShellMeta(arg) {
174
+ if (/["\s]/.test(arg) && !/'/.test(arg)) {
175
+ return `'${arg.replace(/(['\\])/g, "\\$1")}'`;
176
+ }
177
+ if (/["'\s]/.test(arg)) {
178
+ return `"${arg.replace(/(["\\$`!])/g, "\\$1")}"`;
179
+ }
180
+ return arg.replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}])/g, "$1\\$2");
181
+ }
@@ -1,5 +1,6 @@
1
1
  import { BuildOptions } from "@opennextjs/aws/build/helper.js";
2
- import { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
2
+ import type { OpenNextConfig } from "../../api/config.js";
3
3
  export declare function preview(options: BuildOptions, config: OpenNextConfig, previewOptions: {
4
4
  passthroughArgs: string[];
5
+ cacheChunkSize?: number;
5
6
  }): Promise<void>;
@@ -4,6 +4,7 @@ export async function preview(options, config, previewOptions) {
4
4
  await populateCache(options, config, {
5
5
  target: "local",
6
6
  environment: getWranglerEnvironmentFlag(previewOptions.passthroughArgs),
7
+ cacheChunkSize: previewOptions.cacheChunkSize,
7
8
  });
8
9
  runWrangler(options, ["dev", ...previewOptions.passthroughArgs], { logging: "all" });
9
10
  }
@@ -1,5 +1,6 @@
1
1
  import { BuildOptions } from "@opennextjs/aws/build/helper.js";
2
- import { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
2
+ import type { OpenNextConfig } from "../../api/config.js";
3
3
  export declare function upload(options: BuildOptions, config: OpenNextConfig, uploadOptions: {
4
4
  passthroughArgs: string[];
5
+ cacheChunkSize?: number;
5
6
  }): Promise<void>;
@@ -4,6 +4,7 @@ export async function upload(options, config, uploadOptions) {
4
4
  await populateCache(options, config, {
5
5
  target: "remote",
6
6
  environment: getWranglerEnvironmentFlag(uploadOptions.passthroughArgs),
7
+ cacheChunkSize: uploadOptions.cacheChunkSize,
7
8
  });
8
9
  runWrangler(options, ["versions upload", ...uploadOptions.passthroughArgs], { logging: "all" });
9
10
  }
@@ -44,6 +44,9 @@ function initRuntime() {
44
44
  Object.assign(process.versions, { node: "22.14.0", ...process.versions });
45
45
  globalThis.__dirname ??= "";
46
46
  globalThis.__filename ??= "";
47
+ // Some packages rely on `import.meta.url` but it is undefined in workerd
48
+ // For example it causes a bunch of issues, and will make even import crash with payload
49
+ import.meta.url ??= "file:///worker.js";
47
50
  // Do not crash on cache not supported
48
51
  // https://github.com/cloudflare/workerd/pull/2434
49
52
  // compatibility flag "cache_option_enabled" -> does not support "force-cache"
@@ -1,4 +1,6 @@
1
1
  export { DOQueueHandler } from "./.build/durable-objects/queue.js";
2
2
  export { DOShardedTagCache } from "./.build/durable-objects/sharded-tag-cache.js";
3
- declare const _default: ExportedHandler<CloudflareEnv>;
3
+ declare const _default: {
4
+ fetch(request: Request<unknown, IncomingRequestCfProperties<unknown>>, env: CloudflareEnv, ctx: ExecutionContext): Promise<any>;
5
+ };
4
6
  export default _default;
@@ -3,7 +3,6 @@ export type WranglerTarget = "local" | "remote";
3
3
  type WranglerOptions = {
4
4
  target?: WranglerTarget;
5
5
  environment?: string;
6
- excludeRemoteFlag?: boolean;
7
6
  logging?: "all" | "error";
8
7
  };
9
8
  export declare function runWrangler(options: BuildOptions, args: string[], wranglerOpts?: WranglerOptions): void;
@@ -43,7 +43,7 @@ export function runWrangler(options, args, wranglerOpts = {}) {
43
43
  ...injectPassthroughFlagForArgs(options, [
44
44
  ...args,
45
45
  wranglerOpts.environment && `--env ${wranglerOpts.environment}`,
46
- wranglerOpts.target === "remote" && !wranglerOpts.excludeRemoteFlag && "--remote",
46
+ wranglerOpts.target === "remote" && "--remote",
47
47
  wranglerOpts.target === "local" && "--local",
48
48
  ].filter((v) => !!v)),
49
49
  ], {
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.0.0-beta.3",
4
+ "version": "1.0.0",
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.5.7",
46
+ "@opennextjs/aws": "~3.6.0",
47
47
  "enquirer": "^2.4.1",
48
48
  "glob": "^11.0.0",
49
49
  "ts-tqdm": "^0.8.6"
@@ -68,7 +68,7 @@
68
68
  "vitest": "^2.1.1"
69
69
  },
70
70
  "peerDependencies": {
71
- "wrangler": "^3.114.3 || ^4.7.0"
71
+ "wrangler": "^4.14.0"
72
72
  },
73
73
  "scripts": {
74
74
  "clean": "rimraf dist",
@@ -3,7 +3,7 @@
3
3
  "main": ".open-next/worker.js",
4
4
  "name": "app-name",
5
5
  "compatibility_date": "2024-12-30",
6
- "compatibility_flags": ["nodejs_compat"],
6
+ "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
7
7
  "assets": {
8
8
  "directory": ".open-next/assets",
9
9
  "binding": "ASSETS"