Skip to content

Commit 3853200

Browse files
fix: improve the open-next detection that wrangler deploy performs to eliminate false positives for non open-next projects (#11694)
1 parent 171cfd9 commit 3853200

File tree

4 files changed

+133
-10
lines changed

4 files changed

+133
-10
lines changed

.changeset/vast-regions-chew.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
fix: improve the open-next detection that `wrangler deploy` performs to eliminate false positives for non open-next projects

packages/wrangler/src/__tests__/deploy.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15953,6 +15953,14 @@ export default{
1595315953
"./.open-next/worker.js",
1595415954
"export default { fetch() { return new Response(''); } };"
1595515955
);
15956+
fs.writeFileSync("./next.config.js", "export default {};");
15957+
fs.writeFileSync(
15958+
"./open-next.config.ts",
15959+
dedent`
15960+
import { defineCloudflareConfig } from "@opennextjs/cloudflare";
15961+
export default defineCloudflareConfig();
15962+
`
15963+
);
1595615964

1595715965
await mockAUSRequest([]);
1595815966

@@ -16059,6 +16067,70 @@ export default{
1605916067
expect(std.err).toMatchInlineSnapshot(`""`);
1606016068
expect(std.warn).toMatchInlineSnapshot(`""`);
1606116069
});
16070+
16071+
it("should not delegate to open-next deploy when the Next.js config file is missing (to avoid false positives)", async () => {
16072+
const runCommandSpy = (await import("../autoconfig/c3-vendor/command"))
16073+
.runCommand;
16074+
16075+
await mockOpenNextLikeProject();
16076+
16077+
// Let's delete the next.config.js file
16078+
fs.rmSync("./next.config.js");
16079+
16080+
await runWrangler("deploy");
16081+
16082+
expect(runCommandSpy).not.toHaveBeenCalledOnce();
16083+
16084+
expect(std.out).toMatchInlineSnapshot(`
16085+
"
16086+
⛅️ wrangler x.x.x
16087+
──────────────────
16088+
Total Upload: xx KiB / gzip: xx KiB
16089+
Worker Startup Time: 100 ms
16090+
Your Worker has access to the following bindings:
16091+
Binding Resource
16092+
env.ASSETS Assets
16093+
16094+
Uploaded test-name (TIMINGS)
16095+
Deployed test-name triggers (TIMINGS)
16096+
https://test-name.test-sub-domain.workers.dev
16097+
Current Version ID: Galaxy-Class"
16098+
`);
16099+
expect(std.err).toMatchInlineSnapshot(`""`);
16100+
expect(std.warn).toMatchInlineSnapshot(`""`);
16101+
});
16102+
16103+
it("should not delegate to open-next deploy when the open-next config file is missing (to avoid false positives)", async () => {
16104+
const runCommandSpy = (await import("../autoconfig/c3-vendor/command"))
16105+
.runCommand;
16106+
16107+
await mockOpenNextLikeProject();
16108+
16109+
// Let's delete the open-next.config.ts file
16110+
fs.rmSync("./open-next.config.ts");
16111+
16112+
await runWrangler("deploy");
16113+
16114+
expect(runCommandSpy).not.toHaveBeenCalledOnce();
16115+
16116+
expect(std.out).toMatchInlineSnapshot(`
16117+
"
16118+
⛅️ wrangler x.x.x
16119+
──────────────────
16120+
Total Upload: xx KiB / gzip: xx KiB
16121+
Worker Startup Time: 100 ms
16122+
Your Worker has access to the following bindings:
16123+
Binding Resource
16124+
env.ASSETS Assets
16125+
16126+
Uploaded test-name (TIMINGS)
16127+
Deployed test-name triggers (TIMINGS)
16128+
https://test-name.test-sub-domain.workers.dev
16129+
Current Version ID: Galaxy-Class"
16130+
`);
16131+
expect(std.err).toMatchInlineSnapshot(`""`);
16132+
expect(std.warn).toMatchInlineSnapshot(`""`);
16133+
});
1606216134
});
1606316135
});
1606416136

packages/wrangler/src/autoconfig/frameworks/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,19 @@ export abstract class Framework {
4141
// Make a best-effort attempt to find the exact version of the installed package
4242
export function getInstalledPackageVersion(
4343
packageName: string,
44-
projectPath: string
44+
projectPath: string,
45+
opts: {
46+
stopAtProjectPath?: boolean;
47+
} = {}
4548
): string | undefined {
4649
try {
4750
const packagePath = require.resolve(packageName, {
4851
paths: [projectPath],
4952
});
50-
const packageJsonPath = findUpSync("package.json", { cwd: packagePath });
53+
const packageJsonPath = findUpSync("package.json", {
54+
cwd: packagePath,
55+
stopAt: opts.stopAtProjectPath === true ? projectPath : undefined,
56+
});
5157
if (packageJsonPath) {
5258
const packageJson = parsePackageJSON(
5359
readFileSync(packageJsonPath),

packages/wrangler/src/deploy/open-next.ts

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { readdir } from "node:fs/promises";
12
import { getOpenNextDeployFromEnv } from "@cloudflare/workers-utils";
23
import { runCommand } from "../autoconfig/c3-vendor/command";
34
import { getInstalledPackageVersion } from "../autoconfig/frameworks";
@@ -12,15 +13,9 @@ import { getPackageManager } from "../package-manager";
1213
* @returns true is the deployment has been delegated to open-next, false otherwise
1314
*/
1415
export async function maybeDelegateToOpenNextDeployCommand(
15-
projectRoot: string | undefined
16+
projectRoot: string
1617
): Promise<boolean> {
17-
const openNextVersion =
18-
projectRoot &&
19-
getInstalledPackageVersion("@opennextjs/cloudflare", projectRoot);
20-
21-
const isOpenNextProject = openNextVersion !== undefined;
22-
23-
if (isOpenNextProject) {
18+
if (await isOpenNextProject(projectRoot)) {
2419
const openNextDeploy = getOpenNextDeployFromEnv();
2520
if (!openNextDeploy) {
2621
logger.log(
@@ -41,3 +36,48 @@ export async function maybeDelegateToOpenNextDeployCommand(
4136
}
4237
return false;
4338
}
39+
40+
/**
41+
* Discerns if the project is an open-next one. This check is performed in an assertive way to ensure that
42+
* no false positives happen.
43+
*
44+
* @param projectRoot The path to the project's root
45+
* @returns true if the project is an open-next one, false otherwise
46+
*/
47+
async function isOpenNextProject(projectRoot: string) {
48+
try {
49+
const dirFiles = await readdir(projectRoot);
50+
51+
const nextConfigFile = dirFiles.find((file) =>
52+
/^next\.config\.(m|c)?(ts|js)$/.test(file)
53+
);
54+
55+
if (!nextConfigFile) {
56+
// If there is no next config file then the project is not a Next.js one
57+
return false;
58+
}
59+
60+
const opeNextConfigFile = dirFiles.find((file) =>
61+
/^open-next\.config\.(ts|js)$/.test(file)
62+
);
63+
64+
if (!opeNextConfigFile) {
65+
// If there is no open-next config file then the project is not an OpenNext one
66+
return false;
67+
}
68+
69+
const openNextVersion = getInstalledPackageVersion(
70+
"@opennextjs/cloudflare",
71+
projectRoot,
72+
{
73+
// We stop at the projectPath/root just to make extra sure we don't hit false positives
74+
stopAtProjectPath: true,
75+
}
76+
);
77+
78+
return openNextVersion !== undefined;
79+
} catch {
80+
// If any error is thrown then we simply assume that we're not running in an OpenNext project
81+
return false;
82+
}
83+
}

0 commit comments

Comments
 (0)