1+ import assert from "node:assert" ;
12import childProcess from "node:child_process" ;
23import fs from "node:fs" ;
34import path from "node:path" ;
@@ -6,14 +7,16 @@ import {
67 checkRunningInsideNextjsApp ,
78 findNextConfig ,
89 findPackagerAndRoot ,
10+ getNextVersion ,
911} from "@opennextjs/aws/build/helper.js" ;
1012import logger from "@opennextjs/aws/logger.js" ;
1113import type yargs from "yargs" ;
1214
1315import { conditionalAppendFileSync } from "../build/utils/files.js" ;
16+ import { askConfirmation } from "../utils/ask-confirmation.js" ;
1417import { createOpenNextConfigFile , findOpenNextConfig } from "../utils/open-next-config.js" ;
1518import { 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 */
0 commit comments