Skip to content

feat: interactively handle wonky asset deploy commands #10016

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/deep-onions-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"wrangler": minor
---

Interactively handle `wrangler deploy`s that are probably assets-only, where there is no config file and flags are incorrect or missing.

For example:

`npx wrangler deploy ./public` will now ask if you meant to deploy a folder of assets only, ask for a name, set the compat date and then ask whether to write your choices out to `wrangler.json` for subsequent deployments.

`npx wrangler deploy --assets=./public` will now ask for a name, set the compat date and then ask whether to write your choices out to `wrangler.json` for subsequent deployments.

In non-interactive contexts, Wrangler will error as it currently does.
266 changes: 264 additions & 2 deletions packages/wrangler/src/__tests__/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as esbuild from "esbuild";
import { http, HttpResponse } from "msw";
import dedent from "ts-dedent";
import { vi } from "vitest";
import { findWranglerConfig } from "../config/config-helpers";
import {
printBundleSize,
printOffendingDependencies,
Expand All @@ -20,7 +21,7 @@ import { writeAuthConfigFile } from "../user";
import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
import { mockAuthDomain } from "./helpers/mock-auth-domain";
import { mockConsoleMethods } from "./helpers/mock-console";
import { clearDialogs, mockConfirm } from "./helpers/mock-dialogs";
import { clearDialogs, mockConfirm, mockPrompt } from "./helpers/mock-dialogs";
import { mockGetZoneFromHostRequest } from "./helpers/mock-get-zone-from-host";
import { useMockIsTTY } from "./helpers/mock-istty";
import { mockCollectKnownRoutesRequest } from "./helpers/mock-known-routes";
Expand Down Expand Up @@ -2720,6 +2721,266 @@ addEventListener('fetch', event => {});`
});
});
});

describe("should interactively handle misconfigured asset-only deployments", () => {
beforeEach(() => {
setIsTTY(true);

// Mock the date to ensure consistent compatibility_date
vi.setSystemTime(new Date("2024-01-01T00:00:00Z"));

// so that we can test that the name prompt defaults to the directory name
fs.mkdirSync("my-site");
process.chdir("my-site");
const assets = [
{ filePath: "index.html", content: "<html>test</html>" },
];
writeAssets(assets);
expect(findWranglerConfig().configPath).toBe(undefined);
mockSubDomainRequest();
mockUploadWorkerRequest({
expectedAssets: {
jwt: "<<aus-completion-token>>",
config: {},
},
expectedType: "none",
});
});
afterEach(() => {
setIsTTY(false);
vi.useRealTimers();
});

it("should handle `wrangler deploy <directory>`", async () => {
mockConfirm({
text: "It looks like you are trying to deploy a directory of static assets only. Is this correct?",
result: true,
});
mockPrompt({
text: "What do you want to name your project?",
options: { defaultValue: "my-site" },
result: "test-name",
});
mockConfirm({
text: "Do you want Wrangler to write a wrangler.json config file to store this configuration?\nThis will allow you to simply run `wrangler deploy` on future deployments.",
result: true,
});

const bodies: AssetManifest[] = [];
await mockAUSRequest(bodies);

await runWrangler("deploy ./assets");
expect(bodies.length).toBe(1);
expect(bodies[0]).toEqual({
manifest: {
"/index.html": {
hash: "8308ce789f3d08668ce87176838d59d0",
size: 17,
},
},
});
expect(fs.readFileSync("wrangler.jsonc", "utf-8"))
.toMatchInlineSnapshot(`
"{
\\"name\\": \\"test-name\\",
\\"compatibility_date\\": \\"2024-01-01\\",
\\"assets\\": {
\\"directory\\": \\"./assets\\"
}
}"
`);
expect(std.out).toMatchInlineSnapshot(`
"


No compatibility date found Defaulting to today: 2024-01-01

Wrote
{
\\"name\\": \\"test-name\\",
\\"compatibility_date\\": \\"2024-01-01\\",
\\"assets\\": {
\\"directory\\": \\"./assets\\"
}
}
to <cwd>/wrangler.jsonc.
Please run \`wrangler deploy\` instead of \`wrangler deploy ./assets\` next time. Wrangler will automatically use the configuration saved to wrangler.jsonc.

Proceeding with deployment...

Total Upload: xx KiB / gzip: xx KiB
Worker Startup Time: 100 ms
Uploaded test-name (TIMINGS)
Deployed test-name triggers (TIMINGS)
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
});

it("should handle `wrangler deploy --assets` without name or compat date", async () => {
// if the user has used --assets flag and args.script is not set, we just need to prompt for the name and add compat date
mockPrompt({
text: "What do you want to name your project?",
options: { defaultValue: "my-site" },
result: "test-name",
});
mockConfirm({
text: "Do you want Wrangler to write a wrangler.json config file to store this configuration?\nThis will allow you to simply run `wrangler deploy` on future deployments.",
result: true,
});

const bodies: AssetManifest[] = [];
await mockAUSRequest(bodies);

await runWrangler("deploy --assets ./assets");
expect(bodies.length).toBe(1);
expect(bodies[0]).toEqual({
manifest: {
"/index.html": {
hash: "8308ce789f3d08668ce87176838d59d0",
size: 17,
},
},
});
expect(fs.readFileSync("wrangler.jsonc", "utf-8"))
.toMatchInlineSnapshot(`
"{
\\"name\\": \\"test-name\\",
\\"compatibility_date\\": \\"2024-01-01\\",
\\"assets\\": {
\\"directory\\": \\"./assets\\"
}
}"
`);
expect(std.out).toMatchInlineSnapshot(`
"

No compatibility date found Defaulting to today: 2024-01-01

Wrote
{
\\"name\\": \\"test-name\\",
\\"compatibility_date\\": \\"2024-01-01\\",
\\"assets\\": {
\\"directory\\": \\"./assets\\"
}
}
to <cwd>/wrangler.jsonc.
Please run \`wrangler deploy\` instead of \`wrangler deploy ./assets\` next time. Wrangler will automatically use the configuration saved to wrangler.jsonc.

Proceeding with deployment...

Total Upload: xx KiB / gzip: xx KiB
Worker Startup Time: 100 ms
Uploaded test-name (TIMINGS)
Deployed test-name triggers (TIMINGS)
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
});

it("should suggest 'my-project' if the default name from the cwd is invalid", async () => {
process.chdir("../");
fs.renameSync("my-site", "[blah]");
process.chdir("[blah]");
// if the user has used --assets flag and args.script is not set, we just need to prompt for the name and add compat date
mockPrompt({
text: "What do you want to name your project?",
// not [blah] because it is an invalid worker name
options: { defaultValue: "my-project" },
result: "test-name",
});
mockConfirm({
text: "Do you want Wrangler to write a wrangler.json config file to store this configuration?\nThis will allow you to simply run `wrangler deploy` on future deployments.",
result: true,
});

const bodies: AssetManifest[] = [];
await mockAUSRequest(bodies);

await runWrangler("deploy --assets ./assets");
expect(bodies.length).toBe(1);
expect(bodies[0]).toEqual({
manifest: {
"/index.html": {
hash: "8308ce789f3d08668ce87176838d59d0",
size: 17,
},
},
});
expect(fs.readFileSync("wrangler.jsonc", "utf-8"))
.toMatchInlineSnapshot(`
"{
\\"name\\": \\"test-name\\",
\\"compatibility_date\\": \\"2024-01-01\\",
\\"assets\\": {
\\"directory\\": \\"./assets\\"
}
}"
`);
});

it("should bail if the user denies that they are trying to deploy a directory", async () => {
mockConfirm({
text: "It looks like you are trying to deploy a directory of static assets only. Is this correct?",
result: false,
});

await expect(runWrangler("deploy ./assets")).rejects
.toThrowErrorMatchingInlineSnapshot(`
[Error: The entry-point file at "assets" was not found.
The provided entry-point path, "assets", points to a directory, rather than a file.

If you want to deploy a directory of static assets, you can do so by using the \`--assets\` flag. For example:

wrangler deploy --assets=./assets
]
`);
});

it("does not write out a wrangler config file if the user says no", async () => {
mockPrompt({
text: "What do you want to name your project?",
options: { defaultValue: "my-site" },
result: "test-name",
});
mockConfirm({
text: "Do you want Wrangler to write a wrangler.json config file to store this configuration?\nThis will allow you to simply run `wrangler deploy` on future deployments.",
result: false,
});

const bodies: AssetManifest[] = [];
await mockAUSRequest(bodies);

await runWrangler("deploy --assets ./assets");
expect(bodies.length).toBe(1);
expect(bodies[0]).toEqual({
manifest: {
"/index.html": {
hash: "8308ce789f3d08668ce87176838d59d0",
size: 17,
},
},
});
expect(fs.existsSync("wrangler.jsonc")).toBe(false);
expect(std.out).toMatchInlineSnapshot(`
"

No compatibility date found Defaulting to today: 2024-01-01

You should run wrangler deploy --name test-name --compatibility-date 2024-01-01 --assets ./assets next time to deploy this Worker without going through this flow again.

Proceeding with deployment...

Total Upload: xx KiB / gzip: xx KiB
Worker Startup Time: 100 ms
Uploaded test-name (TIMINGS)
Deployed test-name triggers (TIMINGS)
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
});
});
});

describe("(legacy) asset upload", () => {
Expand Down Expand Up @@ -4346,7 +4607,8 @@ addEventListener('fetch', event => {});`
);
});

it("should error if directory specified by flag --assets does not exist", async () => {
it("should error if directory specified by flag --assets does not exist in non-interactive mode", async () => {
setIsTTY(false);
await expect(runWrangler("deploy --assets abc")).rejects.toThrow(
new RegExp(
'^The directory specified by the "--assets" command line argument does not exist:[Ss]*'
Expand Down
Loading
Loading