Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/add-pages-functions-package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@cloudflare/pages-functions": minor
---

Publish helpers for compiling Pages Functions directories into Workers bundle

Provides both a programmatic API and a CLI (`pages-functions build`) for converting a Cloudflare Pages `functions/` directory into a Cloudflare Workers bundle:

```sh
npx @cloudflare/pages-functions build ./functions --outdir ./dist
```

The package compiles the Worker and its auxiliary modules, but does not deploy them or generate deployment configuration. Consumers must provide the appropriate Wrangler configuration, including the selected fallback service binding (`ASSETS` by default) when applicable.
11 changes: 11 additions & 0 deletions .changeset/c3-frameworks-update-14908.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"create-cloudflare": patch
---

Update dependencies of "create-cloudflare"

The following dependency versions have been updated:

| Dependency | From | To |
| --------------- | ------- | ------- |
| create-next-app | 16.2.11 | 16.2.12 |
11 changes: 11 additions & 0 deletions .changeset/c3-frameworks-update-14909.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"create-cloudflare": patch
---

Update dependencies of "create-cloudflare"

The following dependency versions have been updated:

| Dependency | From | To |
| ---------- | ------ | ------ |
| sv | 0.16.5 | 0.16.6 |
11 changes: 11 additions & 0 deletions .changeset/c3-frameworks-update-14910.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"create-cloudflare": patch
---

Update dependencies of "create-cloudflare"

The following dependency versions have been updated:

| Dependency | From | To |
| ----------- | ------- | ------- |
| create-vike | 0.0.668 | 0.0.670 |
11 changes: 11 additions & 0 deletions .changeset/c3-frameworks-update-14911.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"create-cloudflare": patch
---

Update dependencies of "create-cloudflare"

The following dependency versions have been updated:

| Dependency | From | To |
| ------------ | ----- | ----- |
| create-astro | 5.2.2 | 5.2.3 |
7 changes: 7 additions & 0 deletions .changeset/curvy-rivers-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": minor
---

Avoid Worker and workers.dev naming prompts in agent-driven deploys

Wrangler now derives the Worker name from the project and automatically registers the same project-derived workers.dev account subdomain on a first deploy when running in a detected agent environment. The deploy output explains how to change both names.
7 changes: 7 additions & 0 deletions .changeset/miniflare-loopback-keepalive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"miniflare": patch
---

Disable the keep-alive timeout on the loopback server

The loopback server (which serves custom service bindings, `@cloudflare/vite-plugin`'s module transport, and other workerd → Node callbacks) used Node's default `server.keepAliveTimeout` of 5 seconds. workerd pools and reuses connections to the loopback server, so Node closing an idle pooled socket raced with workerd sending the next request on it, making that request fail with `Network connection lost`. The failure is probabilistic and load-dependent; under `@cloudflare/vite-plugin` with a large SSR module graph and a cold optimizer cache (thousands of `fetchModule` calls with multi-second idle gaps between bursts), it broke most dev sessions. Disable the idle keep-alive timeout on the loopback server, mirroring the undici pools used for dispatch in the opposite direction.
7 changes: 7 additions & 0 deletions .changeset/move-url-path-to-workers-utils.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/workers-utils": minor
---

Add `toUrlPath` and `UrlPath` exports

`toUrlPath(filePath)` converts a file-system path into a URL-safe path by replacing backslashes with forward slashes and rejecting Windows drive-letter prefixes (e.g. `C:`). `UrlPath` is the branded string type it returns, letting callers prove at the type level that a string has been normalized for use in URLs.
7 changes: 7 additions & 0 deletions .changeset/tidy-pears-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": patch
---

Fix `wrangler triggers deploy` to use Vite-generated redirected configuration

The command now reads `.wrangler/deploy/config.json`, matching `wrangler deploy` and `wrangler versions upload`, so generated Worker names and trigger settings are applied.
10 changes: 6 additions & 4 deletions packages/create-cloudflare/e2e/tests/frameworks/test-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,6 @@ function getFrameworkTestConfig(pm: string): NamedFrameworkTestConfig[] {
},
{
name: "nuxt:pages",
quarantine: true,
promptHandlers: [
{
matcher: /Would you like to .* install .*modules\?/,
Expand All @@ -371,7 +370,9 @@ function getFrameworkTestConfig(pm: string): NamedFrameworkTestConfig[] {
argv: ["--platform", "pages"],
testCommitMessage: true,
timeout: LONG_TIMEOUT,
unsupportedPms: ["yarn"], // Currently nitro requires youch which expects Node 20+, and yarn will fail hard since we run on Node 18
// yarn: nitro requires youch which expects Node 20+, and yarn will fail hard since we run on Node 18.
// npm: nuxt project creation fails on npm with "Cannot read properties of null (reading 'edgesOut')".
unsupportedPms: ["yarn", "npm"],
unsupportedOSs: ["win32"],
// The Nuxt `ui` template pins `packageManager: pnpm@11.9.0`, so run
// it only on pnpm 11+: under pnpm 10 the cross-version self-provision
Expand All @@ -391,7 +392,6 @@ function getFrameworkTestConfig(pm: string): NamedFrameworkTestConfig[] {
},
{
name: "nuxt:workers",
quarantine: true,
promptHandlers: [
{
matcher: /Would you like to .* install .*modules\?/,
Expand All @@ -401,7 +401,9 @@ function getFrameworkTestConfig(pm: string): NamedFrameworkTestConfig[] {
argv: ["--platform", "workers"],
testCommitMessage: true,
timeout: LONG_TIMEOUT,
unsupportedPms: ["yarn"], // Currently nitro requires youch which expects Node 20+, and yarn will fail hard since we run on Node 18
// yarn: nitro requires youch which expects Node 20+, and yarn will fail hard since we run on Node 18.
// npm: nuxt project creation fails on npm with "Cannot read properties of null (reading 'edgesOut')".
unsupportedPms: ["yarn", "npm"],
unsupportedOSs: ["win32"],
// See note on nuxt:pages above.
unsupportedPmRanges: { pnpm: "<11.0.0" },
Expand Down
8 changes: 4 additions & 4 deletions packages/create-cloudflare/src/frameworks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@
"@angular/create": "22.0.8",
"@tanstack/cli": "0.70.1",
"create-analog": "2.6.4",
"create-astro": "5.2.2",
"create-astro": "5.2.3",
"create-docusaurus": "3.10.2",
"create-hono": "0.19.4",
"create-next-app": "16.2.11",
"create-next-app": "16.2.12",
"create-qwik": "1.20.0",
"create-react-router": "8.3.0",
"create-rwsdk": "3.1.3",
"create-solid": "0.8.0",
"create-vike": "0.0.668",
"create-vike": "0.0.670",
"create-vite": "9.1.1",
"create-vue": "3.23.0",
"create-waku": "0.12.5-1.0.0-alpha.10-0",
"gatsby": "5.16.1",
"nuxi": "3.37.0",
"sv": "0.16.5"
"sv": "0.16.6"
},
"info": [
"This package.json is only used to keep track of the frameworks cli dependencies",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ export async function preUploadApiChecks(
// such deploys still get a correctly-worded prompt in the triggers phase).
if (!workerExists && wantsWorkersDev) {
await getWorkersDevSubdomain(config, accountId, {
autoRegisterSubdomain: props.autoRegisterWorkersDevSubdomain,
configPath: config.configPath,
});
}
Expand Down
2 changes: 2 additions & 0 deletions packages/deploy-helpers/src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ export type SharedDeployVersionsProps = {
export type DeployProps = SharedDeployVersionsProps & {
/** Discriminant for DeployProps vs VersionsUploadProps */
command: "deploy";
/** If set, automatically register this `workers.dev` account subdomain when the account has none. */
autoRegisterWorkersDevSubdomain?: string;
/** Merged from --site arg and config.site. */
legacyAssetPaths: LegacyAssetPaths | undefined;
/** Merged: --triggers arg ?? config.triggers.crons. */
Expand Down
84 changes: 71 additions & 13 deletions packages/deploy-helpers/src/triggers/subdomain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,23 @@ import type { ComplianceConfig } from "@cloudflare/workers-utils";
type WorkersDevSubdomainRegistrationContext = "workers_dev" | "workflows";

type GetWorkersDevSubdomainOptions = {
configPath?: string | undefined;
abortSignal?: AbortSignal | undefined;
autoRegisterSubdomain?: string | undefined;
configPath?: string | undefined;
registrationContext?: WorkersDevSubdomainRegistrationContext | undefined;
};

function toValidSubdomain(input: string): string {
const subdomain = input
.toLowerCase()
.replace(/[^a-z0-9-]+/g, "-")
.replace(/^-+/, "")
.slice(0, 63)
.replace(/-+$/, "");

return subdomain || "my-worker";
}

/**
* Gets the <user-subdomain>.(fed.)workers.dev URL for the given account.
*/
Expand All @@ -24,8 +36,9 @@ export async function getWorkersDevSubdomain(
options: GetWorkersDevSubdomainOptions = {}
): Promise<string> {
const {
configPath,
abortSignal,
autoRegisterSubdomain,
configPath,
registrationContext = "workers_dev",
} = options;

Expand All @@ -48,6 +61,15 @@ export async function getWorkersDevSubdomain(
// 10007 error code: not found
// https://api.cloudflare.com/#worker-subdomain-get-subdomain
logger.warn(getRegistrationWarning(registrationContext));
if (autoRegisterSubdomain) {
return await registerSubdomain(
complianceConfig,
accountId,
configPath,
registrationContext,
autoRegisterSubdomain
);
}

const wantsToRegister = await confirm(
"Would you like to register a workers.dev subdomain now?",
Expand Down Expand Up @@ -116,14 +138,21 @@ async function registerSubdomain(
complianceConfig: ComplianceConfig,
accountId: string,
configPath: string | undefined,
registrationContext: WorkersDevSubdomainRegistrationContext
registrationContext: WorkersDevSubdomainRegistrationContext,
automaticSubdomain?: string
): Promise<string> {
let subdomain: string | undefined;
let suggestedSubdomain = automaticSubdomain
? toValidSubdomain(automaticSubdomain)
: undefined;

while (subdomain === undefined) {
const potentialName = await prompt(
"What would you like your workers.dev subdomain to be? It will be accessible at https://<subdomain>.workers.dev"
);
const potentialName =
suggestedSubdomain ??
(await prompt(
"What would you like your workers.dev subdomain to be? It will be accessible at https://<subdomain>.workers.dev"
));
suggestedSubdomain = undefined;

if (!/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/.test(potentialName)) {
logger.warn(
Expand All @@ -148,24 +177,44 @@ async function registerSubdomain(
// oddly enough, this is a `subdomain_unavailable` error, meaning...that the subdomain
// doesn't exist. and we can register it. this is exactly how the dashboard does it.
} else if (subdomainAvailabilityCheckError.code === 10031) {
if (automaticSubdomain) {
throw new UserError(
`Wrangler could not automatically register "${potentialName}" as your workers.dev subdomain because the name is unavailable. Register a different subdomain at https://dash.cloudflare.com/${accountId}/workers/onboarding.`,
{
telemetryMessage:
"workers dev automatic registration name unavailable",
}
);
}
logger.error(
"Subdomain is unavailable, please try a different subdomain"
);
continue;
} else {
if (automaticSubdomain) {
throw new UserError(
`Wrangler could not verify whether "${potentialName}" is available as your \`workers.dev\` subdomain. Register a subdomain at https://dash.cloudflare.com/${accountId}/workers/onboarding.`,
{
telemetryMessage:
"workers dev automatic registration availability check failed",
}
);
}
logger.error("An unexpected error occurred, please try again.");
continue;
}
}
}

const ok = await confirm(
`Creating a workers.dev subdomain for your account at ${chalk.blue(
chalk.underline(
`https://${potentialName}${getComplianceRegionSubdomain(complianceConfig)}.workers.dev`
)
)}. Ok to proceed?`
);
const ok =
automaticSubdomain !== undefined ||
(await confirm(
`Creating a workers.dev subdomain for your account at ${chalk.blue(
chalk.underline(
`https://${potentialName}${getComplianceRegionSubdomain(complianceConfig)}.workers.dev`
)
)}. Ok to proceed?`
));
if (!ok) {
throw getRegistrationDeclinedError(
registrationContext,
Expand All @@ -186,6 +235,15 @@ async function registerSubdomain(
subdomain = result.subdomain;
} catch (err) {
const subdomainCreationError = err as { code?: number };
if (automaticSubdomain) {
throw new UserError(
`Wrangler could not automatically register "${potentialName}" as your \`workers.dev\` subdomain. Register a subdomain at https://dash.cloudflare.com/${accountId}/workers/onboarding.`,
{
telemetryMessage:
"workers dev automatic registration creation failed",
}
);
}
if (
typeof subdomainCreationError === "object" &&
!!subdomainCreationError &&
Expand Down
61 changes: 61 additions & 0 deletions packages/deploy-helpers/tests/triggers-subdomain.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { beforeEach, describe, it, vi } from "vitest";
import { initDeployHelpersContext } from "../src/shared/context";
import { getWorkersDevSubdomain } from "../src/triggers/subdomain";
import type { ComplianceConfig } from "@cloudflare/workers-utils";

const ACCOUNT_ID = "some-account-id";

describe("getWorkersDevSubdomain", () => {
const confirm = vi.fn();
const prompt = vi.fn();

beforeEach(() => {
initDeployHelpersContext({
confirm,
fetchKVGetValue: (() => {}) as never,
fetchListResult: (() => {}) as never,
fetchPagedListResult: (() => {}) as never,
fetchResult: (async (
_config: ComplianceConfig,
path: string,
init?: RequestInit
) => {
if (path.endsWith("/workers/subdomain") && !init) {
throw Object.assign(new Error("Subdomain not found"), {
code: 10007,
});
}
if (path.endsWith("/workers/subdomains/my-project")) {
throw Object.assign(new Error("Subdomain is available"), {
code: 10032,
});
}
if (path.endsWith("/workers/subdomain") && init?.method === "PUT") {
return { subdomain: "my-project" };
}
throw new Error(`Unexpected request: ${init?.method ?? "GET"} ${path}`);
}) as never,
logger: {
debug() {},
error() {},
info() {},
log() {},
warn() {},
},
prompt,
select: (() => {}) as never,
});
});

it("normalizes an automatic subdomain without prompting", async ({
expect,
}) => {
const subdomain = await getWorkersDevSubdomain({}, ACCOUNT_ID, {
autoRegisterSubdomain: "My Project!",
});

expect(subdomain).toBe("my-project.workers.dev");
expect(confirm).not.toHaveBeenCalled();
expect(prompt).not.toHaveBeenCalled();
});
});
8 changes: 8 additions & 0 deletions packages/miniflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1899,6 +1899,14 @@ export class Miniflare {
http.createServer(this.#handleLoopback),
/* grace */ 0
);
// Disable the idle keep-alive timeout for local dev — workerd pools
// and reuses connections to the loopback server, and Node's default
// `keepAliveTimeout` (5s) races with that reuse: Node closes an idle
// pooled socket just as workerd sends the next request on it, which
// surfaces in the Worker as "Network connection lost". This mirrors
// the undici pools used for dispatch in the opposite direction, which
// already disable their timeouts.
server.keepAliveTimeout = 0;
server.on("upgrade", this.#handleLoopbackUpgrade);
server.listen(0, hostname, () => resolve(server));
});
Expand Down
Loading
Loading