Skip to content

Commit b062597

Browse files
dario-piotrowicz2u841rvicbjames-elicx
authored
Add migrate command for initializing open-next for existing Next.js projects (#1083)
Co-authored-by: 2u841r <[email protected]> Co-authored-by: Victor Berchet <[email protected]> Co-authored-by: James Anderson <[email protected]>
1 parent 4279043 commit b062597

8 files changed

Lines changed: 405 additions & 64 deletions

File tree

.changeset/add-init-command.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@opennextjs/cloudflare": minor
3+
---
4+
5+
feature: add `migrate` command to set up OpenNext for Cloudflare adapter
6+
7+
This command helps users migrate existing Next.js applications to the OpenNext Cloudflare adapter by automatically setting up all necessary configuration files, dependencies, and scripts.
8+
9+
To use the command simply run: `npx opennextjs-cloudflare migrate`

packages/cloudflare/src/cli/build/utils/create-config-files.ts

Lines changed: 8 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { cpSync, existsSync, readFileSync, writeFileSync } from "node:fs";
2-
import { join } from "node:path";
3-
4-
import { getPackageTemplatesDirPath } from "../../../utils/get-package-templates-dir-path.js";
51
import type { ProjectOptions } from "../../project-options.js";
62
import { askConfirmation } from "../../utils/ask-confirmation.js";
3+
import { createOpenNextConfigFile, findOpenNextConfig } from "../../utils/open-next-config.js";
4+
import { createWranglerConfigFile, findWranglerConfig } from "../../utils/wrangler-config.js";
75

86
/**
97
* Creates a `wrangler.jsonc` file for the user if a wrangler config file doesn't already exist,
@@ -13,12 +11,8 @@ import { askConfirmation } from "../../utils/ask-confirmation.js";
1311
*
1412
* @param projectOpts The options for the project
1513
*/
16-
export async function createWranglerConfigIfNotExistent(projectOpts: ProjectOptions): Promise<void> {
17-
const possibleExts = ["toml", "json", "jsonc"];
18-
19-
const wranglerConfigFileExists = possibleExts.some((ext) =>
20-
existsSync(join(projectOpts.sourceDir, `wrangler.${ext}`))
21-
);
14+
export async function createWranglerConfigIfNonExistent(projectOpts: ProjectOptions): Promise<void> {
15+
const wranglerConfigFileExists = Boolean(findWranglerConfig(projectOpts.sourceDir));
2216
if (wranglerConfigFileExists) {
2317
return;
2418
}
@@ -36,54 +30,7 @@ export async function createWranglerConfigIfNotExistent(projectOpts: ProjectOpti
3630
return;
3731
}
3832

39-
let wranglerConfig = readFileSync(join(getPackageTemplatesDirPath(), "wrangler.jsonc"), "utf8");
40-
41-
const appName = getAppNameFromPackageJson(projectOpts.sourceDir) ?? "app-name";
42-
43-
wranglerConfig = wranglerConfig.replaceAll('"<WORKER_NAME>"', JSON.stringify(appName.replaceAll("_", "-")));
44-
45-
const compatDate = await getLatestCompatDate();
46-
if (compatDate) {
47-
wranglerConfig = wranglerConfig.replace(
48-
/"compatibility_date": "\d{4}-\d{2}-\d{2}"/,
49-
`"compatibility_date": ${JSON.stringify(compatDate)}`
50-
);
51-
}
52-
53-
writeFileSync(join(projectOpts.sourceDir, "wrangler.jsonc"), wranglerConfig);
54-
}
55-
56-
function getAppNameFromPackageJson(sourceDir: string): string | undefined {
57-
try {
58-
const packageJsonStr = readFileSync(join(sourceDir, "package.json"), "utf8");
59-
const packageJson: Record<string, string> = JSON.parse(packageJsonStr);
60-
if (typeof packageJson.name === "string") return packageJson.name;
61-
} catch {
62-
/* empty */
63-
}
64-
}
65-
66-
export async function getLatestCompatDate(): Promise<string | undefined> {
67-
try {
68-
const resp = await fetch(`https://registry.npmjs.org/workerd`);
69-
const latestWorkerdVersion = (
70-
(await resp.json()) as {
71-
"dist-tags": { latest: string };
72-
}
73-
)["dist-tags"].latest;
74-
75-
// The format of the workerd version is `major.yyyymmdd.patch`.
76-
const match = latestWorkerdVersion.match(/\d+\.(\d{4})(\d{2})(\d{2})\.\d+/);
77-
78-
if (match) {
79-
const [, year, month, date] = match;
80-
const compatDate = `${year}-${month}-${date}`;
81-
82-
return compatDate;
83-
}
84-
} catch {
85-
/* empty */
86-
}
33+
await createWranglerConfigFile(projectOpts.sourceDir);
8734
}
8835

8936
/**
@@ -95,9 +42,8 @@ export async function getLatestCompatDate(): Promise<string | undefined> {
9542
* @return The path to the created source file
9643
*/
9744
export async function createOpenNextConfigIfNotExistent(sourceDir: string): Promise<string> {
98-
const openNextConfigPath = join(sourceDir, "open-next.config.ts");
99-
100-
if (!existsSync(openNextConfigPath)) {
45+
const openNextConfigPath = findOpenNextConfig(sourceDir);
46+
if (!openNextConfigPath) {
10147
const answer = await askConfirmation(
10248
"Missing required `open-next.config.ts` file, do you want to create one?"
10349
);
@@ -106,7 +52,7 @@ export async function createOpenNextConfigIfNotExistent(sourceDir: string): Prom
10652
throw new Error("The `open-next.config.ts` file is required, aborting!");
10753
}
10854

109-
cpSync(join(getPackageTemplatesDirPath(), "open-next.config.ts"), openNextConfigPath);
55+
return createOpenNextConfigFile(sourceDir);
11056
}
11157

11258
return openNextConfigPath;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import fs from "node:fs";
2+
3+
/**
4+
* Appends text to a file
5+
*
6+
* When the file does not exists, it is always created with the text content.
7+
* When the file exists, the text is appended only when the predicate return `true`.
8+
*
9+
* @param filepath The path to the file.
10+
* @param text The text to append to the file.
11+
* @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.
12+
*/
13+
export function conditionalAppendFileSync(
14+
filepath: string,
15+
text: string,
16+
condition: (fileContent: string) => boolean
17+
): void {
18+
const fileExists = fs.existsSync(filepath);
19+
20+
if (!fileExists || condition(fs.readFileSync(filepath, "utf8"))) {
21+
fs.appendFileSync(filepath, text);
22+
}
23+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type yargs from "yargs";
22

33
import { build as buildImpl } from "../build/build.js";
4-
import { createWranglerConfigIfNotExistent } from "../build/utils/index.js";
4+
import { createWranglerConfigIfNonExistent } from "../build/utils/index.js";
55
import type { WithWranglerArgs } from "./utils.js";
66
import {
77
compileConfig,
@@ -38,7 +38,7 @@ async function buildCommand(
3838
// Note: We don't ask when a custom config file is specified via `--config`
3939
// nor when `--skipWranglerConfigCheck` is used.
4040
if (!projectOpts.wranglerConfigPath && !args.skipWranglerConfigCheck) {
41-
await createWranglerConfigIfNotExistent(projectOpts);
41+
await createWranglerConfigIfNonExistent(projectOpts);
4242
}
4343

4444
const wranglerConfig = await readWranglerConfig(args);

0 commit comments

Comments
 (0)