Skip to content

Commit e965e4d

Browse files
authored
refactor: creation of config files (#1146)
1 parent 5ca6a92 commit e965e4d

File tree

5 files changed

+254
-65
lines changed

5 files changed

+254
-65
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import logger from "@opennextjs/aws/logger.js";
2+
import { afterEach, describe, expect, it, vi } from "vitest";
3+
4+
import { askConfirmation } from "../utils/ask-confirmation.js";
5+
import { createWranglerConfigFile } from "../utils/create-wrangler-config.js";
6+
import { buildCommand } from "./build.js";
7+
8+
// Mock logger
9+
vi.mock("@opennextjs/aws/logger.js", () => ({
10+
default: {
11+
info: vi.fn(),
12+
warn: vi.fn(),
13+
error: vi.fn(),
14+
debug: vi.fn(),
15+
setLevel: vi.fn(),
16+
},
17+
}));
18+
19+
// Mock build implementation
20+
vi.mock("../build/build.js", () => ({
21+
build: vi.fn(),
22+
}));
23+
24+
// Mock askConfirmation
25+
vi.mock("../utils/ask-confirmation.js", () => ({
26+
askConfirmation: vi.fn(),
27+
}));
28+
29+
// Mock create-wrangler-config: findWranglerConfig returns undefined (no config found)
30+
vi.mock("../utils/create-wrangler-config.js", () => ({
31+
findWranglerConfig: vi.fn(() => undefined),
32+
createWranglerConfigFile: vi.fn(async () => ({ cachingEnabled: false })),
33+
}));
34+
35+
// Mock utils
36+
vi.mock("./utils/utils.js", () => ({
37+
compileConfig: vi.fn(async () => ({ config: {}, buildDir: "" })),
38+
getNormalizedOptions: vi.fn(() => ({})),
39+
readWranglerConfig: vi.fn(async () => ({})),
40+
printHeaders: vi.fn(),
41+
nextAppDir: "/test",
42+
withWranglerOptions: vi.fn(),
43+
withWranglerPassthroughArgs: vi.fn(),
44+
}));
45+
46+
const defaultArgs = {
47+
skipNextBuild: false,
48+
noMinify: false,
49+
skipWranglerConfigCheck: false,
50+
openNextConfigPath: undefined,
51+
dangerouslyUseUnsupportedNextVersion: false,
52+
wranglerArgs: [],
53+
wranglerConfigPath: undefined,
54+
env: undefined,
55+
};
56+
57+
describe("buildCommand", () => {
58+
afterEach(() => {
59+
vi.restoreAllMocks();
60+
});
61+
62+
it("should create wrangler config when user confirms", async () => {
63+
vi.mocked(askConfirmation).mockResolvedValue(true);
64+
65+
await buildCommand(defaultArgs);
66+
67+
expect(askConfirmation).toHaveBeenCalledOnce();
68+
expect(vi.mocked(askConfirmation).mock.calls[0]).toMatchInlineSnapshot(`
69+
[
70+
"No \`wrangler.(toml|json|jsonc)\` config file found, do you want to create one?",
71+
]
72+
`);
73+
expect(createWranglerConfigFile).toHaveBeenCalledOnce();
74+
expect(logger.warn).not.toHaveBeenCalled();
75+
});
76+
77+
it("should warn when user declines wrangler config creation", async () => {
78+
vi.mocked(askConfirmation).mockResolvedValue(false);
79+
80+
await buildCommand(defaultArgs);
81+
82+
expect(askConfirmation).toHaveBeenCalledOnce();
83+
expect(createWranglerConfigFile).not.toHaveBeenCalled();
84+
expect(vi.mocked(logger.warn).mock.calls[0]).toMatchInlineSnapshot(`
85+
[
86+
"No Wrangler config file created
87+
88+
(to avoid this check use the \`--skipWranglerConfigCheck\` flag or set a \`SKIP_WRANGLER_CONFIG_CHECK\` environment variable to \`yes\`)",
89+
]
90+
`);
91+
});
92+
});

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import logger from "@opennextjs/aws/logger.js";
12
import type yargs from "yargs";
23

34
import { build as buildImpl } from "../build/build.js";
4-
import { createWranglerConfigIfNonExistent } from "../utils/create-config-files.js";
5+
import { askConfirmation } from "../utils/ask-confirmation.js";
6+
import { createWranglerConfigFile, findWranglerConfig } from "../utils/create-wrangler-config.js";
57
import type { WithWranglerArgs } from "./utils/utils.js";
68
import {
79
compileConfig,
@@ -18,7 +20,7 @@ import {
1820
*
1921
* @param args
2022
*/
21-
async function buildCommand(
23+
export async function buildCommand(
2224
args: WithWranglerArgs<{
2325
skipNextBuild: boolean;
2426
noMinify: boolean;
@@ -38,7 +40,16 @@ async function buildCommand(
3840
// Note: We don't ask when a custom config file is specified via `--config`
3941
// nor when `--skipWranglerConfigCheck` is used.
4042
if (!projectOpts.wranglerConfigPath && !args.skipWranglerConfigCheck) {
41-
await createWranglerConfigIfNonExistent(projectOpts);
43+
if (!findWranglerConfig(projectOpts.sourceDir)) {
44+
const confirmCreate = "No `wrangler.(toml|json|jsonc)` config file found, do you want to create one?";
45+
if (await askConfirmation(confirmCreate)) {
46+
await createWranglerConfigFile(projectOpts.sourceDir);
47+
} else {
48+
logger.warn(`No Wrangler config file created
49+
50+
(to avoid this check use the \`--skipWranglerConfigCheck\` flag or set a \`SKIP_WRANGLER_CONFIG_CHECK\` environment variable to \`yes\`)`);
51+
}
52+
}
4253
}
4354

4455
const wranglerConfig = await readWranglerConfig(args);
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { afterEach, describe, expect, it, vi } from "vitest";
2+
3+
import { askConfirmation } from "../../utils/ask-confirmation.js";
4+
import { createOpenNextConfigFile, findOpenNextConfig } from "../../utils/create-open-next-config.js";
5+
import { compileConfig } from "./utils.js";
6+
7+
const { mockExistsSync } = vi.hoisted(() => ({
8+
mockExistsSync: vi.fn(),
9+
}));
10+
11+
// Mock node:fs — only override existsSync
12+
vi.mock("node:fs", async (importOriginal) => {
13+
const mod = await importOriginal<typeof import("node:fs")>();
14+
return { ...mod, existsSync: mockExistsSync };
15+
});
16+
17+
// Mock logger
18+
vi.mock("@opennextjs/aws/logger.js", () => ({
19+
default: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), setLevel: vi.fn() },
20+
}));
21+
22+
// Mock compileOpenNextConfig
23+
const mockCompileOpenNextConfig = vi.fn(async () => ({
24+
config: { default: {} },
25+
buildDir: "/build",
26+
}));
27+
vi.mock("@opennextjs/aws/build/compileConfig.js", () => ({
28+
compileOpenNextConfig: (...args: unknown[]) => mockCompileOpenNextConfig(...args),
29+
}));
30+
31+
// Mock ensureCloudflareConfig
32+
vi.mock("../../build/utils/ensure-cf-config.js", () => ({
33+
ensureCloudflareConfig: vi.fn(),
34+
}));
35+
36+
// Mock askConfirmation
37+
vi.mock("../../utils/ask-confirmation.js", () => ({
38+
askConfirmation: vi.fn(),
39+
}));
40+
41+
// Mock create-config-files (unused import in utils.ts but required for module resolution)
42+
vi.mock("../../utils/create-config-files.js", () => ({
43+
createOpenNextConfigIfNotExistent: vi.fn(),
44+
}));
45+
46+
// Mock create-open-next-config
47+
vi.mock("../../utils/create-open-next-config.js", () => ({
48+
findOpenNextConfig: vi.fn(),
49+
createOpenNextConfigFile: vi.fn(() => "/test/open-next.config.ts"),
50+
}));
51+
52+
// Mock wrangler
53+
vi.mock("wrangler", () => ({
54+
unstable_readConfig: vi.fn(),
55+
}));
56+
57+
// Mock build utils
58+
vi.mock("@opennextjs/aws/build/utils.js", () => ({
59+
printHeader: vi.fn(),
60+
showWarningOnWindows: vi.fn(),
61+
}));
62+
63+
// Mock build helper
64+
vi.mock("@opennextjs/aws/build/helper.js", () => ({
65+
normalizeOptions: vi.fn(() => ({})),
66+
}));
67+
68+
describe("compileConfig", () => {
69+
afterEach(() => {
70+
vi.restoreAllMocks();
71+
});
72+
73+
it("should compile config when configPath is provided and file exists", async () => {
74+
mockExistsSync.mockReturnValue(true);
75+
76+
const result = await compileConfig("/app/open-next.config.ts");
77+
78+
expect(mockCompileOpenNextConfig).toHaveBeenCalledWith("/app/open-next.config.ts", { compileEdge: true });
79+
expect(result).toEqual({ config: { default: {} }, buildDir: "/build" });
80+
});
81+
82+
it("should throw when configPath is provided but file does not exist", async () => {
83+
mockExistsSync.mockReturnValue(false);
84+
85+
await expect(compileConfig("/app/missing-config.ts")).rejects.toThrowErrorMatchingInlineSnapshot(
86+
`[Error: Custom config file not found at /app/missing-config.ts]`
87+
);
88+
});
89+
90+
it("should compile config when no configPath is provided but one is found", async () => {
91+
vi.mocked(findOpenNextConfig).mockReturnValue("/app/open-next.config.ts");
92+
93+
const result = await compileConfig(undefined);
94+
95+
expect(findOpenNextConfig).toHaveBeenCalledOnce();
96+
expect(mockCompileOpenNextConfig).toHaveBeenCalledWith("/app/open-next.config.ts", { compileEdge: true });
97+
expect(result).toEqual({ config: { default: {} }, buildDir: "/build" });
98+
});
99+
100+
it("should create config when no configPath found and user confirms", async () => {
101+
vi.mocked(findOpenNextConfig).mockReturnValue(undefined);
102+
vi.mocked(askConfirmation).mockResolvedValue(true);
103+
104+
const result = await compileConfig(undefined);
105+
106+
expect(askConfirmation).toHaveBeenCalledOnce();
107+
expect(vi.mocked(askConfirmation).mock.calls[0]).toMatchInlineSnapshot(`
108+
[
109+
"Missing required \`open-next.config.ts\` file, do you want to create one?",
110+
]
111+
`);
112+
expect(createOpenNextConfigFile).toHaveBeenCalledOnce();
113+
expect(mockCompileOpenNextConfig).toHaveBeenCalledWith("/test/open-next.config.ts", {
114+
compileEdge: true,
115+
});
116+
expect(result).toEqual({ config: { default: {} }, buildDir: "/build" });
117+
});
118+
119+
it("should throw when no configPath found and user declines", async () => {
120+
vi.mocked(findOpenNextConfig).mockReturnValue(undefined);
121+
vi.mocked(askConfirmation).mockResolvedValue(false);
122+
123+
await expect(compileConfig(undefined)).rejects.toThrowErrorMatchingInlineSnapshot(
124+
`[Error: The \`open-next.config.ts\` file is required, aborting!]`
125+
);
126+
127+
expect(askConfirmation).toHaveBeenCalledOnce();
128+
expect(createOpenNextConfigFile).not.toHaveBeenCalled();
129+
});
130+
});

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import type yargs from "yargs";
1212

1313
import type { OpenNextConfig } from "../../../api/config.js";
1414
import { ensureCloudflareConfig } from "../../build/utils/ensure-cf-config.js";
15-
import { createOpenNextConfigIfNotExistent } from "../../utils/create-config-files.js";
15+
import { askConfirmation } from "../../utils/ask-confirmation.js";
16+
import { createOpenNextConfigFile, findOpenNextConfig } from "../../utils/create-open-next-config.js";
1617

1718
export type WithWranglerArgs<T = unknown> = T & {
1819
// Array of arguments that can be given to wrangler commands, including the `--config` and `--env` args.
@@ -42,16 +43,30 @@ export function printHeaders(command: string) {
4243
*
4344
* When users specify a custom config file but it doesn't exist, we throw an Error.
4445
*
46+
* @throws If a custom config path is provided but the file does not exist.
47+
* @throws If no config file is found and the user declines to create one.
48+
*
4549
* @param configPath Optional path to the config file. Absolute or relative to cwd.
46-
* @returns OpenNext config.
50+
* @returns The compiled OpenNext config and the build directory.
51+
*
4752
*/
4853
export async function compileConfig(configPath: string | undefined) {
4954
if (configPath && !existsSync(configPath)) {
5055
throw new Error(`Custom config file not found at ${configPath}`);
5156
}
5257

58+
configPath ??= findOpenNextConfig(nextAppDir);
59+
5360
if (!configPath) {
54-
configPath = await createOpenNextConfigIfNotExistent(nextAppDir);
61+
const answer = await askConfirmation(
62+
"Missing required `open-next.config.ts` file, do you want to create one?"
63+
);
64+
65+
if (!answer) {
66+
throw new Error("The `open-next.config.ts` file is required, aborting!");
67+
}
68+
69+
configPath = createOpenNextConfigFile(nextAppDir, { cache: false });
5570
}
5671

5772
const { config, buildDir } = await compileOpenNextConfig(configPath, { compileEdge: true });

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

Lines changed: 0 additions & 59 deletions
This file was deleted.

0 commit comments

Comments
 (0)