Skip to content

Commit 22f25fd

Browse files
penalosaclaude
andauthored
Initial autoconfig scaffolding (#11149)
* Initial autoconfig * Convert autoconfig types comments to JSDoc style 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
1 parent c2a63ab commit 22f25fd

File tree

9 files changed

+168
-3
lines changed

9 files changed

+168
-3
lines changed

.changeset/soft-flies-drive.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+
Add no-op autoconfig logic behind an experimental flag
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { FatalError } from "@cloudflare/workers-utils";
2+
import { vi } from "vitest";
3+
import * as details from "../autoconfig/get-details";
4+
import * as run from "../autoconfig/run";
5+
import * as format from "../deployment-bundle/guess-worker-format";
6+
import { clearOutputFilePath } from "../output";
7+
import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
8+
import { clearDialogs } from "./helpers/mock-dialogs";
9+
import { useMockIsTTY } from "./helpers/mock-istty";
10+
import { runInTempDir } from "./helpers/run-in-tmp";
11+
import { runWrangler } from "./helpers/run-wrangler";
12+
import { writeWorkerSource } from "./helpers/write-worker-source";
13+
import { writeWranglerConfig } from "./helpers/write-wrangler-config";
14+
15+
vi.mock("../deploy/deploy", async (importOriginal) => ({
16+
...(await importOriginal()),
17+
default: () => {
18+
// In unit tests of autoconfig we only care about the configuration aspect, so bail before any actual deployment happens
19+
throw new FatalError("Bailing early in tests");
20+
},
21+
}));
22+
23+
async function runDeploy(withArgs: string = "") {
24+
// Expect "Bailing early in tests" to be thrown
25+
await expect(runWrangler(`deploy ${withArgs}`)).rejects.toThrowError();
26+
}
27+
28+
// We don't care about module/service worker detection in the autoconfig tests,
29+
// and mocking it out speeds up the tests by removing an esbuild invocation
30+
vi.spyOn(format, "guessWorkerFormat").mockImplementation(() =>
31+
Promise.resolve({
32+
format: "modules",
33+
exports: [],
34+
})
35+
);
36+
37+
describe("autoconfig (deploy)", () => {
38+
mockAccountId();
39+
mockApiToken();
40+
runInTempDir();
41+
const { setIsTTY } = useMockIsTTY();
42+
43+
beforeEach(() => {
44+
setIsTTY(true);
45+
});
46+
47+
afterEach(() => {
48+
vi.unstubAllGlobals();
49+
clearDialogs();
50+
clearOutputFilePath();
51+
});
52+
53+
it("should not check for autoconfig without flag", async () => {
54+
writeWorkerSource();
55+
writeWranglerConfig({ main: "index.js" });
56+
const getDetailsSpy = vi.spyOn(details, "getDetailsForAutoConfig");
57+
await runDeploy();
58+
59+
expect(getDetailsSpy).not.toHaveBeenCalled();
60+
});
61+
62+
it("should check for autoconfig with flag", async () => {
63+
const getDetailsSpy = vi.spyOn(details, "getDetailsForAutoConfig");
64+
65+
await runDeploy("--x-autoconfig");
66+
67+
expect(getDetailsSpy).toHaveBeenCalled();
68+
});
69+
70+
it("should run autoconfig if project is not configured", async () => {
71+
const getDetailsSpy = vi
72+
.spyOn(details, "getDetailsForAutoConfig")
73+
.mockImplementationOnce(() => Promise.resolve({ configured: false }));
74+
const runSpy = vi.spyOn(run, "runAutoConfig");
75+
76+
await runDeploy("--x-autoconfig");
77+
78+
expect(getDetailsSpy).toHaveBeenCalled();
79+
expect(runSpy).toHaveBeenCalled();
80+
});
81+
82+
it("should not run autoconfig if project is already configured", async () => {
83+
const getDetailsSpy = vi
84+
.spyOn(details, "getDetailsForAutoConfig")
85+
.mockImplementationOnce(() => Promise.resolve({ configured: true }));
86+
const runSpy = vi.spyOn(run, "runAutoConfig");
87+
88+
await runDeploy("--x-autoconfig");
89+
90+
expect(getDetailsSpy).toHaveBeenCalled();
91+
expect(runSpy).not.toHaveBeenCalled();
92+
});
93+
});

packages/wrangler/src/__tests__/guess-worker-format.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { writeFile } from "fs/promises";
22
import path from "path";
3-
import guessWorkerFormat from "../deployment-bundle/guess-worker-format";
3+
import { guessWorkerFormat } from "../deployment-bundle/guess-worker-format";
44
import { mockConsoleMethods } from "./helpers/mock-console";
55
import { runInTempDir } from "./helpers/run-in-tmp";
66

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { logger } from "../logger";
2+
import type { AutoConfigDetails } from "./types";
3+
4+
export async function getDetailsForAutoConfig(options?: {
5+
projectPath?: string; // the path to the project, defaults to cwd
6+
}): Promise<AutoConfigDetails> {
7+
logger.debug(
8+
`Running autoconfig detection in ${options?.projectPath ?? process.cwd()}...`
9+
);
10+
return { configured: true };
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { logger } from "../logger";
2+
import type { AutoConfigDetails } from "./types";
3+
4+
export async function runAutoConfig(
5+
autoConfigDetails: AutoConfigDetails
6+
): Promise<void> {
7+
logger.debug(
8+
`Running autoconfig with:\n${JSON.stringify(autoConfigDetails, null, 2)}...`
9+
);
10+
return;
11+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export type AutoConfigDetails = {
2+
/** The path to the project (defaults to cwd) */
3+
projectPath?: string;
4+
/** The content of the project's package.json file (if any) */
5+
packageJson?: string;
6+
/** Whether the project is already configured (no autoconfig required) */
7+
configured: boolean;
8+
/** Details about the detected framework (if any) */
9+
framework?: {
10+
/** The detected framework */
11+
name: "astro";
12+
/** The detected version of the framework */
13+
version: string;
14+
/** Whether the framework is used for static generation or fullstack deployment */
15+
mode: "static" | "fullstack";
16+
};
17+
/** The build command used to build the project (if any) */
18+
buildCommand?: string;
19+
/** The output directory (if no framework is used, points to the raw asset files) */
20+
outputDir?: string;
21+
};

packages/wrangler/src/deploy/index.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import {
88
} from "@cloudflare/workers-utils";
99
import chalk from "chalk";
1010
import { getAssetsOptions, validateAssetsArgsAndConfig } from "../assets";
11+
import { getDetailsForAutoConfig } from "../autoconfig/get-details";
12+
import { runAutoConfig } from "../autoconfig/run";
13+
import { readConfig } from "../config";
1114
import { createCommand } from "../core/create-command";
1215
import { getEntry } from "../deployment-bundle/entry";
1316
import { confirm, prompt } from "../dialogs";
@@ -238,6 +241,13 @@ export const deployCommand = createCommand({
238241
type: "boolean",
239242
default: false,
240243
},
244+
"experimental-autoconfig": {
245+
alias: ["x-autoconfig"],
246+
describe:
247+
"Experimental: Enables framework detection and automatic configuration when deploying",
248+
type: "boolean",
249+
default: false,
250+
},
241251
},
242252
behaviour: {
243253
useConfigRedirectIfAvailable: true,
@@ -265,6 +275,20 @@ export const deployCommand = createCommand({
265275
{ telemetryMessage: true }
266276
);
267277
}
278+
if (args.experimentalAutoconfig) {
279+
const details = await getDetailsForAutoConfig();
280+
281+
// Only run auto config if the project is not already configured
282+
if (!details.configured) {
283+
await runAutoConfig(details);
284+
285+
// If autoconfig worked, there should now be a new config file, and so we need to read config again
286+
config = readConfig(args, {
287+
hideWarnings: false,
288+
useRedirectIfAvailable: true,
289+
});
290+
}
291+
}
268292
// We use the `userConfigPath` to compute the root of a project,
269293
// rather than a redirected (potentially generated) `configPath`.
270294
const projectRoot =

packages/wrangler/src/deployment-bundle/entry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from "@cloudflare/workers-utils";
88
import dedent from "ts-dedent";
99
import { sniffUserAgent } from "../package-manager";
10-
import guessWorkerFormat from "./guess-worker-format";
10+
import { guessWorkerFormat } from "./guess-worker-format";
1111
import {
1212
resolveEntryWithAssets,
1313
resolveEntryWithEntryPoint,

packages/wrangler/src/deployment-bundle/guess-worker-format.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type { CfScriptFormat } from "@cloudflare/workers-utils";
1313
* Else, it's a "service-worker" worker. This seems hacky, but works remarkably
1414
* well in practice.
1515
*/
16-
export default async function guessWorkerFormat(
16+
export async function guessWorkerFormat(
1717
entryFile: string,
1818
entryWorkingDirectory: string,
1919
tsconfig?: string | undefined

0 commit comments

Comments
 (0)