diff --git a/.changeset/c3-frameworks-update-11865.md b/.changeset/c3-frameworks-update-11865.md
new file mode 100644
index 000000000000..145badcabf94
--- /dev/null
+++ b/.changeset/c3-frameworks-update-11865.md
@@ -0,0 +1,11 @@
+---
+"create-cloudflare": patch
+---
+
+chore: update dependencies of "create-cloudflare"
+
+The following dependency versions have been updated:
+
+| Dependency | From | To |
+| ------------------- | ------ | ------ |
+| create-react-router | 7.11.0 | 7.12.0 |
diff --git a/.changeset/cli-macos-user-error.md b/.changeset/cli-macos-user-error.md
new file mode 100644
index 000000000000..e712bf90edde
--- /dev/null
+++ b/.changeset/cli-macos-user-error.md
@@ -0,0 +1,7 @@
+---
+"@cloudflare/cli": patch
+---
+
+Mark macOS version compatibility errors as user errors
+
+When checking macOS version compatibility, the CLI now throws `UserError` instead of generic `Error`. This ensures that version incompatibility issues are properly classified as user-facing errors that shouldn't be reported to Sentry.
diff --git a/.changeset/dependabot-update-11949.md b/.changeset/dependabot-update-11949.md
new file mode 100644
index 000000000000..a37c0d8c7a36
--- /dev/null
+++ b/.changeset/dependabot-update-11949.md
@@ -0,0 +1,12 @@
+---
+"miniflare": patch
+"wrangler": patch
+---
+
+chore: update dependencies of "miniflare", "wrangler"
+
+The following dependency versions have been updated:
+
+| Dependency | From | To |
+| ---------- | ------------ | ------------ |
+| workerd | 1.20260115.0 | 1.20260116.0 |
diff --git a/.changeset/file-not-found-errors.md b/.changeset/file-not-found-errors.md
new file mode 100644
index 000000000000..7f196893316b
--- /dev/null
+++ b/.changeset/file-not-found-errors.md
@@ -0,0 +1,7 @@
+---
+"wrangler": patch
+---
+
+Show helpful messages for file not found errors (`ENOENT`)
+
+When users encounter file not found errors, Wrangler now displays a helpful message with the missing file path and common causes, instead of reporting to Sentry.
diff --git a/.changeset/fix-version-tracking-command-events.md b/.changeset/fix-version-tracking-command-events.md
new file mode 100644
index 000000000000..3c0a1f7b55ca
--- /dev/null
+++ b/.changeset/fix-version-tracking-command-events.md
@@ -0,0 +1,7 @@
+---
+"wrangler": patch
+---
+
+fix: include version components in command event metrics
+
+Adds `wranglerMajorVersion`, `wranglerMinorVersion`, and `wranglerPatchVersion` to command events (`wrangler command started`, `wrangler command completed`, `wrangler command errored`). These properties were previously only included in adhoc events.
diff --git a/.changeset/friendly-kv-namespace-error.md b/.changeset/friendly-kv-namespace-error.md
new file mode 100644
index 000000000000..8cd3fbb89a15
--- /dev/null
+++ b/.changeset/friendly-kv-namespace-error.md
@@ -0,0 +1,7 @@
+---
+"wrangler": patch
+---
+
+Improve error message when creating duplicate KV namespace
+
+When attempting to create a KV namespace with a title that already exists, Wrangler now provides a clear, user-friendly error message instead of the generic API error. The new message explains that the namespace already exists and suggests running `wrangler kv namespace list` to see existing namespaces with their IDs, or choosing a different namespace name.
diff --git a/.changeset/green-bears-wave.md b/.changeset/green-bears-wave.md
new file mode 100644
index 000000000000..a326246190c3
--- /dev/null
+++ b/.changeset/green-bears-wave.md
@@ -0,0 +1,7 @@
+---
+"wrangler": patch
+---
+
+Make `name` the positional argument for `wrangler delete` instead of `script`
+
+The `script` argument was meaningless for the delete command since it deletes by worker name, not by entry point path. The `name` argument is now accepted as a positional argument, allowing users to run `wrangler delete my-worker` instead of `wrangler delete --name my-worker`. The `script` argument is now hidden but still accepted for backwards compatibility.
diff --git a/.changeset/miniflare-email-messagebuilder.md b/.changeset/miniflare-email-messagebuilder.md
new file mode 100644
index 000000000000..b3e8650bf338
--- /dev/null
+++ b/.changeset/miniflare-email-messagebuilder.md
@@ -0,0 +1,29 @@
+---
+"miniflare": minor
+---
+
+Add support for Email Sending API's MessageBuilder interface in local mode
+
+Miniflare now supports the simplified MessageBuilder interface for sending emails, alongside the existing `EmailMessage` support.
+
+Example usage:
+
+```javascript
+await env.EMAIL.send({
+ from: { name: "Alice", email: "alice@example.com" },
+ to: ["bob@example.com"],
+ subject: "Hello",
+ text: "Plain text version",
+ html: "
HTML version
",
+ attachments: [
+ {
+ disposition: "attachment",
+ filename: "report.pdf",
+ type: "application/pdf",
+ content: pdfData,
+ },
+ ],
+});
+```
+
+In local mode, email content (text, HTML, attachments) is stored to temporary files that you can open in your editor or browser for inspection. File paths are logged to the console when emails are sent.
diff --git a/.changeset/vite-config-error-handling.md b/.changeset/vite-config-error-handling.md
new file mode 100644
index 000000000000..96c9355133c9
--- /dev/null
+++ b/.changeset/vite-config-error-handling.md
@@ -0,0 +1,7 @@
+---
+"wrangler": patch
+---
+
+Improve error handling for Vite config transformations
+
+Replace assertions with proper error handling when transforming Vite configs. When Wrangler encounters a Vite config that uses a function or lacks a plugins array, it now provides clear, actionable error messages instead of crashing with assertion failures. The check function gracefully skips incompatible configs with debug logging.
diff --git a/fixtures/email-worker/README.md b/fixtures/email-worker/README.md
new file mode 100644
index 000000000000..23cca67d4d96
--- /dev/null
+++ b/fixtures/email-worker/README.md
@@ -0,0 +1,119 @@
+# Email Worker Fixture
+
+This fixture demonstrates both the **EmailMessage API** (raw MIME) and the **MessageBuilder API** for sending emails in Cloudflare Workers.
+
+## Running Locally
+
+Start the development server:
+
+```bash
+pnpm start
+# or
+wrangler dev
+```
+
+## Available Routes
+
+### EmailMessage API (Raw MIME)
+
+**`GET /send`** - Original API using manual MIME construction
+
+- Uses the `mimetext` library to build MIME messages
+- Sends via `LIST_SEND` binding
+
+### MessageBuilder API
+
+**`GET /send-simple`** - Simple text-only email
+
+- Basic MessageBuilder example with plain text
+- Demonstrates named sender: `{ name: "Alice", email: "..." }`
+
+**`GET /send-html`** - Email with text and HTML versions
+
+- Shows how to include both `text` and `html` content
+- HTML includes inline CSS styling
+
+**`GET /send-attachment`** - Email with single text attachment
+
+- Demonstrates attaching a text file
+- Uses `TextEncoder` to create content
+
+**`GET /send-multi-attachment`** - Email with multiple attachments
+
+- Includes three different attachment types:
+ - Text file (`.txt`)
+ - JSON file (`.json`)
+ - Binary file (simulated `.pdf`)
+- Shows both text and HTML content
+
+**`GET /send-complex`** - Complex email with multiple recipients
+
+- Multiple TO recipients (with and without names)
+- CC recipient with name
+- BCC recipient (array)
+- Both text and HTML content
+
+### Testing Bindings
+
+**`GET /test-bindings`** - Test all three email binding types
+
+- `UNBOUND_SEND` - No restrictions on recipients
+- `SPECIFIC_SEND` - Only allows `something@example.com`
+- `LIST_SEND` - Allows `something@example.com` and `else@example.com`
+
+## What to Expect
+
+When you send emails using these routes:
+
+1. **Console Output**: Miniflare logs details about the email being sent
+2. **File Paths**: For MessageBuilder emails, you'll see temp file paths:
+ - Text content → `.txt` files
+ - HTML content → `.html` files
+ - Attachments → Files with their original extensions
+
+### Example Console Output
+
+```
+send_email binding called with MessageBuilder:
+From: "Alice"
+To: else@example.com
+Subject: Simple MessageBuilder Test
+
+Text: /var/folders/.../email-text/abc-123.txt
+```
+
+You can open these files in your editor or browser to inspect the email content.
+
+## Email Bindings Configuration
+
+This fixture has three email bindings configured in `wrangler.jsonc`:
+
+```jsonc
+{
+ "send_email": [
+ {
+ "name": "UNBOUND_SEND",
+ // No restrictions
+ },
+ {
+ "name": "SPECIFIC_SEND",
+ "destination_address": "something@example.com",
+ // Only allows sending to this specific address
+ },
+ {
+ "name": "LIST_SEND",
+ "allowed_destination_addresses": [
+ "something@example.com",
+ "else@example.com",
+ ],
+ // Only allows sending to addresses in this list
+ },
+ ],
+}
+```
+
+## Notes
+
+- Type assertions (`as any`) are used because `@cloudflare/workers-types` doesn't yet include MessageBuilder types
+- At runtime in Miniflare, the MessageBuilder API works correctly
+- The `allowed_sender_addresses` configuration is not included in this fixture, so any sender address is accepted
diff --git a/fixtures/email-worker/src/index.ts b/fixtures/email-worker/src/index.ts
index 1af2d88fa651..e9771b3465a0 100644
--- a/fixtures/email-worker/src/index.ts
+++ b/fixtures/email-worker/src/index.ts
@@ -7,6 +7,7 @@ export default class extends WorkerEntrypoint {
const url = new URL(request.url);
if (url.pathname === "/error") throw new Error("Hello Error");
+ // Original EmailMessage API (raw MIME)
if (url.pathname === "/send") {
const msg = createMimeMessage();
msg.setSender({ name: "GPT-4", addr: "sender@penalosa.cloud" });
@@ -22,9 +23,215 @@ export default class extends WorkerEntrypoint {
msg.asRaw()
);
await this.env.LIST_SEND.send(m);
+ return new Response(
+ "✅ EmailMessage sent! Check console for temp file path.\n"
+ );
+ }
+
+ // MessageBuilder API: Simple text-only email
+ if (url.pathname === "/send-simple") {
+ await this.env.LIST_SEND.send({
+ from: { name: "Alice", email: "sender@penalosa.cloud" },
+ to: "else@example.com",
+ subject: "Simple MessageBuilder Test",
+ text: "This is a plain text email using the MessageBuilder API!",
+ } as any);
+ return new Response(
+ "✅ Simple text email sent! Check console for temp file path.\n"
+ );
+ }
+
+ // MessageBuilder API: Email with both text and HTML
+ if (url.pathname === "/send-html") {
+ await this.env.LIST_SEND.send({
+ from: { name: "Bob", email: "sender@penalosa.cloud" },
+ to: "else@example.com",
+ subject: "HTML Email Test",
+ text: "This is the plain text version.",
+ html: `
+
+
+
+
+
+
+ Hello from MessageBuilder!
+ This is the HTML version.
+
+ - Feature 1
+ - Feature 2
+ - Feature 3
+
+
+
+ `.trim(),
+ } as any);
+ return new Response(
+ "✅ HTML email sent! Check console for text and HTML file paths.\n"
+ );
}
- return new Response("Hello World!");
+ // MessageBuilder API: Email with text attachment
+ if (url.pathname === "/send-attachment") {
+ const textContent = new TextEncoder().encode(
+ "This is a sample text file attachment.\n\nLine 2\nLine 3\n"
+ );
+
+ await this.env.LIST_SEND.send({
+ from: "sender@penalosa.cloud",
+ to: "else@example.com",
+ subject: "Email with Text Attachment",
+ text: "Please see the attached text file.",
+ attachments: [
+ {
+ disposition: "attachment",
+ filename: "sample.txt",
+ type: "text/plain",
+ content: textContent,
+ },
+ ],
+ } as any);
+ return new Response(
+ "✅ Email with text attachment sent! Check console for file paths.\n"
+ );
+ }
+
+ // MessageBuilder API: Email with multiple attachments (text, JSON, simulated binary)
+ if (url.pathname === "/send-multi-attachment") {
+ const textAttachment = new TextEncoder().encode("Sample text file.");
+ const jsonAttachment = new TextEncoder().encode(
+ JSON.stringify({ message: "Hello from JSON!", timestamp: Date.now() })
+ );
+ // Simulate a small binary file (e.g., a tiny "PDF" with some binary data)
+ const binaryAttachment = new Uint8Array([
+ 0x25, 0x50, 0x44, 0x46, 0x2d, 0x31, 0x2e, 0x34, 0x0a, 0x25, 0xe2, 0xe3,
+ 0xcf, 0xd3,
+ ]); // "%PDF-1.4" header
+
+ await this.env.LIST_SEND.send({
+ from: { name: "Charlie", email: "sender@penalosa.cloud" },
+ to: "else@example.com",
+ subject: "Email with Multiple Attachments",
+ text: "This email has three different types of attachments.",
+ html: "Multiple Attachments
Check out the attached files!
",
+ attachments: [
+ {
+ disposition: "attachment",
+ filename: "document.txt",
+ type: "text/plain",
+ content: textAttachment,
+ },
+ {
+ disposition: "attachment",
+ filename: "data.json",
+ type: "application/json",
+ content: jsonAttachment,
+ },
+ {
+ disposition: "attachment",
+ filename: "sample.pdf",
+ type: "application/pdf",
+ content: binaryAttachment,
+ },
+ ],
+ } as any);
+ return new Response(
+ "✅ Email with multiple attachments sent! Check console for all file paths.\n"
+ );
+ }
+
+ // MessageBuilder API: Complex email with multiple recipients (to/cc/bcc)
+ if (url.pathname === "/send-complex") {
+ await this.env.LIST_SEND.send({
+ from: { name: "David", email: "sender@penalosa.cloud" },
+ to: [
+ { name: "Recipient One", email: "else@example.com" },
+ "something@example.com",
+ ],
+ cc: { name: "CC Person", email: "else@example.com" },
+ bcc: ["something@example.com"],
+ subject: "Complex Email Test",
+ text: "Plain text for email clients that don't support HTML.",
+ html: `
+
+
+ Complex Email
+ This demonstrates:
+
+ - Multiple TO recipients (with and without names)
+ - CC recipient with name
+ - BCC recipient
+ - Both text and HTML content
+
+
+
+ `.trim(),
+ } as any);
+ return new Response(
+ "✅ Complex email sent! Check console for file paths.\n"
+ );
+ }
+
+ // Test all three binding types
+ if (url.pathname === "/test-bindings") {
+ const results: string[] = [];
+
+ // Test UNBOUND_SEND (no restrictions)
+ try {
+ await this.env.UNBOUND_SEND.send({
+ from: "sender@penalosa.cloud",
+ to: "anyone@anywhere.com",
+ subject: "UNBOUND_SEND Test",
+ text: "This uses the unbound binding.",
+ } as any);
+ results.push("✅ UNBOUND_SEND: Success");
+ } catch (e) {
+ results.push(`❌ UNBOUND_SEND: ${(e as Error).message}`);
+ }
+
+ // Test SPECIFIC_SEND (only to something@example.com)
+ try {
+ await this.env.SPECIFIC_SEND.send({
+ from: "sender@penalosa.cloud",
+ to: "something@example.com",
+ subject: "SPECIFIC_SEND Test",
+ text: "This uses the specific binding.",
+ } as any);
+ results.push("✅ SPECIFIC_SEND: Success");
+ } catch (e) {
+ results.push(`❌ SPECIFIC_SEND: ${(e as Error).message}`);
+ }
+
+ // Test LIST_SEND (allowed list)
+ try {
+ await this.env.LIST_SEND.send({
+ from: "sender@penalosa.cloud",
+ to: "else@example.com",
+ subject: "LIST_SEND Test",
+ text: "This uses the list binding.",
+ } as any);
+ results.push("✅ LIST_SEND: Success");
+ } catch (e) {
+ results.push(`❌ LIST_SEND: ${(e as Error).message}`);
+ }
+
+ return new Response(results.join("\n") + "\n");
+ }
+
+ return new Response(
+ "Email Worker Fixture\n\n" +
+ "Available routes:\n" +
+ " /send - EmailMessage API (raw MIME)\n" +
+ " /send-simple - MessageBuilder: Simple text email\n" +
+ " /send-html - MessageBuilder: Text + HTML\n" +
+ " /send-attachment - MessageBuilder: With text attachment\n" +
+ " /send-multi-attachment - MessageBuilder: Multiple attachments\n" +
+ " /send-complex - MessageBuilder: Multiple recipients (to/cc/bcc)\n" +
+ " /test-bindings - Test all three email binding types\n"
+ );
}
async email(message: ForwardableEmailMessage) {
console.log("hello");
diff --git a/packages/cli/check-macos-version.ts b/packages/cli/check-macos-version.ts
index c2b441562f58..16b9b5ba3e19 100644
--- a/packages/cli/check-macos-version.ts
+++ b/packages/cli/check-macos-version.ts
@@ -1,4 +1,5 @@
import os from "node:os";
+import { UserError } from "@cloudflare/workers-utils";
/**
* Minimum macOS version required for workerd compatibility.
@@ -27,7 +28,7 @@ export function checkMacOSVersion(options: { shouldThrow: boolean }): void {
if (macOSVersion && isVersionLessThan(macOSVersion, MINIMUM_MACOS_VERSION)) {
if (options.shouldThrow) {
- throw new Error(
+ throw new UserError(
`Unsupported macOS version: The Cloudflare Workers runtime cannot run on the current version of macOS (${macOSVersion}). ` +
`The minimum requirement is macOS ${MINIMUM_MACOS_VERSION}+. See https://github.com/cloudflare/workerd?tab=readme-ov-file#running-workerd ` +
`If you cannot upgrade your version of macOS, you could try running in a DevContainer setup with a supported version of Linux (glibc 2.35+ required).`
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 8aa3bca1c21f..e3a81b6a912e 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -27,6 +27,7 @@
"@clack/prompts": "^0.6.3",
"@cloudflare/eslint-config-shared": "workspace:*",
"@cloudflare/workers-tsconfig": "workspace:*",
+ "@cloudflare/workers-utils": "workspace:*",
"chalk": "^5.2.0",
"esbuild": "catalog:default",
"eslint": "catalog:default",
diff --git a/packages/create-cloudflare/src/frameworks/package.json b/packages/create-cloudflare/src/frameworks/package.json
index 40a506008797..b3dc22a032e7 100644
--- a/packages/create-cloudflare/src/frameworks/package.json
+++ b/packages/create-cloudflare/src/frameworks/package.json
@@ -14,7 +14,7 @@
"create-qwik": "1.18.0",
"create-vite": "7.1.1",
"create-rwsdk": "3.1.3",
- "create-react-router": "7.11.0",
+ "create-react-router": "7.12.0",
"create-solid": "0.6.11",
"create-vike": "0.0.564",
"create-vue": "3.18.5",
diff --git a/packages/miniflare/package.json b/packages/miniflare/package.json
index 031ca7150a94..ad8ce249d19d 100644
--- a/packages/miniflare/package.json
+++ b/packages/miniflare/package.json
@@ -46,7 +46,7 @@
"@cspotcode/source-map-support": "0.8.1",
"sharp": "^0.34.5",
"undici": "catalog:default",
- "workerd": "1.20260115.0",
+ "workerd": "1.20260116.0",
"ws": "catalog:default",
"youch": "4.1.0-beta.10",
"zod": "^3.25.76"
diff --git a/packages/miniflare/src/workers/core/email.ts b/packages/miniflare/src/workers/core/email.ts
index 32d918819ab5..e2145f40b3a4 100644
--- a/packages/miniflare/src/workers/core/email.ts
+++ b/packages/miniflare/src/workers/core/email.ts
@@ -146,7 +146,10 @@ export async function handleEmail(
);
maybeClientError = reason;
},
- forward: async (rcptTo: string, headers?: Headers): Promise => {
+ forward: async (
+ rcptTo: string,
+ headers?: Headers
+ ): Promise => {
await env[CoreBindings.SERVICE_LOOPBACK].fetch(
"http://localhost/core/log",
{
@@ -155,8 +158,17 @@ export async function handleEmail(
body: `${blue("Email handler forwarded message")}${reset(` with\n rcptTo: ${rcptTo}${renderEmailHeaders(headers)}`)}`,
}
);
+ /**
+ * The message ID in production is a 36 character random string that identifies the message for e.g. linking up threads.
+ * In production it uses the sender domain rather than example.com. Locally, we have access to none of that information
+ * so instead we make a dummy message ID that matches the production format (36 characters followed by a domain)
+ */
+ const uuid = crypto.randomUUID().replaceAll("-", "");
+ return { messageId: `${uuid}@example.com` };
},
- reply: async (replyMessage: MiniflareEmailMessage): Promise => {
+ reply: async (
+ replyMessage: MiniflareEmailMessage
+ ): Promise => {
if (
!(await isEmailReplyable(
parsedIncomingEmail,
@@ -198,6 +210,14 @@ export async function handleEmail(
body: `${blue("Email handler replied to sender")}${reset(` with the following message:\n ${file}`)}`,
}
);
+
+ /**
+ * The message ID in production is a 36 character random string that identifies the message for e.g. linking up threads.
+ * In production it uses the sender domain rather than example.com. Locally, we have access to none of that information
+ * so instead we make a dummy message ID that matches the production format (36 characters followed by a domain)
+ */
+ const uuid = crypto.randomUUID().replaceAll("-", "");
+ return { messageId: `${uuid}@example.com` };
},
} satisfies ForwardableEmailMessage
);
diff --git a/packages/miniflare/src/workers/email/send_email.worker.ts b/packages/miniflare/src/workers/email/send_email.worker.ts
index c3e4c1e506a8..495d40b09d25 100644
--- a/packages/miniflare/src/workers/email/send_email.worker.ts
+++ b/packages/miniflare/src/workers/email/send_email.worker.ts
@@ -5,6 +5,50 @@ import PostalMime, { Email } from "postal-mime";
import { CoreBindings } from "../core/constants";
import { RAW_EMAIL } from "./constants";
import { type MiniflareEmailMessage as EmailMessage } from "./email.worker";
+import type { EmailAddress, MessageBuilder } from "./types";
+
+/**
+ * Extracts email address from string or EmailAddress object
+ */
+function extractEmailAddress(addr: string | EmailAddress): string {
+ return typeof addr === "string" ? addr : addr.email;
+}
+
+/**
+ * Formats an email address for display
+ */
+function formatEmailAddress(addr: string | EmailAddress): string {
+ if (typeof addr === "string") {
+ return addr;
+ }
+ return `"${addr.name}" <${addr.email}>`;
+}
+
+/**
+ * Formats a MessageBuilder for logging
+ */
+function formatMessageBuilder(builder: MessageBuilder): string {
+ const lines: string[] = [];
+
+ lines.push("From: " + formatEmailAddress(builder.from));
+
+ const toArray = Array.isArray(builder.to) ? builder.to : [builder.to];
+ lines.push("To: " + toArray.map(formatEmailAddress).join(", "));
+
+ if (builder.cc) {
+ const ccArray = Array.isArray(builder.cc) ? builder.cc : [builder.cc];
+ lines.push("Cc: " + ccArray.map(formatEmailAddress).join(", "));
+ }
+
+ if (builder.bcc) {
+ const bccArray = Array.isArray(builder.bcc) ? builder.bcc : [builder.bcc];
+ lines.push("Bcc: " + bccArray.map(formatEmailAddress).join(", "));
+ }
+
+ lines.push("Subject: " + builder.subject);
+
+ return lines.join("\n");
+}
interface SendEmailEnv {
[CoreBindings.SERVICE_LOOPBACK]: Fetcher;
@@ -14,6 +58,53 @@ interface SendEmailEnv {
}
export class SendEmailBinding extends WorkerEntrypoint {
+ /**
+ * Logs a message via the loopback service
+ */
+ private log(message: string, level: LogLevel = LogLevel.INFO): void {
+ this.ctx.waitUntil(
+ this.env[CoreBindings.SERVICE_LOOPBACK].fetch(
+ "http://localhost/core/log",
+ {
+ method: "POST",
+ headers: { [SharedHeaders.LOG_LEVEL]: level.toString() },
+ body: message,
+ }
+ )
+ );
+ }
+ /**
+ * Stores content to a temporary file via the loopback service
+ */
+ private async storeTempFile(
+ content: string | ArrayBuffer | ArrayBufferView,
+ extension: string,
+ prefix: string
+ ): Promise {
+ let body: string | Uint8Array;
+ if (typeof content === "string") {
+ body = content;
+ } else if (content instanceof ArrayBuffer) {
+ body = new Uint8Array(content);
+ } else {
+ // ArrayBufferView
+ body = new Uint8Array(
+ content.buffer,
+ content.byteOffset,
+ content.byteLength
+ );
+ }
+
+ const resp = await this.env[CoreBindings.SERVICE_LOOPBACK].fetch(
+ `http://localhost/core/store-temp-file?extension=${extension}&prefix=${prefix}`,
+ {
+ method: "POST",
+ body,
+ }
+ );
+ return await resp.text();
+ }
+
private checkDestinationAllowed(to: string) {
if (
this.env.destination_address !== undefined &&
@@ -37,65 +128,146 @@ export class SendEmailBinding extends WorkerEntrypoint {
throw new Error(`email from ${from} not allowed`);
}
}
- async send(emailMessage: EmailMessage): Promise {
- this.checkDestinationAllowed(emailMessage.to);
- this.checkSenderAllowed(emailMessage.from);
- const rawEmail: ReadableStream = emailMessage[RAW_EMAIL];
+ /**
+ * Type guard to check if argument is an EmailMessage (has RAW_EMAIL symbol)
+ */
+ private isEmailMessage(
+ arg: EmailMessage | MessageBuilder
+ ): arg is EmailMessage {
+ return RAW_EMAIL in arg;
+ }
- const rawEmailBuffer = new Uint8Array(
- await new Response(rawEmail).arrayBuffer()
- );
+ /**
+ * Validates recipients against binding configuration
+ */
+ private validateRecipients(recipients: string | string[]): void {
+ const recipientArray = Array.isArray(recipients)
+ ? recipients
+ : [recipients];
+ for (const recipient of recipientArray) {
+ this.checkDestinationAllowed(recipient);
+ }
+ }
- let parsedEmail: Email;
+ /**
+ * Validates MessageBuilder against binding configuration
+ */
+ private validateMessageBuilder(builder: MessageBuilder): void {
+ // Check sender is allowed
+ const fromEmail = extractEmailAddress(builder.from);
+ this.checkSenderAllowed(fromEmail);
- try {
- parsedEmail = await PostalMime.parse(rawEmailBuffer);
- } catch (e) {
- const error = e as Error;
- throw new Error(`could not parse email: ${error.message}`);
- }
+ // Check "to" recipients are allowed (same as EmailMessage - only validate "to")
+ // Extract email addresses from potential EmailAddress objects
+ const toArray = Array.isArray(builder.to) ? builder.to : [builder.to];
+ const toEmails = toArray.map((addr) => extractEmailAddress(addr));
+ this.validateRecipients(toEmails);
+ }
- if (parsedEmail.messageId === undefined) {
- throw new Error("invalid message-id");
- }
+ async send(
+ emailMessageOrBuilder: EmailMessage | MessageBuilder
+ ): Promise {
+ // Check if this is an EmailMessage (has RAW_EMAIL symbol) or MessageBuilder
+ if (this.isEmailMessage(emailMessageOrBuilder)) {
+ // Original EmailMessage API - validate and parse MIME
+ const emailMessage = emailMessageOrBuilder;
+ this.checkSenderAllowed(emailMessage.from);
+ this.validateRecipients(emailMessage.to);
- let emailHeaders: Headers;
- try {
- emailHeaders = new Headers(
- parsedEmail.headers.map((header) => [header.key, header.value])
+ const rawEmail: ReadableStream = emailMessage[RAW_EMAIL];
+ const rawEmailBuffer = new Uint8Array(
+ await new Response(rawEmail).arrayBuffer()
);
- } catch (e) {
- const error = e as Error;
- throw new Error(`could not parse email: ${error.message}`);
- }
- if (emailMessage.from !== parsedEmail.from.address) {
- throw new Error("From: header does not match mail from");
- }
+ let parsedEmail: Email;
- if (emailHeaders.get("received") !== null) {
- throw new Error("invalid headers set");
- }
+ try {
+ parsedEmail = await PostalMime.parse(rawEmailBuffer);
+ } catch (e) {
+ const error = e as Error;
+ throw new Error(`could not parse email: ${error.message}`);
+ }
- const resp = await this.env[CoreBindings.SERVICE_LOOPBACK].fetch(
- "http://localhost/core/store-temp-file?extension=eml&prefix=email",
- {
- method: "POST",
- body: rawEmailBuffer,
+ if (parsedEmail.messageId === undefined) {
+ throw new Error("invalid message-id");
}
- );
- const file = await resp.text();
- this.ctx.waitUntil(
- this.env[CoreBindings.SERVICE_LOOPBACK].fetch(
- "http://localhost/core/log",
- {
- method: "POST",
- headers: { [SharedHeaders.LOG_LEVEL]: LogLevel.INFO.toString() },
- body: `${blue("send_email binding called with the following message:")}\n ${file}`,
+ let emailHeaders: Headers;
+ try {
+ emailHeaders = new Headers(
+ parsedEmail.headers.map((header) => [header.key, header.value])
+ );
+ } catch (e) {
+ const error = e as Error;
+ throw new Error(`could not parse email: ${error.message}`);
+ }
+
+ if (emailMessage.from !== parsedEmail.from.address) {
+ throw new Error("From: header does not match mail from");
+ }
+
+ if (emailHeaders.get("received") !== null) {
+ throw new Error("invalid headers set");
+ }
+
+ const file = await this.storeTempFile(rawEmailBuffer, "eml", "email");
+
+ this.log(
+ `${blue("send_email binding called with the following message:")}\n ${file}`
+ );
+ } else {
+ // New MessageBuilder API - just validate and log
+ const builder = emailMessageOrBuilder;
+
+ // Validate the message builder
+ this.validateMessageBuilder(builder);
+
+ // Store text, HTML content, and attachments to files for easy viewing
+ const files: string[] = [];
+
+ if (builder.text) {
+ const filePath = await this.storeTempFile(
+ builder.text,
+ "txt",
+ "email-text"
+ );
+ files.push(`Text: ${filePath}`);
+ }
+
+ if (builder.html) {
+ const filePath = await this.storeTempFile(
+ builder.html,
+ "html",
+ "email-html"
+ );
+ files.push(`HTML: ${filePath}`);
+ }
+
+ // Store attachments
+ if (builder.attachments) {
+ for (const attachment of builder.attachments) {
+ // Extract file extension from filename or use generic extension
+ const extMatch = attachment.filename.match(/\.([^.]+)$/);
+ const extension = extMatch ? extMatch[1] : "bin";
+
+ const filePath = await this.storeTempFile(
+ attachment.content,
+ extension,
+ "email-attachment"
+ );
+ files.push(
+ `Attachment (${attachment.disposition}): ${attachment.filename} -> ${filePath}`
+ );
}
- )
- );
+ }
+
+ // Format and log the message details with file paths
+ const formatted = formatMessageBuilder(builder);
+ const fileInfo = files.length > 0 ? `\n\n${files.join("\n")}` : "";
+ this.log(
+ `${blue("send_email binding called with MessageBuilder:")}\n${formatted}${fileInfo}`
+ );
+ }
}
}
diff --git a/packages/miniflare/src/workers/email/types.ts b/packages/miniflare/src/workers/email/types.ts
new file mode 100644
index 000000000000..3276cfde6d5d
--- /dev/null
+++ b/packages/miniflare/src/workers/email/types.ts
@@ -0,0 +1,35 @@
+// Types for the Email Sending API MessageBuilder interface
+
+export type EmailAttachment =
+ | {
+ disposition: "inline";
+ contentId: string;
+ filename: string;
+ type: string;
+ content: string | ArrayBuffer | ArrayBufferView;
+ }
+ | {
+ disposition: "attachment";
+ contentId?: undefined;
+ filename: string;
+ type: string;
+ content: string | ArrayBuffer | ArrayBufferView;
+ };
+
+export interface EmailAddress {
+ name: string;
+ email: string;
+}
+
+export interface MessageBuilder {
+ from: string | EmailAddress;
+ to: string | string[];
+ subject: string;
+ replyTo?: string | EmailAddress;
+ cc?: string | string[];
+ bcc?: string | string[];
+ headers?: Record;
+ text?: string;
+ html?: string;
+ attachments?: EmailAttachment[];
+}
diff --git a/packages/miniflare/test/plugins/email/index.spec.ts b/packages/miniflare/test/plugins/email/index.spec.ts
index 88f9230e5c60..35b410033b0f 100644
--- a/packages/miniflare/test/plugins/email/index.spec.ts
+++ b/packages/miniflare/test/plugins/email/index.spec.ts
@@ -971,3 +971,524 @@ test("reply: references generated correctly", async () => {
)
).toBe(true);
});
+
+const MESSAGE_BUILDER_WORKER = dedent/* javascript */ `
+ export default {
+ async fetch(request, env) {
+ const builder = await request.json();
+ await env.SEND_EMAIL.send(builder);
+ return new Response("ok");
+ },
+ };
+`;
+
+test("MessageBuilder with text only", async () => {
+ const log = new TestLog();
+ const mf = new Miniflare({
+ log,
+ modules: true,
+ script: MESSAGE_BUILDER_WORKER,
+ email: {
+ send_email: [{ name: "SEND_EMAIL" }],
+ },
+ compatibilityDate: "2025-03-17",
+ });
+
+ useDispose(mf);
+
+ const res = await mf.dispatchFetch("http://localhost", {
+ method: "POST",
+ body: JSON.stringify({
+ from: "sender@example.com",
+ to: "recipient@example.com",
+ subject: "Test Email",
+ text: "Hello, this is a test email!",
+ }),
+ });
+
+ expect(await res.text()).toBe("ok");
+ expect(res.status).toBe(200);
+
+ await vi.waitFor(
+ async () => {
+ const entry = log.logs.find(
+ ([type, message]) =>
+ type === LogLevel.INFO &&
+ message.includes("send_email binding called with MessageBuilder:")
+ );
+ if (!entry) {
+ throw new Error(
+ "send_email binding log not found in " +
+ JSON.stringify(log.logs, null, 2)
+ );
+ }
+ const message = entry[1];
+
+ // Verify the formatted message contains expected fields
+ expect(message).toContain("From: sender@example.com");
+ expect(message).toContain("To: recipient@example.com");
+ expect(message).toContain("Subject: Test Email");
+ expect(message).toContain("Text: ");
+ },
+ { timeout: 5_000, interval: 100 }
+ );
+});
+
+test("MessageBuilder with HTML only", async () => {
+ const mf = new Miniflare({
+ modules: true,
+ script: MESSAGE_BUILDER_WORKER,
+ email: {
+ send_email: [{ name: "SEND_EMAIL" }],
+ },
+ compatibilityDate: "2025-03-17",
+ });
+
+ useDispose(mf);
+
+ const res = await mf.dispatchFetch("http://localhost", {
+ method: "POST",
+ body: JSON.stringify({
+ from: "sender@example.com",
+ to: "recipient@example.com",
+ subject: "HTML Test",
+ html: "Hello World
",
+ }),
+ });
+
+ expect(await res.text()).toBe("ok");
+ expect(res.status).toBe(200);
+});
+
+test("MessageBuilder with both text and HTML", async () => {
+ const mf = new Miniflare({
+ modules: true,
+ script: MESSAGE_BUILDER_WORKER,
+ email: {
+ send_email: [{ name: "SEND_EMAIL" }],
+ },
+ compatibilityDate: "2025-03-17",
+ });
+
+ useDispose(mf);
+
+ const res = await mf.dispatchFetch("http://localhost", {
+ method: "POST",
+ body: JSON.stringify({
+ from: "sender@example.com",
+ to: "recipient@example.com",
+ subject: "Multipart Test",
+ text: "Plain text",
+ html: "HTML
",
+ }),
+ });
+
+ expect(await res.text()).toBe("ok");
+ expect(res.status).toBe(200);
+});
+
+test("MessageBuilder with attachments", async () => {
+ const log = new TestLog();
+ const mf = new Miniflare({
+ log,
+ modules: true,
+ script: MESSAGE_BUILDER_WORKER,
+ email: {
+ send_email: [{ name: "SEND_EMAIL" }],
+ },
+ compatibilityDate: "2025-03-17",
+ });
+
+ useDispose(mf);
+
+ const res = await mf.dispatchFetch("http://localhost", {
+ method: "POST",
+ body: JSON.stringify({
+ from: "sender@example.com",
+ to: "recipient@example.com",
+ subject: "Attachment Test",
+ text: "See attachment",
+ attachments: [
+ {
+ disposition: "attachment",
+ filename: "test.txt",
+ type: "text/plain",
+ content: "base64content",
+ },
+ ],
+ }),
+ });
+
+ expect(await res.text()).toBe("ok");
+ expect(res.status).toBe(200);
+
+ await vi.waitFor(
+ async () => {
+ const entry = log.logs.find(
+ ([type, message]) =>
+ type === LogLevel.INFO &&
+ message.includes("send_email binding called with MessageBuilder:")
+ );
+ if (!entry) {
+ throw new Error("send_email binding log not found");
+ }
+ const message = entry[1];
+
+ // Verify attachment file path is logged
+ expect(message).toContain("Attachment (attachment): test.txt ->");
+ },
+ { timeout: 5_000, interval: 100 }
+ );
+});
+
+test("MessageBuilder log output format snapshot", async () => {
+ const log = new TestLog();
+ const mf = new Miniflare({
+ log,
+ modules: true,
+ script: MESSAGE_BUILDER_WORKER,
+ email: {
+ send_email: [{ name: "SEND_EMAIL" }],
+ },
+ compatibilityDate: "2025-03-17",
+ });
+
+ useDispose(mf);
+
+ const res = await mf.dispatchFetch("http://localhost", {
+ method: "POST",
+ body: JSON.stringify({
+ from: { name: "Alice Sender", email: "alice@example.com" },
+ to: ["bob@example.com", "charlie@example.com"],
+ cc: "team@example.com",
+ bcc: "boss@example.com",
+ subject: "Quarterly Report",
+ text: "Please see the attached quarterly report.",
+ html: "Quarterly Report
Please see the attached report.
",
+ attachments: [
+ {
+ disposition: "inline",
+ contentId: "logo123",
+ filename: "logo.png",
+ type: "image/png",
+ content: "iVBORw0KGgo=",
+ },
+ {
+ disposition: "attachment",
+ filename: "report.pdf",
+ type: "application/pdf",
+ content: "JVBERi0xLjc=",
+ },
+ ],
+ }),
+ });
+
+ expect(await res.text()).toBe("ok");
+ expect(res.status).toBe(200);
+
+ await vi.waitFor(
+ async () => {
+ const entry = log.logs.find(
+ ([type, message]) =>
+ type === LogLevel.INFO &&
+ message.includes("send_email binding called with MessageBuilder:")
+ );
+ if (!entry) {
+ throw new Error("send_email binding log not found");
+ }
+ const message = entry[1];
+
+ // Strip ANSI color codes and normalize file paths for snapshot
+ const cleanMessage = message
+ .replace(/\x1b\[[0-9;]*m/g, "")
+ // Replace dynamic file paths with placeholders (Unix and Windows)
+ .replace(
+ /(?:[A-Z]:\\|\/)[^\s]*[/\\](email-text|email-html|email-attachment)[/\\][a-f0-9-]+\.(txt|html|png|pdf)/g,
+ "/$1/[FILE].$2"
+ );
+
+ // Snapshot the entire formatted output
+ expect(cleanMessage).toMatchInlineSnapshot(`
+ "send_email binding called with MessageBuilder:
+ From: "Alice Sender"
+ To: bob@example.com, charlie@example.com
+ Cc: team@example.com
+ Bcc: boss@example.com
+ Subject: Quarterly Report
+
+ Text: /email-text/[FILE].txt
+ HTML: /email-html/[FILE].html
+ Attachment (inline): logo.png -> /email-attachment/[FILE].png
+ Attachment (attachment): report.pdf -> /email-attachment/[FILE].pdf"
+ `);
+ },
+ { timeout: 5_000, interval: 100 }
+ );
+});
+
+test("MessageBuilder with inline attachment", async () => {
+ const mf = new Miniflare({
+ modules: true,
+ script: MESSAGE_BUILDER_WORKER,
+ email: {
+ send_email: [{ name: "SEND_EMAIL" }],
+ },
+ compatibilityDate: "2025-03-17",
+ });
+
+ useDispose(mf);
+
+ const res = await mf.dispatchFetch("http://localhost", {
+ method: "POST",
+ body: JSON.stringify({
+ from: "sender@example.com",
+ to: "recipient@example.com",
+ subject: "Inline Test",
+ html: '
',
+ attachments: [
+ {
+ disposition: "inline",
+ contentId: "logo",
+ filename: "logo.png",
+ type: "image/png",
+ content: "base64imagedata",
+ },
+ ],
+ }),
+ });
+
+ expect(await res.text()).toBe("ok");
+ expect(res.status).toBe(200);
+});
+
+test("MessageBuilder with EmailAddress objects", async () => {
+ const log = new TestLog();
+ const mf = new Miniflare({
+ log,
+ modules: true,
+ script: MESSAGE_BUILDER_WORKER,
+ email: {
+ send_email: [{ name: "SEND_EMAIL" }],
+ },
+ compatibilityDate: "2025-03-17",
+ });
+
+ useDispose(mf);
+
+ const res = await mf.dispatchFetch("http://localhost", {
+ method: "POST",
+ body: JSON.stringify({
+ from: { name: "John Doe", email: "john@example.com" },
+ to: { name: "Jane Smith", email: "jane@example.com" },
+ subject: "Named Address Test",
+ text: "Hello",
+ }),
+ });
+
+ expect(await res.text()).toBe("ok");
+ expect(res.status).toBe(200);
+
+ await vi.waitFor(
+ async () => {
+ const entry = log.logs.find(
+ ([type, message]) =>
+ type === LogLevel.INFO &&
+ message.includes("send_email binding called with MessageBuilder:")
+ );
+ if (!entry) {
+ throw new Error("send_email binding log not found");
+ }
+ const message = entry[1];
+
+ // Verify named addresses are formatted correctly
+ expect(message).toContain('"John Doe" ');
+ expect(message).toContain('"Jane Smith" ');
+ expect(message).toContain("Subject: Named Address Test");
+ },
+ { timeout: 5_000, interval: 100 }
+ );
+});
+
+test("MessageBuilder with multiple recipients", async () => {
+ const log = new TestLog();
+ const mf = new Miniflare({
+ log,
+ modules: true,
+ script: MESSAGE_BUILDER_WORKER,
+ email: {
+ send_email: [{ name: "SEND_EMAIL" }],
+ },
+ compatibilityDate: "2025-03-17",
+ });
+
+ useDispose(mf);
+
+ const res = await mf.dispatchFetch("http://localhost", {
+ method: "POST",
+ body: JSON.stringify({
+ from: "sender@example.com",
+ to: ["recipient1@example.com", "recipient2@example.com"],
+ cc: "cc@example.com",
+ bcc: ["bcc1@example.com", "bcc2@example.com"],
+ subject: "Multiple Recipients",
+ text: "Hello all",
+ }),
+ });
+
+ expect(await res.text()).toBe("ok");
+ expect(res.status).toBe(200);
+
+ await vi.waitFor(
+ async () => {
+ const entry = log.logs.find(
+ ([type, message]) =>
+ type === LogLevel.INFO &&
+ message.includes("send_email binding called with MessageBuilder:")
+ );
+ if (!entry) {
+ throw new Error("send_email binding log not found");
+ }
+ const message = entry[1];
+
+ // Verify multiple recipients are listed
+ expect(message).toContain(
+ "To: recipient1@example.com, recipient2@example.com"
+ );
+ expect(message).toContain("Cc: cc@example.com");
+ expect(message).toContain("Bcc: bcc1@example.com, bcc2@example.com");
+ },
+ { timeout: 5_000, interval: 100 }
+ );
+});
+
+test("MessageBuilder with custom headers", async () => {
+ const mf = new Miniflare({
+ modules: true,
+ script: MESSAGE_BUILDER_WORKER,
+ email: {
+ send_email: [{ name: "SEND_EMAIL" }],
+ },
+ compatibilityDate: "2025-03-17",
+ });
+
+ useDispose(mf);
+
+ const res = await mf.dispatchFetch("http://localhost", {
+ method: "POST",
+ body: JSON.stringify({
+ from: "sender@example.com",
+ to: "recipient@example.com",
+ subject: "Custom Headers",
+ text: "Test",
+ headers: {
+ "X-Custom": "value",
+ },
+ }),
+ });
+
+ expect(await res.text()).toBe("ok");
+ expect(res.status).toBe(200);
+});
+
+test("MessageBuilder respects allowed_destination_addresses", async () => {
+ const mf = new Miniflare({
+ modules: true,
+ script: MESSAGE_BUILDER_WORKER,
+ email: {
+ send_email: [
+ {
+ name: "SEND_EMAIL",
+ allowed_destination_addresses: ["allowed@example.com"],
+ },
+ ],
+ },
+ compatibilityDate: "2025-03-17",
+ });
+
+ useDispose(mf);
+
+ const res = await mf.dispatchFetch("http://localhost", {
+ method: "POST",
+ body: JSON.stringify({
+ from: "sender@example.com",
+ to: "notallowed@example.com",
+ subject: "Test",
+ text: "Test",
+ }),
+ });
+
+ expect(res.status).toBe(500);
+ const error = await res.text();
+ expect(error).toContain("not allowed");
+});
+
+test("MessageBuilder respects allowed_sender_addresses", async () => {
+ const mf = new Miniflare({
+ modules: true,
+ script: MESSAGE_BUILDER_WORKER,
+ email: {
+ send_email: [
+ {
+ name: "SEND_EMAIL",
+ allowed_sender_addresses: ["allowed@example.com"],
+ },
+ ],
+ },
+ compatibilityDate: "2025-03-17",
+ });
+
+ useDispose(mf);
+
+ const res = await mf.dispatchFetch("http://localhost", {
+ method: "POST",
+ body: JSON.stringify({
+ from: "notallowed@example.com",
+ to: "recipient@example.com",
+ subject: "Test",
+ text: "Test",
+ }),
+ });
+
+ expect(res.status).toBe(500);
+ const error = await res.text();
+ expect(error).toContain("not allowed");
+});
+
+test("MessageBuilder backward compatibility - old EmailMessage API still works", async () => {
+ const log = new TestLog();
+ const mf = new Miniflare({
+ log,
+ modules: true,
+ script: SEND_EMAIL_WORKER,
+ email: {
+ send_email: [{ name: "SEND_EMAIL" }],
+ },
+ compatibilityDate: "2025-03-17",
+ });
+
+ useDispose(mf);
+
+ const email = dedent`
+ From: someone
+ To: someone else
+ Message-ID:
+ MIME-Version: 1.0
+ Content-Type: text/plain
+
+ This tests backward compatibility.`;
+
+ const res = await mf.dispatchFetch(
+ "http://localhost/?" +
+ new URLSearchParams({
+ from: "someone@example.com",
+ to: "someone-else@example.com",
+ }).toString(),
+ {
+ body: email,
+ method: "POST",
+ }
+ );
+
+ expect(await res.text()).toBe("ok");
+ expect(res.status).toBe(200);
+});
diff --git a/packages/workers-utils/tests/compatibility-date.test.ts b/packages/workers-utils/tests/compatibility-date.test.ts
index a9b1bda27508..8edb7a75c5be 100644
--- a/packages/workers-utils/tests/compatibility-date.test.ts
+++ b/packages/workers-utils/tests/compatibility-date.test.ts
@@ -8,9 +8,18 @@ describe("getLocalWorkerdCompatibilityDate", () => {
});
it("should successfully get the local latest compatibility date from the local workerd instance", () => {
- // Note: this works because the function gets the monorepo's miniflare/workerd instance
+ vi.spyOn(module, "createRequire").mockImplementation(() => {
+ const mockedRequire = ((pkg: string) => {
+ if (pkg === "workerd") {
+ return { compatibilityDate: "2025-01-10" };
+ }
+ return {};
+ }) as NodeJS.Require;
+ mockedRequire.resolve = (() => "") as unknown as NodeJS.RequireResolve;
+ return mockedRequire;
+ });
const { date, source } = getLocalWorkerdCompatibilityDate();
- expect(date).toMatch(/\d{4}-\d{2}-\d{2}/);
+ expect(date).toBe("2025-01-10");
expect(source).toEqual("workerd");
});
diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json
index e19a2f62eaa3..ad42dd1de158 100644
--- a/packages/wrangler/package.json
+++ b/packages/wrangler/package.json
@@ -73,7 +73,7 @@
"miniflare": "workspace:*",
"path-to-regexp": "6.3.0",
"unenv": "2.0.0-rc.24",
- "workerd": "1.20260115.0"
+ "workerd": "1.20260116.0"
},
"devDependencies": {
"@aws-sdk/client-s3": "^3.721.0",
diff --git a/packages/wrangler/src/__tests__/autoconfig/vite-config.test.ts b/packages/wrangler/src/__tests__/autoconfig/vite-config.test.ts
new file mode 100644
index 000000000000..790e36197641
--- /dev/null
+++ b/packages/wrangler/src/__tests__/autoconfig/vite-config.test.ts
@@ -0,0 +1,124 @@
+import { writeFile } from "node:fs/promises";
+import { beforeEach, describe, expect, it } from "vitest";
+import {
+ checkIfViteConfigUsesCloudflarePlugin,
+ transformViteConfig,
+} from "../../autoconfig/frameworks/utils/vite-config";
+import { logger } from "../../logger";
+import { mockConsoleMethods } from "../helpers/mock-console";
+import { runInTempDir } from "../helpers/run-in-tmp";
+
+describe("vite-config utils", () => {
+ runInTempDir();
+ const std = mockConsoleMethods();
+
+ beforeEach(() => {
+ logger.loggerLevel = "debug";
+ });
+
+ describe("checkIfViteConfigUsesCloudflarePlugin", () => {
+ it("should handle vite config with function-based defineConfig", async () => {
+ await writeFile(
+ "vite.config.ts",
+ `
+import { defineConfig } from 'vite';
+
+export default defineConfig(() => ({
+ plugins: []
+}));
+`
+ );
+
+ const result = checkIfViteConfigUsesCloudflarePlugin(".");
+ expect(result).toBe(false);
+ expect(std.debug).toContain("Vite config uses a non-object expression");
+ });
+
+ it("should handle vite config without plugins array", async () => {
+ await writeFile(
+ "vite.config.ts",
+ `
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ server: { port: 3000 }
+});
+`
+ );
+
+ const result = checkIfViteConfigUsesCloudflarePlugin(".");
+ expect(result).toBe(false);
+ expect(std.debug).toContain(
+ "Vite config does not have a valid plugins array"
+ );
+ });
+
+ it("should detect cloudflare plugin correctly", async () => {
+ await writeFile(
+ "vite.config.ts",
+ `
+import { defineConfig } from 'vite';
+import { cloudflare } from '@cloudflare/vite-plugin';
+
+export default defineConfig({
+ plugins: [cloudflare()]
+});
+`
+ );
+
+ const result = checkIfViteConfigUsesCloudflarePlugin(".");
+ expect(result).toBe(true);
+ });
+ });
+
+ describe("transformViteConfig", () => {
+ it("should throw UserError for function-based defineConfig", async () => {
+ await writeFile(
+ "vite.config.ts",
+ `
+import { defineConfig } from 'vite';
+
+export default defineConfig(() => ({
+ plugins: []
+}));
+`
+ );
+
+ expect(() => transformViteConfig(".")).toThrowError(
+ /Cannot modify Vite config: expected an object literal/
+ );
+ });
+
+ it("should throw UserError when plugins array is missing", async () => {
+ await writeFile(
+ "vite.config.ts",
+ `
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ server: { port: 3000 }
+});
+`
+ );
+
+ expect(() => transformViteConfig(".")).toThrowError(
+ /Cannot modify Vite config: could not find a valid plugins array/
+ );
+ });
+
+ it("should successfully transform valid vite config", async () => {
+ await writeFile(
+ "vite.config.ts",
+ `
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ plugins: []
+});
+`
+ );
+
+ expect(() => transformViteConfig(".")).not.toThrow();
+ });
+ });
+});
diff --git a/packages/wrangler/src/__tests__/core/handle-errors.test.ts b/packages/wrangler/src/__tests__/core/handle-errors.test.ts
index ce3bbd3f50ce..7b52c9aa69da 100644
--- a/packages/wrangler/src/__tests__/core/handle-errors.test.ts
+++ b/packages/wrangler/src/__tests__/core/handle-errors.test.ts
@@ -346,4 +346,58 @@ describe("handleError", () => {
);
});
});
+
+ describe("File not found errors (ENOENT)", () => {
+ it("should show user-friendly message for ENOENT errors with path", async () => {
+ const error = Object.assign(
+ new Error("ENOENT: no such file or directory, open 'wrangler.toml'"),
+ {
+ code: "ENOENT",
+ errno: -2,
+ syscall: "open",
+ path: "wrangler.toml",
+ }
+ );
+
+ const errorType = await handleError(error, {}, []);
+
+ expect(errorType).toBe("FileNotFoundError");
+ expect(std.err).toContain("A file or directory could not be found");
+ expect(std.err).toContain("Missing file or directory: wrangler.toml");
+ expect(std.err).toContain("The file or directory does not exist");
+ });
+
+ it("should show error message when path is not available", async () => {
+ const error = Object.assign(
+ new Error("ENOENT: no such file or directory"),
+ {
+ code: "ENOENT",
+ }
+ );
+
+ const errorType = await handleError(error, {}, []);
+
+ expect(errorType).toBe("FileNotFoundError");
+ expect(std.err).toContain("A file or directory could not be found");
+ expect(std.err).toContain("Error: ENOENT: no such file or directory");
+ expect(std.err).not.toContain("Missing file or directory:");
+ });
+
+ it("should handle ENOENT errors in error cause", async () => {
+ const cause = Object.assign(
+ new Error("ENOENT: no such file or directory, stat '.wrangler'"),
+ {
+ code: "ENOENT",
+ path: ".wrangler",
+ }
+ );
+ const error = new Error("Failed to read directory", { cause });
+
+ const errorType = await handleError(error, {}, []);
+
+ expect(errorType).toBe("FileNotFoundError");
+ expect(std.err).toContain("A file or directory could not be found");
+ expect(std.err).toContain("Missing file or directory: .wrangler");
+ });
+ });
});
diff --git a/packages/wrangler/src/__tests__/delete.test.ts b/packages/wrangler/src/__tests__/delete.test.ts
index 25cb8b242d2a..c2ad6b2e7fde 100644
--- a/packages/wrangler/src/__tests__/delete.test.ts
+++ b/packages/wrangler/src/__tests__/delete.test.ts
@@ -46,6 +46,57 @@ describe("delete", () => {
`);
});
+ it("should delete a service using positional name argument", async () => {
+ mockConfirm({
+ text: `Are you sure you want to delete my-positional-worker? This action cannot be undone.`,
+ result: true,
+ });
+ mockListKVNamespacesRequest();
+ mockListReferencesRequest("my-positional-worker");
+ mockListTailsByConsumerRequest("my-positional-worker");
+ mockDeleteWorkerRequest({ name: "my-positional-worker" });
+ await runWrangler("delete my-positional-worker");
+
+ expect(std).toMatchInlineSnapshot(`
+ Object {
+ "debug": "",
+ "err": "",
+ "info": "",
+ "out": "
+ ⛅️ wrangler x.x.x
+ ──────────────────
+ Successfully deleted my-positional-worker",
+ "warn": "",
+ }
+ `);
+ });
+
+ it("should use positional name argument over the name from the Wrangler config file", async () => {
+ writeWranglerConfig({ name: "config-provided-name" });
+ mockConfirm({
+ text: `Are you sure you want to delete cli-provided-name? This action cannot be undone.`,
+ result: true,
+ });
+ mockListKVNamespacesRequest();
+ mockListReferencesRequest("cli-provided-name");
+ mockListTailsByConsumerRequest("cli-provided-name");
+ mockDeleteWorkerRequest({ name: "cli-provided-name" });
+ await runWrangler("delete cli-provided-name");
+
+ expect(std).toMatchInlineSnapshot(`
+ Object {
+ "debug": "",
+ "err": "",
+ "info": "",
+ "out": "
+ ⛅️ wrangler x.x.x
+ ──────────────────
+ Successfully deleted cli-provided-name",
+ "warn": "",
+ }
+ `);
+ });
+
it("should delete a script by configuration", async () => {
mockConfirm({
text: `Are you sure you want to delete test-name? This action cannot be undone.`,
diff --git a/packages/wrangler/src/__tests__/helpers/string-dynamic-values-matcher.ts b/packages/wrangler/src/__tests__/helpers/string-dynamic-values-matcher.ts
index 74389d794f11..54552de52613 100644
--- a/packages/wrangler/src/__tests__/helpers/string-dynamic-values-matcher.ts
+++ b/packages/wrangler/src/__tests__/helpers/string-dynamic-values-matcher.ts
@@ -24,5 +24,13 @@ export function replaceRandomWithConstantData(
))
);
+ // Normalize FormData boundary formatting for cross-platform consistency
+ // On different platforms (macOS vs Linux), FormData boundaries may format differently
+ // Convert "------formdata-undici-0.test--"" to "------formdata-undici-0.test--\n\t\t\t\t""
+ stringWithConstantData = stringWithConstantData.replace(
+ /(------formdata-undici-[^\n-]+)--"/g,
+ '$1--\n\t\t\t\t"'
+ );
+
return stringWithConstantData;
}
diff --git a/packages/wrangler/src/__tests__/index.test.ts b/packages/wrangler/src/__tests__/index.test.ts
index ecff29f59c1f..dfc59ffca7c4 100644
--- a/packages/wrangler/src/__tests__/index.test.ts
+++ b/packages/wrangler/src/__tests__/index.test.ts
@@ -47,7 +47,7 @@ describe("wrangler", () => {
COMPUTE & AI
wrangler ai 🤖 Manage AI models
wrangler containers 📦 Manage Containers [open beta]
- wrangler delete [script] 🗑️ Delete a Worker from Cloudflare
+ wrangler delete [name] 🗑️ Delete a Worker from Cloudflare
wrangler deploy [script] 🆙 Deploy a Worker to Cloudflare
wrangler deployments 🚢 List and view the current and past deployments for your Worker
wrangler dev [script] 👂 Start a local server for developing your Worker
@@ -118,7 +118,7 @@ describe("wrangler", () => {
COMPUTE & AI
wrangler ai 🤖 Manage AI models
wrangler containers 📦 Manage Containers [open beta]
- wrangler delete [script] 🗑️ Delete a Worker from Cloudflare
+ wrangler delete [name] 🗑️ Delete a Worker from Cloudflare
wrangler deploy [script] 🆙 Deploy a Worker to Cloudflare
wrangler deployments 🚢 List and view the current and past deployments for your Worker
wrangler dev [script] 👂 Start a local server for developing your Worker
diff --git a/packages/wrangler/src/__tests__/kv/namespace.test.ts b/packages/wrangler/src/__tests__/kv/namespace.test.ts
index c9c1f5dcd461..b985abaf6dc7 100644
--- a/packages/wrangler/src/__tests__/kv/namespace.test.ts
+++ b/packages/wrangler/src/__tests__/kv/namespace.test.ts
@@ -120,6 +120,42 @@ describe("kv", () => {
`);
});
+ it("should error if the namespace already exists", async () => {
+ msw.use(
+ http.post(
+ "*/accounts/:accountId/storage/kv/namespaces",
+ () => {
+ return HttpResponse.json(
+ {
+ result: null,
+ success: false,
+ errors: [
+ {
+ code: 10014,
+ message:
+ "create namespace: 'A namespace with this account ID and title already exists'",
+ },
+ ],
+ messages: [],
+ },
+ { status: 400 }
+ );
+ },
+ { once: true }
+ )
+ );
+
+ await expect(runWrangler("kv namespace create DuplicateNamespace"))
+ .rejects.toThrowErrorMatchingInlineSnapshot(`
+ [Error: A KV namespace with the title "DuplicateNamespace" already exists.
+
+ You can list existing namespaces with their IDs by running:
+ wrangler kv namespace list
+
+ Or choose a different namespace name.]
+ `);
+ });
+
describe.each(["wrangler.json", "wrangler.toml"])("%s", (configPath) => {
it("should create a namespace", async () => {
writeWranglerConfig({ name: "worker" }, configPath);
diff --git a/packages/wrangler/src/__tests__/metrics.test.ts b/packages/wrangler/src/__tests__/metrics.test.ts
index b1c6f51e3f1b..248fc9ac69b6 100644
--- a/packages/wrangler/src/__tests__/metrics.test.ts
+++ b/packages/wrangler/src/__tests__/metrics.test.ts
@@ -241,6 +241,9 @@ describe("metrics", () => {
describe("sendCommandEvent()", () => {
const reused = {
wranglerVersion: "1.2.3",
+ wranglerMajorVersion: 1,
+ wranglerMinorVersion: 2,
+ wranglerPatchVersion: 3,
osPlatform: "mock platform",
osVersion: "mock os version",
nodeVersion: 1,
diff --git a/packages/wrangler/src/autoconfig/frameworks/utils/vite-config.ts b/packages/wrangler/src/autoconfig/frameworks/utils/vite-config.ts
index a1aa1492c751..229e44f175dd 100644
--- a/packages/wrangler/src/autoconfig/frameworks/utils/vite-config.ts
+++ b/packages/wrangler/src/autoconfig/frameworks/utils/vite-config.ts
@@ -1,7 +1,9 @@
-import assert from "node:assert";
import { existsSync } from "node:fs";
import path from "node:path";
+import { UserError } from "@cloudflare/workers-utils";
import * as recast from "recast";
+import dedent from "ts-dedent";
+import { logger } from "../../../logger";
import { transformFile } from "../../c3-vendor/codemod";
import type { types } from "recast";
@@ -38,9 +40,20 @@ export function checkIfViteConfigUsesCloudflarePlugin(
}
const config = n.node.arguments[0];
- assert(t.ObjectExpression.check(config));
+ if (!t.ObjectExpression.check(config)) {
+ logger.debug(
+ `Vite config uses a non-object expression (e.g., function). Skipping Cloudflare plugin check.`
+ );
+ return this.traverse(n);
+ }
+
const pluginsProp = config.properties.find((prop) => isPluginsProp(prop));
- assert(pluginsProp && t.ArrayExpression.check(pluginsProp.value));
+ if (!pluginsProp || !t.ArrayExpression.check(pluginsProp.value)) {
+ logger.debug(
+ `Vite config does not have a valid plugins array. Skipping Cloudflare plugin check.`
+ );
+ return this.traverse(n);
+ }
if (
pluginsProp.value.elements.some(
@@ -132,9 +145,33 @@ export function transformViteConfig(
}
const config = n.node.arguments[0];
- assert(t.ObjectExpression.check(config));
+ if (!t.ObjectExpression.check(config)) {
+ throw new UserError(dedent`
+ Cannot modify Vite config: expected an object literal but found ${config.type}.
+
+ The Cloudflare plugin can only be automatically added to Vite configs that use a simple object.
+ If your config uses a function or other dynamic configuration, please manually add the plugin:
+
+ import { cloudflare } from "@cloudflare/vite-plugin";
+
+ export default defineConfig({
+ plugins: [cloudflare()]
+ });
+ `);
+ }
+
const pluginsProp = config.properties.find((prop) => isPluginsProp(prop));
- assert(pluginsProp && t.ArrayExpression.check(pluginsProp.value));
+ if (!pluginsProp || !t.ArrayExpression.check(pluginsProp.value)) {
+ throw new UserError(dedent`
+ Cannot modify Vite config: could not find a valid plugins array.
+
+ Please ensure your Vite config has a plugins array:
+
+ export default defineConfig({
+ plugins: []
+ });
+ `);
+ }
// Only add the Cloudflare plugin if it's not already present
if (
diff --git a/packages/wrangler/src/core/handle-errors.ts b/packages/wrangler/src/core/handle-errors.ts
index 1e5097fa43da..4752a0702c5e 100644
--- a/packages/wrangler/src/core/handle-errors.ts
+++ b/packages/wrangler/src/core/handle-errors.ts
@@ -86,6 +86,33 @@ function isPermissionError(e: unknown): boolean {
return false;
}
+/**
+ * File not found errors (ENOENT) occur when users reference files or directories
+ * that don't exist. We present a helpful message instead of reporting to Sentry.
+ *
+ * @param e - The error to check
+ * @returns `true` if the error is a file not found error, `false` otherwise
+ */
+function isFileNotFoundError(e: unknown): boolean {
+ // Check for Node.js ErrnoException with ENOENT code
+ if (
+ e &&
+ typeof e === "object" &&
+ "code" in e &&
+ e.code === "ENOENT" &&
+ "message" in e
+ ) {
+ return true;
+ }
+
+ // Check in the error cause as well
+ if (e instanceof Error && e.cause) {
+ return isFileNotFoundError(e.cause);
+ }
+
+ return false;
+}
+
/**
* Check if a text string contains a reference to Cloudflare's API domains.
* This is a safety precaution to only handle errors related to Cloudflare's
@@ -201,6 +228,25 @@ function isCloudflareAPIConnectionTimeoutError(e: unknown): boolean {
return false;
}
+/**
+ * Generic "fetch failed" TypeErrors are network errors
+ * caused by network connectivity problems. We show a helpful message instead
+ * of sending to Sentry.
+ *
+ * @param e - The error to check
+ * @returns `true` if the error is a network fetch failure, `false` otherwise
+ */
+function isNetworkFetchFailedError(e: unknown): boolean {
+ if (e instanceof TypeError) {
+ const message = e.message;
+ if (message.includes("fetch failed")) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
/**
* Handles an error thrown during command execution.
*
@@ -296,6 +342,57 @@ export async function handleError(
return errorType;
}
+ // Handle file not found errors with a user-friendly message
+ if (isFileNotFoundError(e)) {
+ mayReport = false;
+ errorType = "FileNotFoundError";
+
+ // Extract the error message and path, checking both the error and its cause
+ const errorMessage = e instanceof Error ? e.message : String(e);
+ let path: string | null = null;
+
+ // Check main error for path
+ if (
+ e &&
+ typeof e === "object" &&
+ "path" in e &&
+ typeof e.path === "string"
+ ) {
+ path = e.path;
+ }
+
+ // If no path in main error, check the cause
+ if (
+ !path &&
+ e instanceof Error &&
+ e.cause &&
+ typeof e.cause === "object" &&
+ "path" in e.cause &&
+ typeof e.cause.path === "string"
+ ) {
+ path = e.cause.path;
+ }
+
+ logger.debug(`File not found error: ${errorMessage}`);
+
+ // Include path in main error if available, otherwise include the error message
+ const errorDetails = path
+ ? `\nMissing file or directory: ${path}\n`
+ : `\nError: ${errorMessage}\n`;
+
+ logger.error(dedent`
+ A file or directory could not be found.
+ ${errorDetails}
+ This is typically caused by:
+ - The file or directory does not exist
+ - A typo in the file path
+ - The file was moved or deleted
+
+ Please check the file path and try again.
+ `);
+ return errorType;
+ }
+
// Handle connection timeout errors to Cloudflare API with a user-friendly message
if (isCloudflareAPIConnectionTimeoutError(e)) {
mayReport = false;
@@ -308,6 +405,21 @@ export async function handleError(
return errorType;
}
+ // Handle generic "fetch failed" / "Failed to fetch" network errors
+ if (isNetworkFetchFailedError(e)) {
+ mayReport = false;
+ logger.warn(dedent`
+ A fetch request failed, likely due to a connectivity issue.
+
+ Common causes:
+ - No internet connection or network connectivity problems
+ - Firewall or VPN blocking the request
+ - Network proxy configuration issues
+
+ Please check your network connection and try again.
+ `);
+ }
+
if (e instanceof CommandLineArgsError) {
logger.error(e.message);
// We are not able to ask the `wrangler` CLI parser to show help for a subcommand programmatically.
diff --git a/packages/wrangler/src/delete.ts b/packages/wrangler/src/delete.ts
index fb8f3ed3140e..11dc09dfcf53 100644
--- a/packages/wrangler/src/delete.ts
+++ b/packages/wrangler/src/delete.ts
@@ -70,6 +70,9 @@ export const deleteCommand = createCommand({
describe: "The path to an entry point for your worker",
type: "string",
requiresArg: true,
+ // TODO: the script argument is meaningless for the delete command, we haven't removed it as that could be
+ // considered a breaking change, we should do so in the next major Wrangler release
+ hidden: true,
},
name: {
describe: "Name of the worker",
@@ -91,7 +94,7 @@ export const deleteCommand = createCommand({
hidden: true,
},
},
- positionalArgs: ["script"],
+ positionalArgs: ["name"],
async handler(args, { config }) {
if (config.pages_build_output_dir) {
throw new UserError(
diff --git a/packages/wrangler/src/kv/index.ts b/packages/wrangler/src/kv/index.ts
index 3e9c4ad27762..c5d02a22e49a 100644
--- a/packages/wrangler/src/kv/index.ts
+++ b/packages/wrangler/src/kv/index.ts
@@ -10,6 +10,8 @@ import {
UserError,
} from "@cloudflare/workers-utils";
import chalk from "chalk";
+import { Cloudflare } from "cloudflare";
+import dedent from "ts-dedent";
import { readConfig } from "../config";
import { demandOneOfOption } from "../core";
import { createCommand, createNamespace } from "../core/create-command";
@@ -107,10 +109,30 @@ export const kvNamespaceCreateCommand = createCommand({
// TODO: generate a binding name stripping non alphanumeric chars
logger.log(`🌀 Creating namespace with title "${title}"`);
- const { id: namespaceId } = await sdk.kv.namespaces.create({
- account_id: accountId,
- title,
- });
+ let namespaceId: string;
+ try {
+ const result = await sdk.kv.namespaces.create({
+ account_id: accountId,
+ title,
+ });
+ namespaceId = result.id;
+ } catch (e) {
+ if (
+ e instanceof Cloudflare.APIError &&
+ e.errors.some((err) => err.code === 10014)
+ ) {
+ throw new UserError(dedent`
+ A KV namespace with the title "${title}" already exists.
+
+ You can list existing namespaces with their IDs by running:
+ wrangler kv namespace list
+
+ Or choose a different namespace name.
+ `);
+ }
+ throw e;
+ }
+
metrics.sendMetricsEvent("create kv namespace", {
sendMetrics: config.send_metrics,
});
diff --git a/packages/wrangler/src/metrics/metrics-dispatcher.ts b/packages/wrangler/src/metrics/metrics-dispatcher.ts
index 88fafee1b148..782a8179c1c6 100644
--- a/packages/wrangler/src/metrics/metrics-dispatcher.ts
+++ b/packages/wrangler/src/metrics/metrics-dispatcher.ts
@@ -142,6 +142,9 @@ export function getMetricsDispatcher(options: MetricsConfigOptions) {
amplitude_session_id,
amplitude_event_id: amplitude_event_id++,
wranglerVersion,
+ wranglerMajorVersion,
+ wranglerMinorVersion,
+ wranglerPatchVersion,
osPlatform: getPlatform(),
osVersion: getOSVersion(),
nodeVersion: getNodeVersion(),
diff --git a/packages/wrangler/src/metrics/types.ts b/packages/wrangler/src/metrics/types.ts
index f623cbd33812..d91518dba143 100644
--- a/packages/wrangler/src/metrics/types.ts
+++ b/packages/wrangler/src/metrics/types.ts
@@ -4,6 +4,12 @@ import type { configFormat } from "@cloudflare/workers-utils";
export type CommonEventProperties = {
/** The version of the Wrangler client that is sending the event. */
wranglerVersion: string;
+ /** The major version component of the Wrangler client. */
+ wranglerMajorVersion: number;
+ /** The minor version component of the Wrangler client. */
+ wranglerMinorVersion: number;
+ /** The patch version component of the Wrangler client. */
+ wranglerPatchVersion: number;
/**
* The platform that the Wrangler client is running on.
*/
diff --git a/packages/wrangler/src/type-generation/index.ts b/packages/wrangler/src/type-generation/index.ts
index 6cf918979496..6e1be8ee2343 100644
--- a/packages/wrangler/src/type-generation/index.ts
+++ b/packages/wrangler/src/type-generation/index.ts
@@ -389,7 +389,7 @@ export async function generateEnvTypes(
const userProvidedEnvInterface = envInterface !== "Env";
if (userProvidedEnvInterface && entrypointFormat === "service-worker") {
- throw new Error(
+ throw new UserError(
"An env-interface value has been provided but the worker uses the incompatible Service Worker syntax"
);
}
diff --git a/packages/wrangler/templates/remoteBindings/ProxyServerWorker.ts b/packages/wrangler/templates/remoteBindings/ProxyServerWorker.ts
index 85e38358d67f..cbfaf7fa7ce9 100644
--- a/packages/wrangler/templates/remoteBindings/ProxyServerWorker.ts
+++ b/packages/wrangler/templates/remoteBindings/ProxyServerWorker.ts
@@ -75,10 +75,20 @@ function getExposedJSRPCBinding(request: Request, env: Env) {
if (targetBinding.constructor.name === "SendEmail") {
return {
- async send(e: ForwardableEmailMessage) {
- // @ts-expect-error EmailMessage::raw is defined in packages/miniflare/src/workers/email/email.worker.ts
- const message = new EmailMessage(e.from, e.to, e["EmailMessage::raw"]);
- return (targetBinding as SendEmail).send(message);
+ async send(e: any) {
+ // Check if this is an EmailMessage (has EmailMessage::raw property) or MessageBuilder
+ if ("EmailMessage::raw" in e) {
+ // EmailMessage API - reconstruct the EmailMessage object
+ const message = new EmailMessage(
+ e.from,
+ e.to,
+ e["EmailMessage::raw"]
+ );
+ return (targetBinding as SendEmail).send(message);
+ } else {
+ // MessageBuilder API - pass through directly as a plain object
+ return (targetBinding as SendEmail).send(e);
+ }
},
};
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c0d3b0587e62..9fa190b537a4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -10,8 +10,8 @@ catalogs:
specifier: ^0.10.11
version: 0.10.15
'@cloudflare/workers-types':
- specifier: ^4.20260115.0
- version: 4.20260115.0
+ specifier: ^4.20260116.0
+ version: 4.20260116.0
'@typescript-eslint/eslint-plugin':
specifier: ^8.35.1
version: 8.46.3
@@ -160,7 +160,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -205,7 +205,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
ts-dedent:
specifier: ^2.2.0
version: 2.2.0
@@ -223,7 +223,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -244,7 +244,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -268,7 +268,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -304,7 +304,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
undici:
specifier: catalog:default
version: 7.18.2
@@ -319,7 +319,7 @@ importers:
devDependencies:
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/mimetext':
specifier: ^2.0.4
version: 2.0.4
@@ -358,7 +358,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/jest-image-snapshot':
specifier: ^6.4.0
version: 6.4.0
@@ -385,7 +385,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
miniflare:
specifier: workspace:*
version: link:../../packages/miniflare
@@ -455,7 +455,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/node':
specifier: ^20.19.9
version: 20.19.9
@@ -479,7 +479,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/is-even':
specifier: ^1.0.2
version: 1.0.2
@@ -516,7 +516,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -544,7 +544,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/node':
specifier: ^20.19.9
version: 20.19.9
@@ -574,7 +574,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
undici:
specifier: catalog:default
version: 7.18.2
@@ -592,7 +592,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/debug':
specifier: 4.1.12
version: 4.1.12
@@ -625,7 +625,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -646,7 +646,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -671,7 +671,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@fixture/pages-plugin':
specifier: workspace:*
version: link:../pages-plugin-example
@@ -695,7 +695,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -734,7 +734,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -755,7 +755,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -776,7 +776,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -794,7 +794,7 @@ importers:
devDependencies:
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
is-odd:
specifier: ^3.0.1
version: 3.0.1
@@ -813,7 +813,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@fixture/pages-plugin':
specifier: workspace:*
version: link:../pages-plugin-example
@@ -873,7 +873,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -894,7 +894,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -1071,19 +1071,19 @@ importers:
devDependencies:
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
fixtures/rules-app:
devDependencies:
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
fixtures/secrets-store:
devDependencies:
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
wrangler:
specifier: workspace:*
version: link:../../packages/wrangler
@@ -1107,7 +1107,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/is-even':
specifier: ^1.0.2
version: 1.0.2
@@ -1131,7 +1131,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
vitest:
specifier: catalog:default
version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.0)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(yaml@2.8.1)
@@ -1146,7 +1146,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
esbuild:
specifier: catalog:default
version: 0.27.0
@@ -1176,7 +1176,7 @@ importers:
version: link:../../packages/vitest-pool-workers
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@microlabs/otel-cf-workers':
specifier: 1.0.0-rc.45
version: 1.0.0-rc.45(@opentelemetry/api@1.7.0)
@@ -1238,7 +1238,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
undici:
specifier: catalog:default
version: 7.18.2
@@ -1293,7 +1293,7 @@ importers:
devDependencies:
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
wrangler:
specifier: workspace:*
version: link:../../packages/wrangler
@@ -1332,7 +1332,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
run-script-os:
specifier: ^1.1.6
version: 1.1.6
@@ -1356,7 +1356,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -1377,7 +1377,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -1398,7 +1398,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -1419,7 +1419,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -1440,7 +1440,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/jest-image-snapshot':
specifier: ^6.4.0
version: 6.4.0
@@ -1473,7 +1473,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
playwright-chromium:
specifier: catalog:default
version: 1.56.1
@@ -1497,7 +1497,7 @@ importers:
version: link:../../packages/workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -1515,7 +1515,7 @@ importers:
devDependencies:
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -1549,6 +1549,9 @@ importers:
'@cloudflare/workers-tsconfig':
specifier: workspace:*
version: link:../workers-tsconfig
+ '@cloudflare/workers-utils':
+ specifier: workspace:*
+ version: link:../workers-utils
chalk:
specifier: ^5.2.0
version: 5.3.0
@@ -1614,7 +1617,7 @@ importers:
version: link:../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@cloudflare/workers-utils':
specifier: workspace:*
version: link:../workers-utils
@@ -1752,7 +1755,7 @@ importers:
version: link:../eslint-config-shared
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@octokit/types':
specifier: ^13.8.0
version: 13.8.0
@@ -1776,7 +1779,7 @@ importers:
version: link:../eslint-config-shared
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/cookie':
specifier: ^0.6.0
version: 0.6.0
@@ -1842,7 +1845,7 @@ importers:
version: link:../eslint-config-shared
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
eslint:
specifier: catalog:default
version: 9.39.1(jiti@2.6.0)
@@ -1872,10 +1875,10 @@ importers:
version: link:../eslint-config-shared
'@cloudflare/vitest-pool-workers':
specifier: catalog:default
- version: 0.10.15(@cloudflare/workers-types@4.20260115.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@2.1.9)
+ version: 0.10.15(@cloudflare/workers-types@4.20260116.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@2.1.9)
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/mime':
specifier: ^3.0.4
version: 3.0.4
@@ -1907,8 +1910,8 @@ importers:
specifier: catalog:default
version: 7.18.2
workerd:
- specifier: 1.20260115.0
- version: 1.20260115.0
+ specifier: 1.20260116.0
+ version: 1.20260116.0
ws:
specifier: catalog:default
version: 8.18.0
@@ -1936,7 +1939,7 @@ importers:
version: link:../workers-shared
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@cloudflare/workflows-shared':
specifier: workspace:*
version: link:../workflows-shared
@@ -2105,7 +2108,7 @@ importers:
version: link:../eslint-config-shared
'@cloudflare/vitest-pool-workers':
specifier: catalog:default
- version: 0.10.15(@cloudflare/workers-types@4.20260115.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@3.2.3)
+ version: 0.10.15(@cloudflare/workers-types@4.20260116.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@3.2.3)
'@cloudflare/workers-shared':
specifier: workspace:*
version: link:../workers-shared
@@ -2114,7 +2117,7 @@ importers:
version: link:../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
concurrently:
specifier: ^8.2.2
version: 8.2.2
@@ -2154,7 +2157,7 @@ importers:
version: link:../eslint-config-shared
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/cookie':
specifier: ^0.6.0
version: 0.6.0
@@ -2194,7 +2197,7 @@ importers:
version: link:../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/node':
specifier: ^20.19.9
version: 20.19.9
@@ -2224,7 +2227,7 @@ importers:
version: link:../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
esbuild:
specifier: catalog:default
version: 0.27.0
@@ -2313,7 +2316,7 @@ importers:
version: link:../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@cloudflare/workers-utils':
specifier: workspace:*
version: link:../workers-utils
@@ -2400,7 +2403,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2421,7 +2424,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2442,7 +2445,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2463,7 +2466,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2484,7 +2487,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2505,7 +2508,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2526,7 +2529,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2547,7 +2550,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2568,7 +2571,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2589,7 +2592,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2610,7 +2613,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2631,7 +2634,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2652,7 +2655,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2673,7 +2676,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2694,7 +2697,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2715,7 +2718,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2736,7 +2739,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/mimetext':
specifier: ^2.0.4
version: 2.0.4
@@ -2769,7 +2772,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2790,7 +2793,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2811,7 +2814,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2832,7 +2835,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2853,7 +2856,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2874,7 +2877,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -2895,7 +2898,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@playground/main-resolution-package':
specifier: file:./package
version: file:packages/vite-plugin-cloudflare/playground/main-resolution/package
@@ -2919,7 +2922,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/express':
specifier: ^5.0.1
version: 5.0.1
@@ -2946,7 +2949,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@playground/module-resolution-excludes':
specifier: file:./packages/excludes
version: file:packages/vite-plugin-cloudflare/playground/module-resolution/packages/excludes
@@ -2958,7 +2961,7 @@ importers:
version: file:packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires
'@remix-run/cloudflare':
specifier: 2.12.0
- version: 2.12.0(@cloudflare/workers-types@4.20260115.0)(typescript@5.8.3)
+ version: 2.12.0(@cloudflare/workers-types@4.20260116.0)(typescript@5.8.3)
'@types/react':
specifier: ^18.3.11
version: 18.3.18
@@ -2991,7 +2994,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -3012,7 +3015,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/debug':
specifier: ^4.1.12
version: 4.1.12
@@ -3061,7 +3064,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/react':
specifier: 19.1.0
version: 19.1.0
@@ -3082,7 +3085,7 @@ importers:
dependencies:
partyserver:
specifier: ^0.0.64
- version: 0.0.64(@cloudflare/workers-types@4.20260115.0)
+ version: 0.0.64(@cloudflare/workers-types@4.20260116.0)
partysocket:
specifier: ^1.0.3
version: 1.0.3
@@ -3101,7 +3104,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@tailwindcss/vite':
specifier: ^4.0.15
version: 4.0.15(vite@7.1.12(@types/node@20.19.9)(jiti@2.6.0)(lightningcss@1.30.2)(yaml@2.8.1))
@@ -3137,7 +3140,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@prisma/adapter-d1':
specifier: ^7.0.0
version: 7.0.1
@@ -3174,7 +3177,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/react':
specifier: 19.1.0
version: 19.1.0
@@ -3204,7 +3207,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -3225,7 +3228,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -3253,7 +3256,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/react':
specifier: 19.1.0
version: 19.1.0
@@ -3286,7 +3289,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -3307,7 +3310,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -3328,7 +3331,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@vitejs/plugin-basic-ssl':
specifier: ^2.0.0
version: 2.0.0(vite@7.1.12(@types/node@20.19.9)(jiti@2.6.0)(lightningcss@1.30.2)(yaml@2.8.1))
@@ -3352,7 +3355,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -3373,7 +3376,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -3394,7 +3397,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -3415,7 +3418,7 @@ importers:
version: link:../../../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
typescript:
specifier: catalog:default
version: 5.8.3
@@ -3455,7 +3458,7 @@ importers:
version: link:../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@cloudflare/workflows-shared':
specifier: workspace:*
version: link:../workflows-shared
@@ -3673,13 +3676,13 @@ importers:
version: link:../eslint-config-shared
'@cloudflare/vitest-pool-workers':
specifier: catalog:default
- version: 0.10.15(@cloudflare/workers-types@4.20260115.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@2.1.9)
+ version: 0.10.15(@cloudflare/workers-types@4.20260116.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@2.1.9)
'@cloudflare/workers-tsconfig':
specifier: workspace:*
version: link:../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@sentry/cli':
specifier: ^2.37.0
version: 2.41.1(encoding@0.1.13)
@@ -3796,13 +3799,13 @@ importers:
version: link:../eslint-config-shared
'@cloudflare/vitest-pool-workers':
specifier: catalog:default
- version: 0.10.15(@cloudflare/workers-types@4.20260115.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@3.2.3)
+ version: 0.10.15(@cloudflare/workers-types@4.20260116.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@3.2.3)
'@cloudflare/workers-tsconfig':
specifier: workspace:*
version: link:../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@types/mime':
specifier: ^3.0.4
version: 3.0.4
@@ -3846,8 +3849,8 @@ importers:
specifier: 2.0.0-rc.24
version: 2.0.0-rc.24
workerd:
- specifier: 1.20260115.0
- version: 1.20260115.0
+ specifier: 1.20260116.0
+ version: 1.20260116.0
optionalDependencies:
fsevents:
specifier: ~2.3.2
@@ -3879,7 +3882,7 @@ importers:
version: link:../workers-tsconfig
'@cloudflare/workers-types':
specifier: catalog:default
- version: 4.20260115.0
+ version: 4.20260116.0
'@cloudflare/workers-utils':
specifier: workspace:*
version: link:../workers-utils
@@ -4878,8 +4881,8 @@ packages:
cpu: [x64]
os: [darwin]
- '@cloudflare/workerd-darwin-64@1.20260115.0':
- resolution: {integrity: sha512-4XDikrMLnvQ7PDUF8Ji8YAu13G0i9p6oUij9ATdZHhcLslTfp7AOh7M1KJZRAAJb4OJFlEaNUKlGPoK/m2gBLg==}
+ '@cloudflare/workerd-darwin-64@1.20260116.0':
+ resolution: {integrity: sha512-0LF2jR/5bfCIMYsqtCXHqaZRlXEMgnz4NzG/8KVmHROlKb06SJezYYoNKw+7s6ji4fgi1BcYAJBmWbC4nzMbqw==}
engines: {node: '>=16'}
cpu: [x64]
os: [darwin]
@@ -4896,8 +4899,8 @@ packages:
cpu: [arm64]
os: [darwin]
- '@cloudflare/workerd-darwin-arm64@1.20260115.0':
- resolution: {integrity: sha512-vmWJsGKkLGCJw7hExaFAXjUk3RgNqVcqU8AkzkNvCS0S74C6ihLFp7++kPYs3eVfUDgH2/fUCALUmEWY7/QhCA==}
+ '@cloudflare/workerd-darwin-arm64@1.20260116.0':
+ resolution: {integrity: sha512-a9OHts4jMoOkPedc4CnuHPeo9XRG3VCMMgr0ER5HtSfEDRQhh7MwIuPEmqI27KKrYj+DeoCazIgbp3gW9bFTAg==}
engines: {node: '>=16'}
cpu: [arm64]
os: [darwin]
@@ -4914,8 +4917,8 @@ packages:
cpu: [x64]
os: [linux]
- '@cloudflare/workerd-linux-64@1.20260115.0':
- resolution: {integrity: sha512-o6GiYNchY5WprhTjOM9i4MBS9KvPi2OKbByQ1Rk5f97qGxsF+oZDi+p+a7hFFh7+yyKk2Fl51aj3yg/iEdx7rQ==}
+ '@cloudflare/workerd-linux-64@1.20260116.0':
+ resolution: {integrity: sha512-nCMy7D7BeH/feGiD7C5Z1LG19Wvs3qmHSRe3cwz6HYRQHdDXUHTjXwEVid7Vejf9QFNe3iAn49Sy/h2XY2Rqeg==}
engines: {node: '>=16'}
cpu: [x64]
os: [linux]
@@ -4932,8 +4935,8 @@ packages:
cpu: [arm64]
os: [linux]
- '@cloudflare/workerd-linux-arm64@1.20260115.0':
- resolution: {integrity: sha512-m9quPRLIz31aZ7FdzxkvT7dxLLGSiPpkrerS9jDWkPRy3xjSs7y4eqEazaipvcy7V0SNtrWbIruelNf9t1YozQ==}
+ '@cloudflare/workerd-linux-arm64@1.20260116.0':
+ resolution: {integrity: sha512-Hve4ciPI69aIzwfSD12PVZJoEnKIkdR3Vd0w8rD1hDVxk75xAA65KqVYf5qW+8KOYrYkU3pg7hBTMjeyDF//IQ==}
engines: {node: '>=16'}
cpu: [arm64]
os: [linux]
@@ -4950,14 +4953,14 @@ packages:
cpu: [x64]
os: [win32]
- '@cloudflare/workerd-windows-64@1.20260115.0':
- resolution: {integrity: sha512-y18p+4fQbrcyvVMAibU3N9THRyKs7hz57NVPmQ1oereWVNSl+hnueajGuiDnD8bVjtN6oGmOSM+dvN5O06DhgQ==}
+ '@cloudflare/workerd-windows-64@1.20260116.0':
+ resolution: {integrity: sha512-7QA6OTXQtBdszkXw3rzxpkk1RoINZJY1ADQjF0vFNAbVXD1VEXLZnk0jc505tqARI8w/0DdVjaJszqL7K5k00w==}
engines: {node: '>=16'}
cpu: [x64]
os: [win32]
- '@cloudflare/workers-types@4.20260115.0':
- resolution: {integrity: sha512-vi68ZODh6m9fH9wdBOzDsyWgrYRIZbzZEAGGkvFn4b1FQSukxaWS8NAtSd/h9mL4gVK9hG8FEYq/jipdOo4RJg==}
+ '@cloudflare/workers-types@4.20260116.0':
+ resolution: {integrity: sha512-iMZIQDco7ARzzH+r8j90757kbPKEetKY3/6V5UurOzS6T1GJ+rsREw5i7vlBA4XjFV5UMaPtUD6HuUFSMLcxPQ==}
'@colors/colors@1.5.0':
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
@@ -13972,8 +13975,8 @@ packages:
engines: {node: '>=16'}
hasBin: true
- workerd@1.20260115.0:
- resolution: {integrity: sha512-phyxUf3E4suGbpRpn/CIC+2idc4j6D14c4QBEh/GDf+jIpOUGFOIyc3UQxgYH+40kUZtol3MSeHdZMxKIeIYXA==}
+ workerd@1.20260116.0:
+ resolution: {integrity: sha512-tVdBes3qkZKm9ntrgSDlvKzk4g2mcMp4bNM1+UgZMpTesb0x7e59vYYcKclbSNypmVkdLWpEc2TOpO0WF/rrZw==}
engines: {node: '>=16'}
hasBin: true
@@ -15399,7 +15402,7 @@ snapshots:
lodash.memoize: 4.1.2
marked: 0.3.19
- '@cloudflare/vitest-pool-workers@0.10.15(@cloudflare/workers-types@4.20260115.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@2.1.9)':
+ '@cloudflare/vitest-pool-workers@0.10.15(@cloudflare/workers-types@4.20260116.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@2.1.9)':
dependencies:
'@vitest/runner': 3.2.3
'@vitest/snapshot': 3.2.3
@@ -15409,14 +15412,14 @@ snapshots:
miniflare: 4.20251210.0
semver: 7.7.3
vitest: 2.1.9(@types/node@20.19.9)(@vitest/ui@2.1.9)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))
- wrangler: 4.54.0(@cloudflare/workers-types@4.20260115.0)
+ wrangler: 4.54.0(@cloudflare/workers-types@4.20260116.0)
zod: 3.25.76
transitivePeerDependencies:
- '@cloudflare/workers-types'
- bufferutil
- utf-8-validate
- '@cloudflare/vitest-pool-workers@0.10.15(@cloudflare/workers-types@4.20260115.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@3.2.3)':
+ '@cloudflare/vitest-pool-workers@0.10.15(@cloudflare/workers-types@4.20260116.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@3.2.3)':
dependencies:
'@vitest/runner': 3.2.3
'@vitest/snapshot': 3.2.3
@@ -15426,7 +15429,7 @@ snapshots:
miniflare: 4.20251210.0
semver: 7.7.3
vitest: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.0)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(yaml@2.8.1)
- wrangler: 4.54.0(@cloudflare/workers-types@4.20260115.0)
+ wrangler: 4.54.0(@cloudflare/workers-types@4.20260116.0)
zod: 3.25.76
transitivePeerDependencies:
- '@cloudflare/workers-types'
@@ -15439,7 +15442,7 @@ snapshots:
'@cloudflare/workerd-darwin-64@1.20260111.0':
optional: true
- '@cloudflare/workerd-darwin-64@1.20260115.0':
+ '@cloudflare/workerd-darwin-64@1.20260116.0':
optional: true
'@cloudflare/workerd-darwin-arm64@1.20251210.0':
@@ -15448,7 +15451,7 @@ snapshots:
'@cloudflare/workerd-darwin-arm64@1.20260111.0':
optional: true
- '@cloudflare/workerd-darwin-arm64@1.20260115.0':
+ '@cloudflare/workerd-darwin-arm64@1.20260116.0':
optional: true
'@cloudflare/workerd-linux-64@1.20251210.0':
@@ -15457,7 +15460,7 @@ snapshots:
'@cloudflare/workerd-linux-64@1.20260111.0':
optional: true
- '@cloudflare/workerd-linux-64@1.20260115.0':
+ '@cloudflare/workerd-linux-64@1.20260116.0':
optional: true
'@cloudflare/workerd-linux-arm64@1.20251210.0':
@@ -15466,7 +15469,7 @@ snapshots:
'@cloudflare/workerd-linux-arm64@1.20260111.0':
optional: true
- '@cloudflare/workerd-linux-arm64@1.20260115.0':
+ '@cloudflare/workerd-linux-arm64@1.20260116.0':
optional: true
'@cloudflare/workerd-windows-64@1.20251210.0':
@@ -15475,10 +15478,10 @@ snapshots:
'@cloudflare/workerd-windows-64@1.20260111.0':
optional: true
- '@cloudflare/workerd-windows-64@1.20260115.0':
+ '@cloudflare/workerd-windows-64@1.20260116.0':
optional: true
- '@cloudflare/workers-types@4.20260115.0': {}
+ '@cloudflare/workers-types@4.20260116.0': {}
'@colors/colors@1.5.0':
optional: true
@@ -16728,7 +16731,7 @@ snapshots:
'@prisma/adapter-d1@7.0.1':
dependencies:
- '@cloudflare/workers-types': 4.20260115.0
+ '@cloudflare/workers-types': 4.20260116.0
'@prisma/driver-adapter-utils': 7.0.1
ky: 1.7.5
@@ -16955,10 +16958,10 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.3
- '@remix-run/cloudflare@2.12.0(@cloudflare/workers-types@4.20260115.0)(typescript@5.8.3)':
+ '@remix-run/cloudflare@2.12.0(@cloudflare/workers-types@4.20260116.0)(typescript@5.8.3)':
dependencies:
'@cloudflare/kv-asset-handler': 0.1.3
- '@cloudflare/workers-types': 4.20260115.0
+ '@cloudflare/workers-types': 4.20260116.0
'@remix-run/server-runtime': 2.12.0(typescript@5.8.3)
optionalDependencies:
typescript: 5.8.3
@@ -22478,9 +22481,9 @@ snapshots:
parseurl@1.3.3: {}
- partyserver@0.0.64(@cloudflare/workers-types@4.20260115.0):
+ partyserver@0.0.64(@cloudflare/workers-types@4.20260116.0):
dependencies:
- '@cloudflare/workers-types': 4.20260115.0
+ '@cloudflare/workers-types': 4.20260116.0
nanoid: 5.1.0
partysocket@1.0.3:
@@ -25337,15 +25340,15 @@ snapshots:
'@cloudflare/workerd-linux-arm64': 1.20260111.0
'@cloudflare/workerd-windows-64': 1.20260111.0
- workerd@1.20260115.0:
+ workerd@1.20260116.0:
optionalDependencies:
- '@cloudflare/workerd-darwin-64': 1.20260115.0
- '@cloudflare/workerd-darwin-arm64': 1.20260115.0
- '@cloudflare/workerd-linux-64': 1.20260115.0
- '@cloudflare/workerd-linux-arm64': 1.20260115.0
- '@cloudflare/workerd-windows-64': 1.20260115.0
+ '@cloudflare/workerd-darwin-64': 1.20260116.0
+ '@cloudflare/workerd-darwin-arm64': 1.20260116.0
+ '@cloudflare/workerd-linux-64': 1.20260116.0
+ '@cloudflare/workerd-linux-arm64': 1.20260116.0
+ '@cloudflare/workerd-windows-64': 1.20260116.0
- wrangler@4.54.0(@cloudflare/workers-types@4.20260115.0):
+ wrangler@4.54.0(@cloudflare/workers-types@4.20260116.0):
dependencies:
'@cloudflare/kv-asset-handler': 0.4.1
'@cloudflare/unenv-preset': 2.7.13(unenv@2.0.0-rc.24)(workerd@1.20251210.0)
@@ -25356,7 +25359,7 @@ snapshots:
unenv: 2.0.0-rc.24
workerd: 1.20251210.0
optionalDependencies:
- '@cloudflare/workers-types': 4.20260115.0
+ '@cloudflare/workers-types': 4.20260116.0
fsevents: 2.3.3
transitivePeerDependencies:
- bufferutil
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 794ddac936ab..4796d3ecb80c 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -34,8 +34,8 @@ catalog:
"ws": "8.18.0"
esbuild: "0.27.0"
playwright-chromium: "^1.56.1"
- "@cloudflare/workers-types": "^4.20260115.0"
- workerd: "1.20260115.0"
+ "@cloudflare/workers-types": "^4.20260116.0"
+ workerd: "1.20260116.0"
eslint: "^9.39.1"
smol-toml: "^1.5.2"
# CAUTION: Most usage of @cloudflare/vitest-pool-workers in this mono repo should use workspace:* instead of this catalog version