Skip to content

Commit 2b437f1

Browse files
In the migrate command, add an initial step to create a Next.js config file (required by open-next) if it doesn't exist (#1127)
1 parent 8c3a36e commit 2b437f1

4 files changed

Lines changed: 125 additions & 34 deletions

File tree

.changeset/light-hats-tan.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@opennextjs/cloudflare": patch
3+
---
4+
5+
In the `migrate` command, add an initial step to create a Next.js config file (required by open-next) if it doesn't exist

packages/cloudflare/src/cli/build/build.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import logger from "@opennextjs/aws/logger.js";
99
import type { Unstable_Config } from "wrangler";
1010

1111
import { OpenNextConfig } from "../../api/config.js";
12+
import { ensureNextjsVersionSupported } from "../commands/utils.js";
1213
import type { ProjectOptions } from "../project-options.js";
1314
import { bundleServer } from "./bundle-server.js";
1415
import { compileCacheAssetsManifestSqlFile } from "./open-next/compile-cache-assets-manifest.js";
@@ -104,25 +105,3 @@ export async function build(
104105

105106
logger.info("OpenNext build complete.");
106107
}
107-
108-
async function ensureNextjsVersionSupported({ nextVersion }: buildHelper.BuildOptions) {
109-
if (buildHelper.compareSemver(nextVersion, "<", "14.2.0")) {
110-
logger.error("Next.js version unsupported, please upgrade to version 14.2 or greater.");
111-
process.exit(1);
112-
}
113-
114-
const {
115-
default: { version: wranglerVersion },
116-
} = await import("wrangler/package.json", { with: { type: "json" } });
117-
118-
// We need a version of workerd that has a fix for setImmediate for Next.js 16.1+
119-
// See:
120-
// - https://github.com/cloudflare/workerd/pull/5869
121-
// - https://github.com/opennextjs/opennextjs-cloudflare/issues/1049
122-
if (
123-
buildHelper.compareSemver(nextVersion, ">=", "16.1.0") &&
124-
buildHelper.compareSemver(wranglerVersion, "<", "4.59.2")
125-
) {
126-
logger.warn(`Next.js 16.1+ requires wrangler 4.59.2 or greater (${wranglerVersion} detected).`);
127-
}
128-
}

packages/cloudflare/src/cli/commands/migrate.ts

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import assert from "node:assert";
12
import childProcess from "node:child_process";
23
import fs from "node:fs";
34
import path from "node:path";
@@ -6,14 +7,16 @@ import {
67
checkRunningInsideNextjsApp,
78
findNextConfig,
89
findPackagerAndRoot,
10+
getNextVersion,
911
} from "@opennextjs/aws/build/helper.js";
1012
import logger from "@opennextjs/aws/logger.js";
1113
import type yargs from "yargs";
1214

1315
import { conditionalAppendFileSync } from "../build/utils/files.js";
16+
import { askConfirmation } from "../utils/ask-confirmation.js";
1417
import { createOpenNextConfigFile, findOpenNextConfig } from "../utils/open-next-config.js";
1518
import { createWranglerConfigFile, findWranglerConfig } from "../utils/wrangler-config.js";
16-
import { printHeaders } from "./utils.js";
19+
import { ensureNextjsVersionSupported, printHeaders } from "./utils.js";
1720

1821
/**
1922
* Implementation of the `opennextjs-cloudflare migrate` command.
@@ -27,6 +30,18 @@ async function migrateCommand(args: { forceInstall: boolean }): Promise<void> {
2730

2831
const projectDir = process.cwd();
2932

33+
const nextConfigFileCreated = await maybeCreateNextConfigFileIfMissing(projectDir, args.forceInstall).catch(
34+
(e) => {
35+
logger.error(`${e instanceof Error ? e.message : e}\n`);
36+
process.exit(1);
37+
}
38+
);
39+
40+
if (nextConfigFileCreated === false) {
41+
logger.error("The next.config file is required, aborting!\n");
42+
process.exit(1);
43+
}
44+
3045
checkRunningInsideNextjsApp({ appPath: projectDir });
3146

3247
const wranglerConfigFilePath = findWranglerConfig(projectDir);
@@ -130,17 +145,19 @@ async function migrateCommand(args: { forceInstall: boolean }): Promise<void> {
130145

131146
const nextConfig = findNextConfig({ appPath: projectDir });
132147

133-
if (nextConfig) {
134-
printStepTitle("Updating Next.js config");
135-
conditionalAppendFileSync(
136-
nextConfig.path,
137-
"import('@opennextjs/cloudflare').then(m => m.initOpenNextCloudflareForDev());\n",
138-
{
139-
appendIf: (content) => !content.includes("initOpenNextCloudflareForDev"),
140-
appendPrefix: "\n",
141-
}
142-
);
143-
}
148+
// At this point the next config file should exist (it either
149+
// was part of the original project or we've created it)
150+
assert(nextConfig, "Next config file unexpectedly missing");
151+
152+
printStepTitle("Updating Next.js config");
153+
conditionalAppendFileSync(
154+
nextConfig.path,
155+
"import('@opennextjs/cloudflare').then(m => m.initOpenNextCloudflareForDev());\n",
156+
{
157+
appendIf: (content) => !content.includes("initOpenNextCloudflareForDev"),
158+
appendPrefix: "\n",
159+
}
160+
);
144161

145162
printStepTitle("Checking for edge runtime usage");
146163
try {
@@ -235,6 +252,64 @@ function printStepTitle(title: string): void {
235252
logger.info(`⚙️ ${title}...\n`);
236253
}
237254

255+
/**
256+
* Creates a plain next.config.ts file
257+
*
258+
* @param appDir The directory where the config file should be created
259+
*/
260+
function createNextConfigFile(appDir: string): void {
261+
const nextConfigPath = path.join(appDir, "next.config.ts");
262+
const content = `import type { NextConfig } from "next";
263+
264+
const nextConfig: NextConfig = {};
265+
266+
export default nextConfig;
267+
`;
268+
fs.writeFileSync(nextConfigPath, content);
269+
}
270+
271+
/**
272+
* Creates a next.config.ts file, after asking for the user's confirmation, if missing in the project's directory.
273+
*
274+
* To be safe, this function also ensures that the "next" package is installed and its version is compatible with OpenNext.
275+
*
276+
* @param projectDir The project directory to check
277+
* @param skipNextVersionCheck Whether to bypass the "next" version compatibility check
278+
* @returns A boolean representing whether the user has accepter the creation of the config file, undefined if the file already existed
279+
* @throws {Error} If "next" is not installed or the Next.js version is incompatible with open-next
280+
*/
281+
async function maybeCreateNextConfigFileIfMissing(
282+
projectDir: string,
283+
skipNextVersionCheck: boolean
284+
): Promise<boolean | undefined> {
285+
if (findNextConfig({ appPath: projectDir })) {
286+
return;
287+
}
288+
289+
let nextVersion: string;
290+
try {
291+
nextVersion = getNextVersion(projectDir);
292+
} catch {
293+
throw new Error(
294+
"This does not appear to be a Next.js application. The 'next' package is not installed and no next.config file was found."
295+
);
296+
}
297+
298+
if (!skipNextVersionCheck) {
299+
await ensureNextjsVersionSupported({ nextVersion });
300+
}
301+
302+
const answer = await askConfirmation("Missing required next.config file. Do you want to create one?");
303+
304+
if (!answer) {
305+
return false;
306+
}
307+
308+
createNextConfigFile(projectDir);
309+
logger.info("Created next.config.ts\n");
310+
return true;
311+
}
312+
238313
/**
239314
* Add the `migrate` command to yargs configuration.
240315
*/

packages/cloudflare/src/cli/commands/utils.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import url from "node:url";
55

66
import { compileOpenNextConfig } from "@opennextjs/aws/build/compileConfig.js";
77
import { normalizeOptions } from "@opennextjs/aws/build/helper.js";
8+
import * as buildHelper from "@opennextjs/aws/build/helper.js";
89
import { printHeader, showWarningOnWindows } from "@opennextjs/aws/build/utils.js";
910
import logger from "@opennextjs/aws/logger.js";
1011
import { unstable_readConfig } from "wrangler";
@@ -33,6 +34,37 @@ export function printHeaders(command: string) {
3334
showWarningOnWindows();
3435
}
3536

37+
/**
38+
* Validates that the Next.js version is supported and checks wrangler compatibility.
39+
*
40+
* Note: this function assumes that wrangler is installed.
41+
*
42+
* @param options.nextVersion The detected Next.js version string
43+
* @throws {Error} If the Next.js version is unsupported
44+
*/
45+
export async function ensureNextjsVersionSupported({
46+
nextVersion,
47+
}: Pick<buildHelper.BuildOptions, "nextVersion">) {
48+
if (buildHelper.compareSemver(nextVersion, "<", "14.2.0")) {
49+
throw new Error("Next.js version unsupported, please upgrade to version 14.2 or greater.");
50+
}
51+
52+
const {
53+
default: { version: wranglerVersion },
54+
} = await import("wrangler/package.json", { with: { type: "json" } });
55+
56+
// We need a version of workerd that has a fix for setImmediate for Next.js 16.1+
57+
// See:
58+
// - https://github.com/cloudflare/workerd/pull/5869
59+
// - https://github.com/opennextjs/opennextjs-cloudflare/issues/1049
60+
if (
61+
buildHelper.compareSemver(nextVersion, ">=", "16.1.0") &&
62+
buildHelper.compareSemver(wranglerVersion, "<", "4.59.2")
63+
) {
64+
logger.warn(`Next.js 16.1+ requires wrangler 4.59.2 or greater (${wranglerVersion} detected).`);
65+
}
66+
}
67+
3668
/**
3769
* Compile the OpenNext config.
3870
*

0 commit comments

Comments
 (0)