@opennextjs/cloudflare 1.15.1 → 1.16.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.
@@ -11,4 +11,4 @@ import type { ProjectOptions } from "../project-options.js";
11
11
  * @param config The OpenNext config
12
12
  * @param projectOpts The options for the project
13
13
  */
14
- export declare function build(options: buildHelper.BuildOptions, config: OpenNextConfig, projectOpts: ProjectOptions, wranglerConfig: Unstable_Config): Promise<void>;
14
+ export declare function build(options: buildHelper.BuildOptions, config: OpenNextConfig, projectOpts: ProjectOptions, wranglerConfig: Unstable_Config, allowUnsupportedNextVersions: boolean): Promise<void>;
@@ -25,7 +25,7 @@ import { getVersion } from "./utils/version.js";
25
25
  * @param config The OpenNext config
26
26
  * @param projectOpts The options for the project
27
27
  */
28
- export async function build(options, config, projectOpts, wranglerConfig) {
28
+ export async function build(options, config, projectOpts, wranglerConfig, allowUnsupportedNextVersions) {
29
29
  // Do not minify the code so that we can apply string replacement patch.
30
30
  options.minify = false;
31
31
  // Pre-build validation
@@ -33,6 +33,7 @@ export async function build(options, config, projectOpts, wranglerConfig) {
33
33
  logger.info(`App directory: ${options.appPath}`);
34
34
  buildHelper.printNextjsVersion(options);
35
35
  await ensureNextjsVersionSupported(options);
36
+ buildHelper.checkNextVersionSupport(options.nextVersion, allowUnsupportedNextVersions, `--dangerouslyUseUnsupportedNextVersion`);
36
37
  const { aws, cloudflare } = getVersion();
37
38
  logger.info(`@opennextjs/cloudflare version: ${cloudflare}`);
38
39
  logger.info(`@opennextjs/aws version: ${aws}`);
@@ -7,8 +7,7 @@ import type { ProjectOptions } from "../../project-options.js";
7
7
  *
8
8
  * @param projectOpts The options for the project
9
9
  */
10
- export declare function createWranglerConfigIfNotExistent(projectOpts: ProjectOptions): Promise<void>;
11
- export declare function getLatestCompatDate(): Promise<string | undefined>;
10
+ export declare function createWranglerConfigIfNonExistent(projectOpts: ProjectOptions): Promise<void>;
12
11
  /**
13
12
  * Creates a `open-next.config.ts` file for the user if it doesn't exist, but only after asking for the user's confirmation.
14
13
  *
@@ -1,7 +1,6 @@
1
- import { cpSync, existsSync, readFileSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { getPackageTemplatesDirPath } from "../../../utils/get-package-templates-dir-path.js";
4
1
  import { askConfirmation } from "../../utils/ask-confirmation.js";
2
+ import { createOpenNextConfigFile, findOpenNextConfig } from "../../utils/open-next-config.js";
3
+ import { createWranglerConfigFile, findWranglerConfig } from "../../utils/wrangler-config.js";
5
4
  /**
6
5
  * Creates a `wrangler.jsonc` file for the user if a wrangler config file doesn't already exist,
7
6
  * but only after asking for the user's confirmation.
@@ -10,9 +9,8 @@ import { askConfirmation } from "../../utils/ask-confirmation.js";
10
9
  *
11
10
  * @param projectOpts The options for the project
12
11
  */
13
- export async function createWranglerConfigIfNotExistent(projectOpts) {
14
- const possibleExts = ["toml", "json", "jsonc"];
15
- const wranglerConfigFileExists = possibleExts.some((ext) => existsSync(join(projectOpts.sourceDir, `wrangler.${ext}`)));
12
+ export async function createWranglerConfigIfNonExistent(projectOpts) {
13
+ const wranglerConfigFileExists = Boolean(findWranglerConfig(projectOpts.sourceDir));
16
14
  if (wranglerConfigFileExists) {
17
15
  return;
18
16
  }
@@ -23,41 +21,7 @@ export async function createWranglerConfigIfNotExistent(projectOpts) {
23
21
  "(to avoid this check use the `--skipWranglerConfigCheck` flag or set a `SKIP_WRANGLER_CONFIG_CHECK` environment variable to `yes`)");
24
22
  return;
25
23
  }
26
- let wranglerConfig = readFileSync(join(getPackageTemplatesDirPath(), "wrangler.jsonc"), "utf8");
27
- const appName = getAppNameFromPackageJson(projectOpts.sourceDir) ?? "app-name";
28
- wranglerConfig = wranglerConfig.replaceAll('"<WORKER_NAME>"', JSON.stringify(appName.replaceAll("_", "-")));
29
- const compatDate = await getLatestCompatDate();
30
- if (compatDate) {
31
- wranglerConfig = wranglerConfig.replace(/"compatibility_date": "\d{4}-\d{2}-\d{2}"/, `"compatibility_date": ${JSON.stringify(compatDate)}`);
32
- }
33
- writeFileSync(join(projectOpts.sourceDir, "wrangler.jsonc"), wranglerConfig);
34
- }
35
- function getAppNameFromPackageJson(sourceDir) {
36
- try {
37
- const packageJsonStr = readFileSync(join(sourceDir, "package.json"), "utf8");
38
- const packageJson = JSON.parse(packageJsonStr);
39
- if (typeof packageJson.name === "string")
40
- return packageJson.name;
41
- }
42
- catch {
43
- /* empty */
44
- }
45
- }
46
- export async function getLatestCompatDate() {
47
- try {
48
- const resp = await fetch(`https://registry.npmjs.org/workerd`);
49
- const latestWorkerdVersion = (await resp.json())["dist-tags"].latest;
50
- // The format of the workerd version is `major.yyyymmdd.patch`.
51
- const match = latestWorkerdVersion.match(/\d+\.(\d{4})(\d{2})(\d{2})\.\d+/);
52
- if (match) {
53
- const [, year, month, date] = match;
54
- const compatDate = `${year}-${month}-${date}`;
55
- return compatDate;
56
- }
57
- }
58
- catch {
59
- /* empty */
60
- }
24
+ await createWranglerConfigFile(projectOpts.sourceDir);
61
25
  }
62
26
  /**
63
27
  * Creates a `open-next.config.ts` file for the user if it doesn't exist, but only after asking for the user's confirmation.
@@ -68,13 +32,13 @@ export async function getLatestCompatDate() {
68
32
  * @return The path to the created source file
69
33
  */
70
34
  export async function createOpenNextConfigIfNotExistent(sourceDir) {
71
- const openNextConfigPath = join(sourceDir, "open-next.config.ts");
72
- if (!existsSync(openNextConfigPath)) {
35
+ const openNextConfigPath = findOpenNextConfig(sourceDir);
36
+ if (!openNextConfigPath) {
73
37
  const answer = await askConfirmation("Missing required `open-next.config.ts` file, do you want to create one?");
74
38
  if (!answer) {
75
39
  throw new Error("The `open-next.config.ts` file is required, aborting!");
76
40
  }
77
- cpSync(join(getPackageTemplatesDirPath(), "open-next.config.ts"), openNextConfigPath);
41
+ return createOpenNextConfigFile(sourceDir);
78
42
  }
79
43
  return openNextConfigPath;
80
44
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Appends text to a file
3
+ *
4
+ * When the file does not exists, it is always created with the text content.
5
+ * When the file exists, the text is appended only when the predicate return `true`.
6
+ *
7
+ * @param filepath The path to the file.
8
+ * @param text The text to append to the file.
9
+ * @param condition A function that receives the current file content and returns `true` if the text should be appended to it, the condition is skipped when the file is being created.
10
+ */
11
+ export declare function conditionalAppendFileSync(filepath: string, text: string, condition: (fileContent: string) => boolean): void;
@@ -0,0 +1,17 @@
1
+ import fs from "node:fs";
2
+ /**
3
+ * Appends text to a file
4
+ *
5
+ * When the file does not exists, it is always created with the text content.
6
+ * When the file exists, the text is appended only when the predicate return `true`.
7
+ *
8
+ * @param filepath The path to the file.
9
+ * @param text The text to append to the file.
10
+ * @param condition A function that receives the current file content and returns `true` if the text should be appended to it, the condition is skipped when the file is being created.
11
+ */
12
+ export function conditionalAppendFileSync(filepath, text, condition) {
13
+ const fileExists = fs.existsSync(filepath);
14
+ if (!fileExists || condition(fs.readFileSync(filepath, "utf8"))) {
15
+ fs.appendFileSync(filepath, text);
16
+ }
17
+ }
@@ -1,4 +1,7 @@
1
+ /**
2
+ * Returns the version of the Cloudflare package and its AWS dependency.
3
+ */
1
4
  export declare function getVersion(): {
2
- cloudflare: any;
3
- aws: any;
5
+ cloudflare: string;
6
+ aws: string;
4
7
  };
@@ -1,6 +1,9 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { join } from "node:path";
3
3
  import { fileURLToPath, URL } from "node:url";
4
+ /**
5
+ * Returns the version of the Cloudflare package and its AWS dependency.
6
+ */
4
7
  export function getVersion() {
5
8
  const require = createRequire(import.meta.url);
6
9
  const __dirname = fileURLToPath(new URL(".", import.meta.url));
@@ -1,5 +1,5 @@
1
1
  import { build as buildImpl } from "../build/build.js";
2
- import { createWranglerConfigIfNotExistent } from "../build/utils/index.js";
2
+ import { createWranglerConfigIfNonExistent } from "../build/utils/index.js";
3
3
  import { compileConfig, getNormalizedOptions, nextAppDir, printHeaders, readWranglerConfig, withWranglerOptions, withWranglerPassthroughArgs, } from "./utils.js";
4
4
  /**
5
5
  * Implementation of the `opennextjs-cloudflare build` command.
@@ -15,10 +15,10 @@ async function buildCommand(args) {
15
15
  // Note: We don't ask when a custom config file is specified via `--config`
16
16
  // nor when `--skipWranglerConfigCheck` is used.
17
17
  if (!projectOpts.wranglerConfigPath && !args.skipWranglerConfigCheck) {
18
- await createWranglerConfigIfNotExistent(projectOpts);
18
+ await createWranglerConfigIfNonExistent(projectOpts);
19
19
  }
20
- const wranglerConfig = readWranglerConfig(args);
21
- await buildImpl(options, config, projectOpts, wranglerConfig);
20
+ const wranglerConfig = await readWranglerConfig(args);
21
+ await buildImpl(options, config, projectOpts, wranglerConfig, args.dangerouslyUseUnsupportedNextVersion);
22
22
  }
23
23
  /**
24
24
  * Add the `build` command to yargs configuration.
@@ -46,5 +46,10 @@ export function addBuildCommand(y) {
46
46
  .option("openNextConfigPath", {
47
47
  type: "string",
48
48
  desc: "Path to the OpenNext configuration file",
49
+ })
50
+ .option("dangerouslyUseUnsupportedNextVersion", {
51
+ type: "boolean",
52
+ default: false,
53
+ desc: "Allow using unsupported Next.js versions",
49
54
  }), (args) => buildCommand(withWranglerPassthroughArgs(args)));
50
55
  }
@@ -13,7 +13,7 @@ export async function deployCommand(args) {
13
13
  printHeaders("deploy");
14
14
  const { config } = await retrieveCompiledConfig();
15
15
  const buildOpts = getNormalizedOptions(config);
16
- const wranglerConfig = readWranglerConfig(args);
16
+ const wranglerConfig = await readWranglerConfig(args);
17
17
  const envVars = await getEnvFromPlatformProxy(config, buildOpts);
18
18
  await populateCache(buildOpts, config, wranglerConfig, {
19
19
  target: "remote",
@@ -0,0 +1,5 @@
1
+ import type yargs from "yargs";
2
+ /**
3
+ * Add the `migrate` command to yargs configuration.
4
+ */
5
+ export declare function addMigrateCommand<T extends yargs.Argv>(y: T): yargs.Argv<{}>;
@@ -0,0 +1,171 @@
1
+ import childProcess from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { checkRunningInsideNextjsApp, findNextConfig, findPackagerAndRoot, } from "@opennextjs/aws/build/helper.js";
5
+ import logger from "@opennextjs/aws/logger.js";
6
+ import { conditionalAppendFileSync } from "../build/utils/files.js";
7
+ import { createOpenNextConfigFile, findOpenNextConfig } from "../utils/open-next-config.js";
8
+ import { createWranglerConfigFile, findWranglerConfig } from "../utils/wrangler-config.js";
9
+ import { printHeaders } from "./utils.js";
10
+ /**
11
+ * Implementation of the `opennextjs-cloudflare migrate` command.
12
+ *
13
+ * @param args
14
+ */
15
+ async function migrateCommand(args) {
16
+ printHeaders("migrate");
17
+ logger.info("🚀 Setting up the OpenNext Cloudflare adapter...\n");
18
+ const projectDir = process.cwd();
19
+ checkRunningInsideNextjsApp({ appPath: projectDir });
20
+ const wranglerConfigFilePath = findWranglerConfig(projectDir);
21
+ if (wranglerConfigFilePath) {
22
+ logger.error(`The project already contains a Wrangler config file (at ${wranglerConfigFilePath}).\n` +
23
+ "This means that your application is either a static site or a next-on-pages project.\n" +
24
+ "If your project is a static site and you want to migrate to OpenNext, delete the Wrangler configuration file, convert the project to a full stack one and try again." +
25
+ " if your project is a next-on-pages one remove any next-on-pages configuration, any edge runtime usage and try again.");
26
+ process.exit(1);
27
+ }
28
+ if (findOpenNextConfig(projectDir)) {
29
+ logger.info(`Exiting since the project is already configured for OpenNext (an \`open-next.config.ts\` file already exists)\n`);
30
+ return;
31
+ }
32
+ const { packager } = findPackagerAndRoot(projectDir);
33
+ const packageManager = packageManagers[packager];
34
+ printStepTitle("Installing dependencies");
35
+ try {
36
+ const forceFlag = args.forceInstall ? " --force" : "";
37
+ childProcess.execSync(`${packageManager.install}${forceFlag} @opennextjs/cloudflare@latest`, {
38
+ stdio: "inherit",
39
+ });
40
+ childProcess.execSync(`${packageManager.installDev}${forceFlag} wrangler@latest`, { stdio: "inherit" });
41
+ }
42
+ catch (error) {
43
+ logger.error("Failed to install dependencies:", error.message);
44
+ process.exit(1);
45
+ }
46
+ printStepTitle("Creating wrangler.jsonc");
47
+ await createWranglerConfigFile("./");
48
+ printStepTitle("Creating open-next.config.ts");
49
+ await createOpenNextConfigFile("./");
50
+ const devVarsExists = fs.existsSync(".dev.vars");
51
+ printStepTitle(`${devVarsExists ? "Updating" : "Creating"} .dev.vars file`);
52
+ conditionalAppendFileSync(".dev.vars", "\nNEXTJS_ENV=development\n", (content) => !/\bNEXTJS_ENV\b/.test(content));
53
+ printStepTitle(`${fs.existsSync("public/_headers") ? "Updating" : "Creating"} public/_headers file`);
54
+ conditionalAppendFileSync("public/_headers", "\n\n# https://developers.cloudflare.com/workers/static-assets/headers\n" +
55
+ "# https://opennext.js.org/cloudflare/caching#static-assets-caching\n" +
56
+ "/_next/static/*\n" +
57
+ " Cache-Control: public,max-age=31536000,immutable\n\n", (content) => !/^\/_next\/static\/*\b/.test(content));
58
+ printStepTitle("Updating package.json scripts");
59
+ const openNextScripts = {
60
+ preview: "opennextjs-cloudflare build && opennextjs-cloudflare preview",
61
+ deploy: "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
62
+ upload: "opennextjs-cloudflare build && opennextjs-cloudflare upload",
63
+ ["cf-typegen"]: "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts",
64
+ };
65
+ try {
66
+ let packageJson = {};
67
+ if (fs.existsSync("package.json")) {
68
+ packageJson = JSON.parse(fs.readFileSync("package.json", "utf8"));
69
+ }
70
+ packageJson.scripts = {
71
+ build: "next build",
72
+ ...packageJson.scripts,
73
+ ...openNextScripts,
74
+ };
75
+ fs.writeFileSync("package.json", JSON.stringify(packageJson, null, 2));
76
+ }
77
+ catch (error) {
78
+ logger.error("Failed to update package.json", error.message);
79
+ logger.warn("\nPlease ensure that your package.json contains the following scripts:\n" +
80
+ console.log([...Object.entries(openNextScripts)].map(([key, value]) => ` - ${key}: ${value}`).join("\n")) +
81
+ "\n");
82
+ }
83
+ const gitIgnoreExists = fs.existsSync(".gitignore");
84
+ printStepTitle(`${gitIgnoreExists ? "Updating" : "Creating"} .gitignore file`);
85
+ conditionalAppendFileSync(".gitignore", "\n# OpenNext\n.open-next\n", (content) => !content.includes(".open-next"));
86
+ printStepTitle("Updating Next.js config");
87
+ conditionalAppendFileSync(findNextConfig({ appPath: projectDir }), "\nimport('@opennextjs/cloudflare').then(m => m.initOpenNextCloudflareForDev());\n", (content) => !content.includes("initOpenNextCloudflareForDev"));
88
+ printStepTitle("Checking for edge runtime usage");
89
+ try {
90
+ const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".mts"];
91
+ const files = findFilesRecursive(projectDir, extensions);
92
+ let foundEdgeRuntime = false;
93
+ for (const file of files) {
94
+ try {
95
+ const content = fs.readFileSync(file, "utf8");
96
+ if (content.includes('export const runtime = "edge"')) {
97
+ logger.warn(`Found edge runtime in: ${file}`);
98
+ foundEdgeRuntime = true;
99
+ break;
100
+ }
101
+ }
102
+ catch {
103
+ // Skip files that can't be read
104
+ }
105
+ }
106
+ if (foundEdgeRuntime) {
107
+ logger.warn("Detected usage of the edge runtime.\n" +
108
+ "The edge runtime is not supported yet with @opennextjs/cloudflare.\n" +
109
+ 'Remove all the `export const runtime = "edge";` lines from your source files');
110
+ }
111
+ }
112
+ catch {
113
+ logger.warn("Failed to check for edge runtime usage.\n" +
114
+ "The edge runtime is not supported yet with @opennextjs/cloudflare.\n" +
115
+ 'If present, remove all the `export const runtime = "edge";` lines from your source files');
116
+ }
117
+ logger.info("🎉 OpenNext Cloudflare adapter complete!\n" +
118
+ "\nNext steps:\n" +
119
+ `- Run: "${packageManager.run} preview" to build and preview your Cloudflare application locally\n` +
120
+ `- Run: "${packageManager.run} deploy" to deploy your application to Cloudflare Workers\n`);
121
+ }
122
+ const packageManagers = {
123
+ pnpm: { name: "pnpm", install: "pnpm add", installDev: "pnpm add -D", run: "pnpm" },
124
+ npm: { name: "npm", install: "npm install", installDev: "npm install --save-dev", run: "npm run" },
125
+ bun: { name: "bun", install: "bun add", installDev: "bun add -D", run: "bun" },
126
+ yarn: { name: "yarn", install: "yarn add", installDev: "yarn add -D", run: "yarn" },
127
+ };
128
+ /**
129
+ * Recursively searches a directory for files with specified extensions.
130
+ *
131
+ * Skips common build/cache directories: node_modules, .next, .open-next, .git, dist, build.
132
+ *
133
+ * @param dir - The directory path to start searching from
134
+ * @param extensions - Array of file extensions to match
135
+ * @param fileList - Accumulator array for found files (used internally for recursion)
136
+ * @returns Array of file paths matching the specified extensions
137
+ */
138
+ function findFilesRecursive(dir, extensions, fileList = []) {
139
+ const files = fs.readdirSync(dir);
140
+ files.forEach((file) => {
141
+ const filePath = path.join(dir, file);
142
+ const stat = fs.statSync(filePath);
143
+ if (stat.isDirectory()) {
144
+ // Skip node_modules, .next, .open-next, and other common build/cache directories
145
+ if (!["node_modules", ".next", ".open-next", ".git", "dist", "build"].includes(file)) {
146
+ findFilesRecursive(filePath, extensions, fileList);
147
+ }
148
+ }
149
+ else if (stat.isFile()) {
150
+ const ext = path.extname(file).toLowerCase();
151
+ if (extensions.includes(ext)) {
152
+ fileList.push(filePath);
153
+ }
154
+ }
155
+ });
156
+ return fileList;
157
+ }
158
+ function printStepTitle(title) {
159
+ logger.info(`⚙️ ${title}...\n`);
160
+ }
161
+ /**
162
+ * Add the `migrate` command to yargs configuration.
163
+ */
164
+ export function addMigrateCommand(y) {
165
+ return y.command("migrate", "Set up the OpenNext Cloudflare adapter in an existing Next.js project", (args) => args.option("forceInstall", {
166
+ type: "boolean",
167
+ alias: "f",
168
+ desc: "Install the dependencies using the `--force` flag.",
169
+ default: false,
170
+ }), (args) => migrateCommand(args));
171
+ }
@@ -23,7 +23,7 @@ async function populateCacheCommand(target, args) {
23
23
  printHeaders(`populate cache - ${target}`);
24
24
  const { config } = await retrieveCompiledConfig();
25
25
  const buildOpts = getNormalizedOptions(config);
26
- const wranglerConfig = readWranglerConfig(args);
26
+ const wranglerConfig = await readWranglerConfig(args);
27
27
  const envVars = await getEnvFromPlatformProxy(config, buildOpts);
28
28
  await populateCache(buildOpts, config, wranglerConfig, {
29
29
  target,
@@ -11,7 +11,7 @@ export async function previewCommand(args) {
11
11
  printHeaders("preview");
12
12
  const { config } = await retrieveCompiledConfig();
13
13
  const buildOpts = getNormalizedOptions(config);
14
- const wranglerConfig = readWranglerConfig(args);
14
+ const wranglerConfig = await readWranglerConfig(args);
15
15
  const envVars = await getEnvFromPlatformProxy(config, buildOpts);
16
16
  await populateCache(buildOpts, config, wranglerConfig, {
17
17
  target: args.remote ? "remote" : "local",
@@ -13,7 +13,7 @@ export async function uploadCommand(args) {
13
13
  printHeaders("upload");
14
14
  const { config } = await retrieveCompiledConfig();
15
15
  const buildOpts = getNormalizedOptions(config);
16
- const wranglerConfig = readWranglerConfig(args);
16
+ const wranglerConfig = await readWranglerConfig(args);
17
17
  const envVars = await getEnvFromPlatformProxy({
18
18
  configPath: args.wranglerConfigPath,
19
19
  environment: args.env,
@@ -65,7 +65,7 @@ export declare function getNormalizedOptions(config: OpenNextConfig, buildDir?:
65
65
  * @param args Wrangler environment and config path.
66
66
  * @returns Wrangler config.
67
67
  */
68
- export declare function readWranglerConfig(args: WithWranglerArgs): Config$1;
68
+ export declare function readWranglerConfig(args: WithWranglerArgs): Promise<Config$1>;
69
69
  /**
70
70
  * Adds flags for the wrangler config path and environment to the yargs configuration.
71
71
  */
@@ -75,8 +75,11 @@ export function getNormalizedOptions(config, buildDir = nextAppDir) {
75
75
  * @param args Wrangler environment and config path.
76
76
  * @returns Wrangler config.
77
77
  */
78
- export function readWranglerConfig(args) {
79
- return unstable_readConfig({ env: args.env, config: args.wranglerConfigPath });
78
+ export async function readWranglerConfig(args) {
79
+ // Note: `unstable_readConfig` is sync as of wrangler 4.60.0
80
+ // But it will eventually become async.
81
+ // See https://github.com/cloudflare/workers-sdk/pull/12031
82
+ return await unstable_readConfig({ env: args.env, config: args.wranglerConfigPath });
80
83
  }
81
84
  /**
82
85
  * Adds flags for the wrangler config path and environment to the yargs configuration.
package/dist/cli/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import yargs from "yargs";
3
3
  import { addBuildCommand } from "./commands/build.js";
4
4
  import { addDeployCommand } from "./commands/deploy.js";
5
+ import { addMigrateCommand } from "./commands/migrate.js";
5
6
  import { addPopulateCacheCommand } from "./commands/populate-cache.js";
6
7
  import { addPreviewCommand } from "./commands/preview.js";
7
8
  import { addUploadCommand } from "./commands/upload.js";
@@ -14,6 +15,7 @@ export function runCommand() {
14
15
  addDeployCommand(y);
15
16
  addUploadCommand(y);
16
17
  addPopulateCacheCommand(y);
18
+ addMigrateCommand(y);
17
19
  return y.demandCommand(1, 1).parse();
18
20
  }
19
21
  await runCommand();
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Finds the path to the OpenNext configuration file if it exists.
3
+ *
4
+ * @param appDir The directory to check for the open-next.config.ts file
5
+ * @returns The full path to open-next.config.ts if it exists, undefined otherwise
6
+ */
7
+ export declare function findOpenNextConfig(appDir: string): string | undefined;
8
+ /**
9
+ * Creates a `open-next.config.ts` file in the target directory for the project.
10
+ *
11
+ * @param appDir The Next application root
12
+ * @return The path to the created source file
13
+ */
14
+ export declare function createOpenNextConfigFile(appDir: string): Promise<string>;
@@ -0,0 +1,27 @@
1
+ import { cpSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getPackageTemplatesDirPath } from "../../utils/get-package-templates-dir-path.js";
4
+ /**
5
+ * Finds the path to the OpenNext configuration file if it exists.
6
+ *
7
+ * @param appDir The directory to check for the open-next.config.ts file
8
+ * @returns The full path to open-next.config.ts if it exists, undefined otherwise
9
+ */
10
+ export function findOpenNextConfig(appDir) {
11
+ const openNextConfigPath = join(appDir, "open-next.config.ts");
12
+ if (existsSync(openNextConfigPath)) {
13
+ return openNextConfigPath;
14
+ }
15
+ return undefined;
16
+ }
17
+ /**
18
+ * Creates a `open-next.config.ts` file in the target directory for the project.
19
+ *
20
+ * @param appDir The Next application root
21
+ * @return The path to the created source file
22
+ */
23
+ export async function createOpenNextConfigFile(appDir) {
24
+ const openNextConfigPath = join(appDir, "open-next.config.ts");
25
+ cpSync(join(getPackageTemplatesDirPath(), "open-next.config.ts"), openNextConfigPath);
26
+ return openNextConfigPath;
27
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Gets the path to the Wrangler configuration file if it exists.
3
+ *
4
+ * @param appDir The directory to check for the Wrangler config file
5
+ * @returns The path to Wrangler config file if it exists, undefined otherwise
6
+ */
7
+ export declare function findWranglerConfig(appDir: string): string | undefined;
8
+ /**
9
+ * Creates a wrangler.jsonc config file in the target directory for the project.
10
+ *
11
+ * If a wrangler.jsonc file already exists it will be overridden.
12
+ *
13
+ * @param projectDir The target directory for the project
14
+ */
15
+ export declare function createWranglerConfigFile(projectDir: string): Promise<void>;
@@ -0,0 +1,63 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getPackageTemplatesDirPath } from "../../utils/get-package-templates-dir-path.js";
4
+ /**
5
+ * Gets the path to the Wrangler configuration file if it exists.
6
+ *
7
+ * @param appDir The directory to check for the Wrangler config file
8
+ * @returns The path to Wrangler config file if it exists, undefined otherwise
9
+ */
10
+ export function findWranglerConfig(appDir) {
11
+ const possibleExts = ["toml", "json", "jsonc"];
12
+ for (const ext of possibleExts) {
13
+ const path = join(appDir, `wrangler.${ext}`);
14
+ if (existsSync(path)) {
15
+ return path;
16
+ }
17
+ }
18
+ return undefined;
19
+ }
20
+ /**
21
+ * Creates a wrangler.jsonc config file in the target directory for the project.
22
+ *
23
+ * If a wrangler.jsonc file already exists it will be overridden.
24
+ *
25
+ * @param projectDir The target directory for the project
26
+ */
27
+ export async function createWranglerConfigFile(projectDir) {
28
+ let wranglerConfig = readFileSync(join(getPackageTemplatesDirPath(), "wrangler.jsonc"), "utf8");
29
+ const appName = getAppNameFromPackageJson(projectDir) ?? "app-name";
30
+ wranglerConfig = wranglerConfig.replaceAll('"<WORKER_NAME>"', JSON.stringify(appName.replaceAll("_", "-")));
31
+ const compatDate = await getLatestCompatDate();
32
+ if (compatDate) {
33
+ wranglerConfig = wranglerConfig.replace(/"compatibility_date": "\d{4}-\d{2}-\d{2}"/, `"compatibility_date": ${JSON.stringify(compatDate)}`);
34
+ }
35
+ writeFileSync(join(projectDir, "wrangler.jsonc"), wranglerConfig);
36
+ }
37
+ function getAppNameFromPackageJson(sourceDir) {
38
+ try {
39
+ const packageJsonStr = readFileSync(join(sourceDir, "package.json"), "utf8");
40
+ const packageJson = JSON.parse(packageJsonStr);
41
+ if (typeof packageJson.name === "string")
42
+ return packageJson.name;
43
+ }
44
+ catch {
45
+ /* empty */
46
+ }
47
+ }
48
+ async function getLatestCompatDate() {
49
+ try {
50
+ const resp = await fetch(`https://registry.npmjs.org/workerd`);
51
+ const latestWorkerdVersion = (await resp.json())["dist-tags"].latest;
52
+ // The format of the workerd version is `major.yyyymmdd.patch`.
53
+ const match = latestWorkerdVersion.match(/\d+\.(\d{4})(\d{2})(\d{2})\.\d+/);
54
+ if (match) {
55
+ const [, year, month, date] = match;
56
+ const compatDate = `${year}-${month}-${date}`;
57
+ return compatDate;
58
+ }
59
+ }
60
+ catch {
61
+ /* empty */
62
+ }
63
+ }
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.15.1",
4
+ "version": "1.16.0",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "opennextjs-cloudflare": "dist/cli/index.js"
@@ -44,7 +44,7 @@
44
44
  "dependencies": {
45
45
  "@ast-grep/napi": "0.40.0",
46
46
  "@dotenvx/dotenvx": "1.31.0",
47
- "@opennextjs/aws": "3.9.12",
47
+ "@opennextjs/aws": "3.9.13",
48
48
  "cloudflare": "^4.4.1",
49
49
  "enquirer": "^2.4.1",
50
50
  "glob": "^12.0.0",
@@ -76,7 +76,7 @@
76
76
  },
77
77
  "peerDependencies": {
78
78
  "wrangler": "^4.59.2",
79
- "next": "^14.2.35 || ~15.0.7 || ~15.1.11 || ~15.2.8 || ~15.3.8 || ~15.4.10 || ~15.5.9 || ^16.0.10"
79
+ "next": "~15.0.8 || ~15.1.12 || ~15.2.9 || ~15.3.9 || ~15.4.11 || ~15.5.10 || ~16.0.11 || ^16.1.5"
80
80
  },
81
81
  "scripts": {
82
82
  "clean": "rimraf dist",