Skip to content

Commit 240ebeb

Browse files
autoconfig small refactoring (#11183)
* expose programmatic autoconfig API * remove unnecessary `await`s * add new `displayAutoConfigDetails()` utility
1 parent 1ae020d commit 240ebeb

File tree

7 files changed

+269
-39
lines changed

7 files changed

+269
-39
lines changed

.changeset/bitter-emus-jog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
expose `experimental_getDetailsForAutoConfig` and `experimental_runAutoConfig` APIs that provide respectively the autoconfig detection and execution functionalities
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import { writeFile } from "node:fs/promises";
2+
import { join } from "node:path";
3+
import { describe, expect, it } from "vitest";
4+
import { displayAutoConfigDetails } from "../../autoconfig/details";
5+
import * as details from "../../autoconfig/details";
6+
import { clearOutputFilePath } from "../../output";
7+
import { mockConsoleMethods } from "../helpers/mock-console";
8+
import { useMockIsTTY } from "../helpers/mock-istty";
9+
import { runInTempDir } from "../helpers/run-in-tmp";
10+
import { seed } from "../helpers/seed";
11+
import type { Config } from "@cloudflare/workers-utils";
12+
13+
vi.mock("../../package-manager", () => ({
14+
getPackageManager() {
15+
return {
16+
type: "npm",
17+
npx: "npx",
18+
};
19+
},
20+
}));
21+
22+
describe("autoconfig details", () => {
23+
describe("getDetailsForAutoConfig()", () => {
24+
runInTempDir();
25+
const { setIsTTY } = useMockIsTTY();
26+
mockConsoleMethods();
27+
28+
beforeEach(() => {
29+
setIsTTY(true);
30+
});
31+
32+
afterEach(() => {
33+
vi.unstubAllGlobals();
34+
clearOutputFilePath();
35+
});
36+
37+
it("should set configured: true if a configPath exists", async () => {
38+
await expect(
39+
details.getDetailsForAutoConfig({
40+
wranglerConfig: { configPath: "/tmp" } as Config,
41+
})
42+
).resolves.toMatchObject({ configured: true });
43+
});
44+
45+
// Check that Astro is detected. We don't want to duplicate the tests of @netlify/build-info
46+
// by exhaustively checking every possible combination
47+
it("should perform basic framework detection", async () => {
48+
await writeFile(
49+
"package.json",
50+
JSON.stringify({
51+
dependencies: {
52+
astro: "5",
53+
},
54+
})
55+
);
56+
57+
await expect(details.getDetailsForAutoConfig()).resolves.toMatchObject({
58+
buildCommand: "astro build",
59+
configured: false,
60+
outputDir: "dist",
61+
packageJson: {
62+
dependencies: {
63+
astro: "5",
64+
},
65+
},
66+
});
67+
});
68+
69+
it("should bail when multiple frameworks are detected", async () => {
70+
await writeFile(
71+
"package.json",
72+
JSON.stringify({
73+
dependencies: {
74+
astro: "5",
75+
gatsby: "5",
76+
},
77+
})
78+
);
79+
80+
await expect(
81+
details.getDetailsForAutoConfig()
82+
).rejects.toThrowErrorMatchingInlineSnapshot(
83+
`[Error: Wrangler was unable to automatically configure your project to work with Cloudflare, since multiple frameworks were found: Astro, Gatsby]`
84+
);
85+
});
86+
87+
it("should use npm build instead of framework build if present", async () => {
88+
await writeFile(
89+
"package.json",
90+
JSON.stringify({
91+
scripts: {
92+
build: "echo build",
93+
},
94+
dependencies: {
95+
astro: "5",
96+
},
97+
})
98+
);
99+
100+
await expect(details.getDetailsForAutoConfig()).resolves.toMatchObject({
101+
buildCommand: "npm run build",
102+
});
103+
});
104+
105+
it("outputDir should be empty if nothing can be detected", async () => {
106+
await expect(details.getDetailsForAutoConfig()).resolves.toMatchObject({
107+
outputDir: undefined,
108+
});
109+
});
110+
111+
it("outputDir should be set to cwd if an index.html file exists", async () => {
112+
await writeFile("index.html", `<h1>Hello World</h1>`);
113+
114+
await expect(details.getDetailsForAutoConfig()).resolves.toMatchObject({
115+
outputDir: process.cwd(),
116+
});
117+
});
118+
119+
it("outputDir should find first child directory with an index.html file", async () => {
120+
await seed({
121+
"public/index.html": `<h1>Hello World</h1>`,
122+
"random/index.html": `<h1>Hello World</h1>`,
123+
});
124+
125+
await expect(details.getDetailsForAutoConfig()).resolves.toMatchObject({
126+
outputDir: join(process.cwd(), "public"),
127+
});
128+
});
129+
130+
it("outputDir should prioritize the project directory over its child directories", async () => {
131+
await seed({
132+
"index.html": `<h1>Hello World</h1>`,
133+
"public/index.html": `<h1>Hello World</h1>`,
134+
});
135+
136+
await expect(details.getDetailsForAutoConfig()).resolves.toMatchObject({
137+
outputDir: process.cwd(),
138+
});
139+
});
140+
});
141+
142+
describe("displayAutoConfigDetails()", () => {
143+
const std = mockConsoleMethods();
144+
145+
it("should cleanly handle a case in which no settings have been detected", () => {
146+
displayAutoConfigDetails({
147+
configured: false,
148+
projectPath: process.cwd(),
149+
});
150+
expect(std.out).toMatchInlineSnapshot(
151+
`"No Project Settings Auto-detected"`
152+
);
153+
});
154+
155+
it("should display all the project settings provided by the details object", () => {
156+
displayAutoConfigDetails({
157+
configured: false,
158+
projectPath: process.cwd(),
159+
framework: { name: "Astro", configured: false, configure: () => ({}) },
160+
buildCommand: "astro build",
161+
outputDir: "dist",
162+
});
163+
expect(std.out).toMatchInlineSnapshot(`
164+
"Auto-detected Project Settings:
165+
- Framework: Astro
166+
- Build Command: astro build
167+
- Output Directory: dist
168+
"
169+
`);
170+
});
171+
172+
it("should omit the framework entry when they it is not part of the details object", () => {
173+
displayAutoConfigDetails({
174+
configured: false,
175+
projectPath: process.cwd(),
176+
buildCommand: "npm run build",
177+
outputDir: "dist",
178+
});
179+
expect(std.out).toMatchInlineSnapshot(`
180+
"Auto-detected Project Settings:
181+
- Build Command: npm run build
182+
- Output Directory: dist
183+
"
184+
`);
185+
});
186+
187+
it("should omit the framework and build command entries when they are not part of the details object", () => {
188+
displayAutoConfigDetails({
189+
configured: false,
190+
projectPath: process.cwd(),
191+
outputDir: "dist",
192+
});
193+
expect(std.out).toMatchInlineSnapshot(`
194+
"Auto-detected Project Settings:
195+
- Output Directory: dist
196+
"
197+
`);
198+
});
199+
});
200+
});

packages/wrangler/src/__tests__/autoconfig.test.ts renamed to packages/wrangler/src/__tests__/autoconfig/run.test.ts

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,26 @@ import { writeFile } from "node:fs/promises";
33
import { join } from "node:path";
44
import { FatalError, readFileSync } from "@cloudflare/workers-utils";
55
import { vi } from "vitest";
6-
import * as c3 from "../autoconfig/c3-vendor/packages";
7-
import * as details from "../autoconfig/get-details";
8-
import * as run from "../autoconfig/run";
9-
import * as format from "../deployment-bundle/guess-worker-format";
10-
import { clearOutputFilePath } from "../output";
11-
import * as compatDate from "../utils/compatibility-date";
12-
import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
13-
import { mockConsoleMethods } from "./helpers/mock-console";
14-
import { clearDialogs, mockConfirm } from "./helpers/mock-dialogs";
15-
import { useMockIsTTY } from "./helpers/mock-istty";
16-
import { runInTempDir } from "./helpers/run-in-tmp";
17-
import { runWrangler } from "./helpers/run-wrangler";
18-
import { seed } from "./helpers/seed";
19-
import { writeWorkerSource } from "./helpers/write-worker-source";
20-
import { writeWranglerConfig } from "./helpers/write-wrangler-config";
21-
import type { Framework } from "../autoconfig/frameworks";
6+
import * as c3 from "../../autoconfig/c3-vendor/packages";
7+
import * as details from "../../autoconfig/details";
8+
import * as run from "../../autoconfig/run";
9+
import * as format from "../../deployment-bundle/guess-worker-format";
10+
import { clearOutputFilePath } from "../../output";
11+
import * as compatDate from "../../utils/compatibility-date";
12+
import { mockAccountId, mockApiToken } from "../helpers/mock-account-id";
13+
import { mockConsoleMethods } from "../helpers/mock-console";
14+
import { clearDialogs, mockConfirm } from "../helpers/mock-dialogs";
15+
import { useMockIsTTY } from "../helpers/mock-istty";
16+
import { runInTempDir } from "../helpers/run-in-tmp";
17+
import { runWrangler } from "../helpers/run-wrangler";
18+
import { seed } from "../helpers/seed";
19+
import { writeWorkerSource } from "../helpers/write-worker-source";
20+
import { writeWranglerConfig } from "../helpers/write-wrangler-config";
21+
import type { Framework } from "../../autoconfig/frameworks";
2222
import type { Config } from "@cloudflare/workers-utils";
2323
import type { MockInstance } from "vitest";
2424

25-
vi.mock("../package-manager", () => ({
25+
vi.mock("../../package-manager", () => ({
2626
getPackageManager() {
2727
return {
2828
type: "npm",
@@ -265,10 +265,11 @@ describe("autoconfig (deploy)", () => {
265265
});
266266

267267
expect(std.out).toMatchInlineSnapshot(`
268-
"Project settings detected:
269-
Framework: fake
270-
Build Command: echo 'built' > build.txt
271-
Output Directory: dist
268+
"Auto-detected Project Settings:
269+
- Framework: fake
270+
- Build Command: echo 'built' > build.txt
271+
- Output Directory: dist
272+
272273
[build] Running: echo 'built' > build.txt"
273274
`);
274275

packages/wrangler/src/autoconfig/get-details.ts renamed to packages/wrangler/src/autoconfig/details.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { readdir, stat } from "node:fs/promises";
22
import { join, resolve } from "node:path";
3+
import { brandColor } from "@cloudflare/cli/colors";
34
import {
45
FatalError,
56
parsePackageJSON,
@@ -119,3 +120,30 @@ export async function getDetailsForAutoConfig({
119120
outputDir: detectedFramework?.dist ?? (await findAssetsDir(projectPath)),
120121
};
121122
}
123+
124+
export function displayAutoConfigDetails(
125+
autoConfigDetails: AutoConfigDetails
126+
): void {
127+
if (
128+
!autoConfigDetails.framework &&
129+
!autoConfigDetails.buildCommand &&
130+
!autoConfigDetails.outputDir
131+
) {
132+
logger.log("No Project Settings Auto-detected");
133+
return;
134+
}
135+
136+
logger.log("Auto-detected Project Settings:");
137+
138+
if (autoConfigDetails.framework) {
139+
logger.log(brandColor(" - Framework:"), autoConfigDetails.framework.name);
140+
}
141+
if (autoConfigDetails.buildCommand) {
142+
logger.log(brandColor(" - Build Command:"), autoConfigDetails.buildCommand);
143+
}
144+
if (autoConfigDetails.outputDir) {
145+
logger.log(brandColor(" - Output Directory:"), autoConfigDetails.outputDir);
146+
}
147+
148+
logger.log("");
149+
}

packages/wrangler/src/autoconfig/run.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { writeFile } from "node:fs/promises";
22
import { dirname, resolve } from "node:path";
33
import { endSection, startSection } from "@cloudflare/cli";
4-
import { brandColor } from "@cloudflare/cli/colors";
54
import { FatalError } from "@cloudflare/workers-utils";
65
import { runCommand } from "../deployment-bundle/run-custom-build";
76
import { confirm } from "../dialogs";
@@ -11,25 +10,14 @@ import { getDevCompatibilityDate } from "../utils/compatibility-date";
1110
import { addWranglerToAssetsIgnore } from "./add-wrangler-assetsignore";
1211
import { addWranglerToGitIgnore } from "./c3-vendor/add-wrangler-gitignore";
1312
import { installWrangler } from "./c3-vendor/packages";
13+
import { displayAutoConfigDetails } from "./details";
1414
import type { AutoConfigDetails } from "./types";
1515
import type { RawConfig } from "@cloudflare/workers-utils";
1616

1717
export async function runAutoConfig(
1818
autoConfigDetails: AutoConfigDetails
1919
): Promise<void> {
20-
logger.debug(
21-
`Running autoconfig with:\n${JSON.stringify(autoConfigDetails, null, 2)}...`
22-
);
23-
logger.log("Project settings detected:");
24-
if (autoConfigDetails.framework) {
25-
logger.log(brandColor("Framework:"), autoConfigDetails.framework.name);
26-
}
27-
if (autoConfigDetails.buildCommand) {
28-
logger.log(brandColor("Build Command:"), autoConfigDetails.buildCommand);
29-
}
30-
if (autoConfigDetails.outputDir) {
31-
logger.log(brandColor("Output Directory:"), autoConfigDetails.outputDir);
32-
}
20+
displayAutoConfigDetails(autoConfigDetails);
3321

3422
const deploy = await confirm("Do you want to deploy using these settings?");
3523
if (!deploy) {
@@ -39,7 +27,11 @@ export async function runAutoConfig(
3927
throw new FatalError("Cannot deploy project without an output directory");
4028
}
4129

42-
startSection("Configuring your application for Cloudflare", "Step 2 of 3");
30+
logger.debug(
31+
`Running autoconfig with:\n${JSON.stringify(autoConfigDetails, null, 2)}...`
32+
);
33+
34+
startSection("Configuring your application for Cloudflare");
4335

4436
await installWrangler();
4537

@@ -67,11 +59,11 @@ export async function runAutoConfig(
6759
)
6860
);
6961

70-
await addWranglerToGitIgnore(autoConfigDetails.projectPath);
62+
addWranglerToGitIgnore(autoConfigDetails.projectPath);
7163

7264
// If we're uploading the project path as the output directory, make sure we don't accidentally upload any sensitive Wrangler files
7365
if (autoConfigDetails.outputDir === autoConfigDetails.projectPath) {
74-
await addWranglerToAssetsIgnore(autoConfigDetails.projectPath);
66+
addWranglerToAssetsIgnore(autoConfigDetails.projectPath);
7567
}
7668

7769
endSection(`Application configured`);

packages/wrangler/src/cli.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,8 @@ export {
7474
convertConfigBindingsToStartWorkerBindings as unstable_convertConfigBindingsToStartWorkerBindings,
7575
} from "./api";
7676

77+
export { getDetailsForAutoConfig as experimental_getDetailsForAutoConfig } from "./autoconfig/details";
78+
export { runAutoConfig as experimental_runAutoConfig } from "./autoconfig/run";
79+
export { Framework as experimental_AutoConfigFramework } from "./autoconfig/frameworks/index";
80+
7781
export { experimental_getWranglerCommands } from "./experimental-commands-api";

packages/wrangler/src/deploy/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from "@cloudflare/workers-utils";
99
import chalk from "chalk";
1010
import { getAssetsOptions, validateAssetsArgsAndConfig } from "../assets";
11-
import { getDetailsForAutoConfig } from "../autoconfig/get-details";
11+
import { getDetailsForAutoConfig } from "../autoconfig/details";
1212
import { runAutoConfig } from "../autoconfig/run";
1313
import { readConfig } from "../config";
1414
import { createCommand } from "../core/create-command";

0 commit comments

Comments
 (0)