Skip to content

Commit ce114e8

Browse files
committed
Detect static assets directory, provide instructions in Wrangler output
1 parent dceb550 commit ce114e8

File tree

8 files changed

+503
-5
lines changed

8 files changed

+503
-5
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Improve error messaging when no entry point is found by detecting potential static asset directories
6+
7+
When `wrangler deploy` fails due to missing entry points, Wrangler now automatically detects common static asset directories (like `dist`, `build`, `public`, etc.) and framework-specific output directories (like Astro, Vite, Next.js, Eleventy). The error message now includes helpful suggestions about which directories might contain static assets and provides the exact command to deploy them with `--assets`.
8+
9+
This feature helps new users better understand what they need to do when deploying static sites to Cloudflare without requiring them to understand Wrangler configuration files.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Changesets
2+
3+
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets)
4+
5+
We have a quick list of common questions to get you started engaging with this project in
6+
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
3+
"changelog": [
4+
"@changesets/changelog-github",
5+
{ "repo": "cloudflare/workers-sdk" }
6+
],
7+
"commit": false,
8+
"linked": [],
9+
"access": "public",
10+
"baseBranch": "main",
11+
"updateInternalDependencies": "patch",
12+
"bumpVersionsWithWorkspaceProtocolOnly": true,
13+
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
14+
"onlyUpdatePeerDependentsWhenOutOfRange": true
15+
},
16+
"ignore": [],
17+
"privatePackages": {
18+
"tag": true,
19+
"version": true
20+
}
21+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Changesets
2+
3+
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets)
4+
5+
We have a quick list of common questions to get you started engaging with this project in
6+
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

packages/wrangler/src/__tests__/get-entry.test.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,104 @@ describe("getEntry()", () => {
141141
moduleRoot: "/tmp/dir/other-worker/src",
142142
});
143143
});
144+
145+
describe("error messages with asset directory suggestions", () => {
146+
it("should suggest single detected asset directory", async () => {
147+
await seed({
148+
"dist/index.html": "<html></html>",
149+
"dist/style.css": "body { color: red; }",
150+
});
151+
152+
await expect(
153+
getEntry({}, defaultWranglerConfig, "deploy")
154+
).rejects.toThrow(
155+
/We noticed that there is a directory called `\.\/dist` in your project \(common build output directory\)\. If you are trying to deploy the contents of that directory to Cloudflare, please run:/
156+
);
157+
});
158+
159+
it("should suggest multiple detected asset directories", async () => {
160+
await seed({
161+
"dist/index.html": "<html></html>",
162+
"build/app.js": "console.log('hello');",
163+
"public/favicon.ico": "fake-ico-content",
164+
});
165+
166+
await expect(
167+
getEntry({}, defaultWranglerConfig, "deploy")
168+
).rejects.toThrow(
169+
/We noticed several directories that might contain static assets:/
170+
);
171+
172+
await expect(
173+
getEntry({}, defaultWranglerConfig, "deploy")
174+
).rejects.toThrow(
175+
/- `\.\/build` \(common build output directory\)/
176+
);
177+
178+
await expect(
179+
getEntry({}, defaultWranglerConfig, "deploy")
180+
).rejects.toThrow(
181+
/- `\.\/dist` \(common build output directory\)/
182+
);
183+
184+
await expect(
185+
getEntry({}, defaultWranglerConfig, "deploy")
186+
).rejects.toThrow(
187+
/- `\.\/public` \(common static assets directory\)/
188+
);
189+
});
190+
191+
it("should suggest framework-specific directories", async () => {
192+
await seed({
193+
"package.json": JSON.stringify({
194+
name: "my-astro-project",
195+
dependencies: {
196+
astro: "^4.0.0",
197+
},
198+
}),
199+
"astro.config.mjs": "export default {};",
200+
"dist/index.html": "<html></html>",
201+
});
202+
203+
await expect(
204+
getEntry({}, defaultWranglerConfig, "deploy")
205+
).rejects.toThrow(
206+
/We noticed that there is a directory called `\.\/dist` in your project \(detected astro\.config project\)/
207+
);
208+
});
209+
210+
it("should not suggest asset directories when none exist", async () => {
211+
await seed({
212+
"src/some-file.ts": "export default {};",
213+
});
214+
215+
await expect(
216+
getEntry({}, defaultWranglerConfig, "deploy")
217+
).rejects.toThrow(
218+
/Missing entry-point to Worker script or to assets directory/
219+
);
220+
221+
// Should not contain asset directory suggestions
222+
await expect(
223+
getEntry({}, defaultWranglerConfig, "deploy")
224+
).rejects.not.toThrow(/We noticed/);
225+
});
226+
227+
it("should not suggest empty directories", async () => {
228+
await seed({
229+
"dist/": "", // Creates empty directory
230+
});
231+
232+
await expect(
233+
getEntry({}, defaultWranglerConfig, "deploy")
234+
).rejects.toThrow(
235+
/Missing entry-point to Worker script or to assets directory/
236+
);
237+
238+
// Should not contain asset directory suggestions
239+
await expect(
240+
getEntry({}, defaultWranglerConfig, "deploy")
241+
).rejects.not.toThrow(/We noticed/);
242+
});
243+
});
144244
});
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import { detectStaticAssetDirectories } from "../deployment-bundle/static-assets-detector";
2+
import { mockConsoleMethods } from "./helpers/mock-console";
3+
import { runInTempDir } from "./helpers/run-in-tmp";
4+
import { seed } from "./helpers/seed";
5+
6+
describe("detectStaticAssetDirectories()", () => {
7+
runInTempDir();
8+
mockConsoleMethods();
9+
10+
it("should detect dist directory", async () => {
11+
await seed({
12+
"dist/index.html": "<html></html>",
13+
"dist/style.css": "body { color: red; }",
14+
});
15+
16+
const suggestions = detectStaticAssetDirectories();
17+
expect(suggestions).toEqual([
18+
{
19+
directory: "./dist",
20+
reason: "common build output directory",
21+
},
22+
]);
23+
});
24+
25+
it("should detect multiple asset directories", async () => {
26+
await seed({
27+
"dist/index.html": "<html></html>",
28+
"build/app.js": "console.log('hello');",
29+
"public/favicon.ico": "fake-ico-content",
30+
});
31+
32+
const suggestions = detectStaticAssetDirectories();
33+
expect(suggestions).toEqual([
34+
{
35+
directory: "./build",
36+
reason: "common build output directory",
37+
},
38+
{
39+
directory: "./dist",
40+
reason: "common build output directory",
41+
},
42+
{
43+
directory: "./public",
44+
reason: "common static assets directory",
45+
},
46+
]);
47+
});
48+
49+
it("should not detect empty directories", async () => {
50+
await seed({
51+
"dist/": "",
52+
"build/": "",
53+
});
54+
55+
const suggestions = detectStaticAssetDirectories();
56+
expect(suggestions).toEqual([]);
57+
});
58+
59+
it("should detect Astro project with dist directory", async () => {
60+
await seed({
61+
"package.json": JSON.stringify({
62+
name: "my-astro-project",
63+
dependencies: {
64+
astro: "^4.0.0",
65+
},
66+
}),
67+
"astro.config.mjs": "export default {};",
68+
"dist/index.html": "<html></html>",
69+
"dist/style.css": "body { color: red; }",
70+
});
71+
72+
const suggestions = detectStaticAssetDirectories();
73+
expect(suggestions).toContainEqual({
74+
directory: "./dist",
75+
reason: "detected astro.config project",
76+
});
77+
});
78+
79+
it("should detect Next.js project with out directory", async () => {
80+
await seed({
81+
"package.json": JSON.stringify({
82+
name: "my-next-project",
83+
dependencies: {
84+
next: "^14.0.0",
85+
},
86+
}),
87+
"next.config.js": "module.exports = {};",
88+
"out/index.html": "<html></html>",
89+
"out/favicon.ico": "fake-ico-content",
90+
});
91+
92+
const suggestions = detectStaticAssetDirectories();
93+
expect(suggestions).toContainEqual({
94+
directory: "./out",
95+
reason: "detected next.config project",
96+
});
97+
});
98+
99+
it("should detect Vite project from package.json", async () => {
100+
await seed({
101+
"package.json": JSON.stringify({
102+
name: "my-vite-project",
103+
devDependencies: {
104+
vite: "^5.0.0",
105+
},
106+
}),
107+
"dist/index.html": "<html></html>",
108+
"dist/assets/main.js": "console.log('hello');",
109+
});
110+
111+
const suggestions = detectStaticAssetDirectories();
112+
expect(suggestions).toContainEqual({
113+
directory: "./dist",
114+
reason: "detected package.json project",
115+
});
116+
});
117+
118+
it("should detect Eleventy project", async () => {
119+
await seed({
120+
"package.json": JSON.stringify({
121+
name: "my-eleventy-project",
122+
devDependencies: {
123+
"@11ty/eleventy": "^2.0.0",
124+
},
125+
}),
126+
".eleventy.js": "module.exports = {};",
127+
"_site/index.html": "<html></html>",
128+
"_site/style.css": "body { color: red; }",
129+
});
130+
131+
const suggestions = detectStaticAssetDirectories();
132+
expect(suggestions).toContainEqual({
133+
directory: "./_site",
134+
reason: "detected .eleventy project",
135+
});
136+
});
137+
138+
it("should not duplicate suggestions", async () => {
139+
await seed({
140+
"package.json": JSON.stringify({
141+
name: "my-astro-project",
142+
dependencies: {
143+
astro: "^4.0.0",
144+
},
145+
}),
146+
"astro.config.mjs": "export default {};",
147+
"dist/index.html": "<html></html>",
148+
});
149+
150+
const suggestions = detectStaticAssetDirectories();
151+
// Should only have one suggestion for dist, not duplicates
152+
const distSuggestions = suggestions.filter(s => s.directory === "./dist");
153+
expect(distSuggestions).toHaveLength(1);
154+
});
155+
156+
it("should handle non-existent directories gracefully", async () => {
157+
await seed({
158+
"package.json": JSON.stringify({
159+
name: "my-project",
160+
dependencies: {
161+
astro: "^4.0.0",
162+
},
163+
}),
164+
// No dist directory created
165+
});
166+
167+
const suggestions = detectStaticAssetDirectories();
168+
expect(suggestions).toEqual([]);
169+
});
170+
171+
it("should handle invalid package.json gracefully", async () => {
172+
await seed({
173+
"package.json": "invalid json content",
174+
"dist/index.html": "<html></html>",
175+
});
176+
177+
const suggestions = detectStaticAssetDirectories();
178+
expect(suggestions).toEqual([
179+
{
180+
directory: "./dist",
181+
reason: "common build output directory",
182+
},
183+
]);
184+
});
185+
});

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

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
resolveEntryWithScript,
1313
} from "./resolve-entry";
1414
import { runCustomBuild } from "./run-custom-build";
15+
import { detectStaticAssetDirectories } from "./static-assets-detector";
1516
import type { Config, RawConfig } from "../config";
1617
import type { DurableObjectBindings } from "../config/environment";
1718
import type { CfScriptFormat } from "./worker";
@@ -101,8 +102,11 @@ export async function getEntry(
101102
`;
102103

103104
const fullCommand = `${getNpxEquivalent()} wrangler ${command}`;
104-
throw new UserError(
105-
dedent`
105+
106+
// Detect potential static asset directories
107+
const assetSuggestions = detectStaticAssetDirectories(config.configPath ? path.dirname(config.configPath) : process.cwd());
108+
109+
let errorMessage = dedent`
106110
Missing entry-point to Worker script or to assets directory
107111
108112
If there is code to deploy, you can either:
@@ -111,9 +115,26 @@ export async function getEntry(
111115
112116
If are uploading a directory of assets, you can either:
113117
- Specify the path to the directory of assets via the command line: (ex: \`${fullCommand} --assets=./dist\`)
114-
- Or ${updateConfigMessage({ assets: { directory: "./dist" } })}`,
115-
{ telemetryMessage: "missing worker entrypoint or assets directory" }
116-
);
118+
- Or ${updateConfigMessage({ assets: { directory: "./dist" } })}`;
119+
120+
// Add asset directory suggestions if any were found
121+
if (assetSuggestions.length > 0) {
122+
errorMessage += "\n\n";
123+
if (assetSuggestions.length === 1) {
124+
const suggestion = assetSuggestions[0];
125+
errorMessage += dedent`
126+
We noticed that there is a directory called \`${suggestion.directory}\` in your project (${suggestion.reason}). If you are trying to deploy the contents of that directory to Cloudflare, please run:
127+
\`${fullCommand} --assets ${suggestion.directory}\``;
128+
} else {
129+
errorMessage += "We noticed several directories that might contain static assets:\n";
130+
for (const suggestion of assetSuggestions) {
131+
errorMessage += `- \`${suggestion.directory}\` (${suggestion.reason})\n`;
132+
}
133+
errorMessage += `\nIf you want to deploy one of these directories, run: \`${fullCommand} --assets <directory>\``;
134+
}
135+
}
136+
137+
throw new UserError(errorMessage, { telemetryMessage: "missing worker entrypoint or assets directory" });
117138
}
118139
await runCustomBuild(
119140
paths.absolutePath,

0 commit comments

Comments
 (0)