From 4cdfebdbf45d71b40eb548bbc255b2af6c1f065d Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Thu, 20 Aug 2026 16:57:54 +0100 Subject: [PATCH 1/4] [wrangler] Skip VPC Service remote binding test (#15281) --- .../e2e/remote-binding/miniflare-remote-resources.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts index 8f8e9c9b643..59abc7ff123 100644 --- a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts +++ b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts @@ -609,6 +609,9 @@ const testCases: TestCase[] = [ { name: "VPC Service", scriptPath: "vpc-service.js", + // Currently this test starts failing whenever the VPC worker is doing a deployment + // Re-enable when EW-10563 is resolved + skip: true, setup: async (helper) => { const serviceName = generateResourceName(); From 39dcea6c9362e2d651e3108fa769dbbc32db5a7b Mon Sep 17 00:00:00 2001 From: emily-shen <69125074+emily-shen@users.noreply.github.com> Date: Thu, 20 Aug 2026 17:36:51 +0100 Subject: [PATCH 2/4] move writeOutput into deploy helpers (#15284) --- .changeset/quiet-output-helpers.md | 7 + packages/deploy-helpers/src/deploy/deploy.ts | 41 +++- .../src/deploy/versions-upload.ts | 47 +++- packages/deploy-helpers/src/shared/types.ts | 2 + packages/workers-utils/src/index.ts | 2 + packages/workers-utils/src/output.ts | 219 ++++++++++++++++++ packages/wrangler/src/deploy/index.ts | 24 +- packages/wrangler/src/output.ts | 205 +--------------- packages/wrangler/src/versions/upload.ts | 32 +-- 9 files changed, 325 insertions(+), 254 deletions(-) create mode 100644 .changeset/quiet-output-helpers.md create mode 100644 packages/workers-utils/src/output.ts diff --git a/.changeset/quiet-output-helpers.md b/.changeset/quiet-output-helpers.md new file mode 100644 index 00000000000..5edd25de04c --- /dev/null +++ b/.changeset/quiet-output-helpers.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +"@cloudflare/deploy-helpers": patch +"@cloudflare/workers-utils": patch +--- + +Move deploy output writing into shared deploy helpers diff --git a/packages/deploy-helpers/src/deploy/deploy.ts b/packages/deploy-helpers/src/deploy/deploy.ts index 127a2608489..20d82b405bf 100644 --- a/packages/deploy-helpers/src/deploy/deploy.ts +++ b/packages/deploy-helpers/src/deploy/deploy.ts @@ -12,6 +12,7 @@ import { parseNonHyphenedUuid, retryOnAPIFailure, UserError, + writeOutput, } from "@cloudflare/workers-utils"; import { Response } from "undici"; import { fetchResult, logger } from "../shared/context"; @@ -133,18 +134,44 @@ export type DeployCallbacks = { | undefined; }; -export default async function deploy( - props: DeployProps, - config: Config, - buildResult: WorkerBuildResult, - callbacks: DeployCallbacks -): Promise<{ +type DeployResult = { sourceMapSize?: number; versionId: string | null; workerTag: string | null; assetUploadStats?: AssetUploadStats; targets?: string[]; -}> { +}; + +export default async function deploy( + props: DeployProps, + config: Config, + buildResult: WorkerBuildResult, + callbacks: DeployCallbacks +): Promise { + // DO NOT put anything in this function, this is just a thin wrapper to call writeOutput at the end + + const result = await deployWorker(props, config, buildResult, callbacks); + + writeOutput({ + type: "deploy", + version: 1, + worker_name: props.name ?? null, + worker_tag: result.workerTag, + version_id: result.versionId, + targets: result.targets, + wrangler_environment: props.env, + worker_name_overridden: props.workerNameOverridden ?? false, + }); + + return result; +} + +async function deployWorker( + props: DeployProps, + config: Config, + buildResult: WorkerBuildResult, + callbacks: DeployCallbacks +): Promise { const { entry, compatibilityDate, compatibilityFlags, keepVars, accountId } = props; diff --git a/packages/deploy-helpers/src/deploy/versions-upload.ts b/packages/deploy-helpers/src/deploy/versions-upload.ts index ebce780fd4a..97400f9cb78 100644 --- a/packages/deploy-helpers/src/deploy/versions-upload.ts +++ b/packages/deploy-helpers/src/deploy/versions-upload.ts @@ -8,6 +8,7 @@ import { ParseError, retryOnAPIFailure, UserError, + writeOutput, } from "@cloudflare/workers-utils"; import { Response } from "undici"; import { fetchResult, logger } from "../shared/context"; @@ -52,18 +53,50 @@ import type { FormData } from "undici"; export type VersionsUploadCallbacks = Pick; -export default async function versionsUpload( - props: VersionsUploadProps, - config: Config, - buildResult: WorkerBuildResult, - callbacks: VersionsUploadCallbacks -): Promise<{ +type VersionsUploadResult = { versionId: string | null; workerTag: string | null; assetUploadStats?: AssetUploadStats; versionPreviewUrl?: string | undefined; versionPreviewAliasUrl?: string | undefined; -}> { +}; + +export default async function versionsUpload( + props: VersionsUploadProps, + config: Config, + buildResult: WorkerBuildResult, + callbacks: VersionsUploadCallbacks +): Promise { + // DO NOT put anything in this function, this is just a thin wrapper to call writeOutput at the end + + const result = await uploadWorkerVersion( + props, + config, + buildResult, + callbacks + ); + + writeOutput({ + type: "version-upload", + version: 1, + worker_name: props.name ?? null, + worker_tag: result.workerTag, + version_id: result.versionId, + preview_url: result.versionPreviewUrl, + preview_alias_url: result.versionPreviewAliasUrl, + wrangler_environment: props.env, + worker_name_overridden: props.workerNameOverridden ?? false, + }); + + return result; +} + +async function uploadWorkerVersion( + props: VersionsUploadProps, + config: Config, + buildResult: WorkerBuildResult, + callbacks: VersionsUploadCallbacks +): Promise { const { entry, compatibilityDate, compatibilityFlags, keepVars, accountId } = props; diff --git a/packages/deploy-helpers/src/shared/types.ts b/packages/deploy-helpers/src/shared/types.ts index 94ade226c20..3b155fe3746 100644 --- a/packages/deploy-helpers/src/shared/types.ts +++ b/packages/deploy-helpers/src/shared/types.ts @@ -104,6 +104,8 @@ export type SharedDeployVersionsProps = { skipProvisioningConfigWriteback: boolean; /** From --strict arg. In strict mode, conflicting pre-upload checks abort instead of auto-continuing. */ strict: boolean; + /** Whether the resolved Worker name differs from the pre-merge config/args name. */ + workerNameOverridden?: boolean; }; export type DeployProps = SharedDeployVersionsProps & { diff --git a/packages/workers-utils/src/index.ts b/packages/workers-utils/src/index.ts index 97cc84982dd..b1b3526a121 100644 --- a/packages/workers-utils/src/index.ts +++ b/packages/workers-utils/src/index.ts @@ -161,6 +161,8 @@ export type { Logger, LoggerLevel } from "./logger"; export { isCI, isInteractive, isNonInteractiveOrCI } from "./is-interactive"; export { openInBrowser } from "./open-in-browser"; +export { clearOutputFilePath, writeOutput } from "./output"; +export type { OutputEntry } from "./output"; export { retryOnAPIFailure } from "./retry"; export { formatTime } from "./format-time"; diff --git a/packages/workers-utils/src/output.ts b/packages/workers-utils/src/output.ts new file mode 100644 index 00000000000..2fa2fc296d0 --- /dev/null +++ b/packages/workers-utils/src/output.ts @@ -0,0 +1,219 @@ +import { randomBytes } from "node:crypto"; +import { appendFileSync, mkdirSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { + getOutputFileDirectoryFromEnv, + getOutputFilePathFromEnv, +} from "./environment-variables/misc-variables"; + +/** + * Write an entry to the output file. + * + * Control whether (and where) to write this file by setting either + * `WRANGLER_OUTPUT_FILE_DIRECTORY` or `WRANGLER_OUTPUT_FILE_PATH`. + * + * @param entry The structured output entry to append. + * @returns Nothing. + */ +export function writeOutput(entry: OutputEntry): void { + if (outputFilePath === undefined) { + outputFilePath = getOutputFilePath(); + } + if (outputFilePath !== null) { + mkdirSync(dirname(outputFilePath), { recursive: true }); + const entryJSON = JSON.stringify({ + ...entry, + timestamp: new Date().toISOString(), + }); + appendFileSync(outputFilePath, entryJSON + "\n"); + } +} + +/** + * Clear the cached output file path. + * + * @returns Nothing. + */ +export function clearOutputFilePath(): void { + outputFilePath = undefined; +} + +let outputFilePath: string | null | undefined = undefined; + +/** + * Get the output file path from the configured output environment variables. + * + * @returns The configured output file path, or null when output is disabled. + */ +function getOutputFilePath(): string | null { + const outputFilePathFromEnv = getOutputFilePathFromEnv(); + if (outputFilePathFromEnv) { + return outputFilePathFromEnv; + } + + const outputFileDirectoryFromEnv = getOutputFileDirectoryFromEnv(); + if (outputFileDirectoryFromEnv) { + const date = new Date() + .toISOString() + .replaceAll(":", "-") + .replace(".", "_") + .replace("T", "_") + .replace("Z", ""); + + return resolve( + outputFileDirectoryFromEnv, + `wrangler-output-${date}-${randomBytes(3).toString("hex")}.json` + ); + } + + return null; +} + +interface OutputEntryBase { + version: number; + type: T; +} + +/** + * All the different types of entry that can be written to the output file. + */ +export type OutputEntry = + | OutputEntrySession + | OutputEntryDeployment + | OutputEntryPreview + | OutputEntryPagesDeployment + | OutputEntryVersionUpload + | OutputEntryVersionDeployment + | OutputEntryPagesDeploymentDetailed + | OutputEntryCommandFailed + | OutputEntryAutoConfig; + +interface OutputEntrySession extends OutputEntryBase<"wrangler-session"> { + version: 1; + /** The semver version string taken from Wrangler's package.json. */ + wrangler_version: string; + /** The arguments passed to Wrangler. */ + command_line_args: string[]; + /** The absolute path to a file that contains debug logs for this Wrangler instance. */ + log_file_path: string; +} + +interface OutputEntryDeployment extends OutputEntryBase<"deploy"> { + version: 1; + /** The name of the Worker. */ + worker_name: string | null; + /** The GUID that identifies the Worker. This never changes even if the name is changed. */ + worker_tag: string | null; + /** A GUID that identifies this deployed version of the Worker. This version is associated with an automatically created deployment, with this version set at 100%. */ + version_id: string | null; + /** A list of URLs that represent the HTTP triggers associated with this deployment */ + targets: string[] | undefined; + /** set if the worker's name was overridden */ + worker_name_overridden: boolean; + /** wrangler environment used */ + wrangler_environment: string | undefined; +} + +interface OutputEntryPreview extends OutputEntryBase<"preview"> { + version: 1; + /** The name of the Worker. */ + worker_name: string | null; + /** The ID of the Preview resource. */ + preview_id: string; + /** The human-readable name of the Preview resource. */ + preview_name: string; + /** The slug of the Preview resource. */ + preview_slug: string; + /** A list of URLs associated with the Preview resource. */ + preview_urls: string[] | undefined; + /** The ID of the Preview deployment resource. */ + deployment_id: string; + /** A list of URLs associated with the Preview deployment. */ + deployment_urls: string[] | undefined; +} + +interface OutputEntryAutoConfig extends OutputEntryBase<"autoconfig"> { + version: 1; + /** The command that triggered autoconfig */ + command: "setup" | "deploy"; + /** The summary of the autoconfig process */ + summary: { + wranglerConfig?: { + name?: string; + compatibility_date?: string; + }; + [key: string]: unknown; + }; +} + +interface OutputEntryPagesDeployment extends OutputEntryBase<"pages-deploy"> { + version: 1; + /** The name of the Pages project. */ + pages_project: string | null; + /** A GUID that identifies this Pages deployment. */ + deployment_id: string | null; + /** The URL associated with this deployment */ + url: string | undefined; +} + +interface OutputEntryPagesDeploymentDetailed extends OutputEntryBase<"pages-deploy-detailed"> { + version: 1; + /** The name of the Pages project. */ + pages_project: string | null; + /** A GUID that identifies this Pages deployment. */ + deployment_id: string | null; + /** The URL associated with this deployment */ + url: string | undefined; + /** The Alias url, if it exists */ + alias: string | undefined; + /** The environment being deployed to */ + environment: "production" | "preview"; + /** The production branch of the pages project */ + production_branch: string; + deployment_trigger: { + metadata: { + /** Commit hash of the deployment trigger metadata for the pages project */ + commit_hash: string; + }; + }; +} + +interface OutputEntryVersionUpload extends OutputEntryBase<"version-upload"> { + version: 1; + /** The name of the Worker. */ + worker_name: string | null; + /** The GUID that identifies the Worker. This never changes even if the name is changed. */ + worker_tag: string | null; + /** A GUID that identifies this uploaded, but not yet deployed, version of the Worker. This version will need to be "deployed" to receive traffic. */ + version_id: string | null; + /** The preview URL associated with this version upload */ + preview_url: string | undefined; + /** The ephemeral aliased preview URL associated with this version upload */ + preview_alias_url: string | undefined; + /** set if the worker's name was overridden */ + worker_name_overridden: boolean; + /** wrangler environment used */ + wrangler_environment: string | undefined; +} + +interface OutputEntryVersionDeployment extends OutputEntryBase<"version-deploy"> { + version: 1; + /** The name of the Worker. */ + worker_name: string | null; + /** The GUID that identifies the Worker. This never changes even if the name is changed. */ + worker_tag: string | null; + /** The ID of the gradual rollout deployment. */ + deployment_id: string; + /** The percentage of traffic that goes to each version. */ + version_traffic: Map; +} + +interface OutputEntryCommandFailed extends OutputEntryBase<"command-failed"> { + version: 1; + /** The code in the error. */ + code: number | undefined; + /** The message in the error. */ + message: string | undefined; + /** How many milliseconds to retry the operation after if available. */ + retry_after_ms: number | undefined; +} diff --git a/packages/wrangler/src/deploy/index.ts b/packages/wrangler/src/deploy/index.ts index 5820d2f539a..bd01e55d38e 100644 --- a/packages/wrangler/src/deploy/index.ts +++ b/packages/wrangler/src/deploy/index.ts @@ -20,7 +20,6 @@ import { import { experimentalNewConfigArg } from "../experimental-config/cli-flag"; import { logger } from "../logger"; import * as metrics from "../metrics"; -import { writeOutput } from "../output"; import { syncWorkersSite } from "../sites"; import { detectAgent } from "../utils/detect-agent"; import { getScriptName } from "../utils/getScriptName"; @@ -188,32 +187,25 @@ export async function runDeployCommandHandler( try { // Derive workerNameOverridden by comparing pre-merge name with post-merge name const preMergeName = getScriptName(args, config); - const workerNameOverridden = + props.workerNameOverridden = props.name !== undefined && props.name !== preMergeName; const beforeUpload = Date.now(); const buildResult = await buildWorker(buildProps, config); - const { sourceMapSize, versionId, workerTag, assetUploadStats, targets } = - await deploy(props, config, buildResult, { + const { sourceMapSize, assetUploadStats } = await deploy( + props, + config, + buildResult, + { syncWorkersSite, getNormalizedContainerOptions, buildContainer, deployContainers, analyseBundle, - }); - - writeOutput({ - type: "deploy", - version: 1, - worker_name: props.name ?? null, - worker_tag: workerTag, - version_id: versionId, - targets, - wrangler_environment: args.env, - worker_name_overridden: workerNameOverridden, - }); + } + ); metrics.sendMetricsEvent( "deploy worker script", diff --git a/packages/wrangler/src/output.ts b/packages/wrangler/src/output.ts index 65d845d6154..af53e8edbd1 100644 --- a/packages/wrangler/src/output.ts +++ b/packages/wrangler/src/output.ts @@ -1,202 +1,5 @@ -import { randomBytes } from "node:crypto"; -import { appendFileSync } from "node:fs"; -import { resolve } from "node:path"; -import { - getOutputFileDirectoryFromEnv, - getOutputFilePathFromEnv, +export { + clearOutputFilePath, + writeOutput, + type OutputEntry, } from "@cloudflare/workers-utils"; -import { ensureDirectoryExistsSync } from "./utils/filesystem"; -import type { AutoConfigSummary } from "@cloudflare/autoconfig"; - -/** - * Write an entry to the output file. - * - * Control whether (and where) to write this file by setting either - * `WRANGLER_OUTPUT_FILE_DIRECTORY` or `WRANGLER_OUTPUT_FILE_PATH`. - */ -export function writeOutput(entry: OutputEntry) { - if (outputFilePath === undefined) { - outputFilePath = getOutputFilePath(); - } - if (outputFilePath !== null) { - ensureDirectoryExistsSync(outputFilePath); - const entryJSON = JSON.stringify({ - ...entry, - timestamp: new Date().toISOString(), - }); - appendFileSync(outputFilePath, entryJSON + "\n"); - } -} - -// Only used internally for cleaning up tests -export function clearOutputFilePath() { - outputFilePath = undefined; -} - -let outputFilePath: string | null | undefined = undefined; -function getOutputFilePath() { - const outputFilePathFromEnv = getOutputFilePathFromEnv(); - if (outputFilePathFromEnv) { - return outputFilePathFromEnv; - } - - const outputFileDirectoryFromEnv = getOutputFileDirectoryFromEnv(); - if (outputFileDirectoryFromEnv) { - const date = new Date() - .toISOString() - .replaceAll(":", "-") - .replace(".", "_") - .replace("T", "_") - .replace("Z", ""); - - return resolve( - outputFileDirectoryFromEnv, - `wrangler-output-${date}-${randomBytes(3).toString("hex")}.json` - ); - } - - return null; -} - -interface OutputEntryBase { - version: number; - type: T; -} - -/** - * All the different types of entry you can output. - */ -export type OutputEntry = - | OutputEntrySession - | OutputEntryDeployment - | OutputEntryPreview - | OutputEntryPagesDeployment - | OutputEntryVersionUpload - | OutputEntryVersionDeployment - | OutputEntryPagesDeploymentDetailed - | OutputEntryCommandFailed - | OutputEntryAutoConfig; - -interface OutputEntrySession extends OutputEntryBase<"wrangler-session"> { - version: 1; - /** The semver version string taken from Wrangler's package.json. */ - wrangler_version: string; - /** The arguments passed to Wrangler. */ - command_line_args: string[]; - /** The absolute path to a file that contains debug logs for this Wrangler instance. */ - log_file_path: string; -} - -interface OutputEntryDeployment extends OutputEntryBase<"deploy"> { - version: 1; - /** The name of the Worker. */ - worker_name: string | null; - /** The GUID that identifies the Worker. This never changes even if the name is changed. */ - worker_tag: string | null; - /** A GUID that identifies this deployed version of the Worker. This version is associated with an automatically created deployment, with this version set at 100%. */ - version_id: string | null; - /** A list of URLs that represent the HTTP triggers associated with this deployment */ - targets: string[] | undefined; - /** set if the worker's name was overridden */ - worker_name_overridden: boolean; - /** wrangler environment used */ - wrangler_environment: string | undefined; -} - -interface OutputEntryPreview extends OutputEntryBase<"preview"> { - version: 1; - /** The name of the Worker. */ - worker_name: string | null; - /** The ID of the Preview resource. */ - preview_id: string; - /** The human-readable name of the Preview resource. */ - preview_name: string; - /** The slug of the Preview resource. */ - preview_slug: string; - /** A list of URLs associated with the Preview resource. */ - preview_urls: string[] | undefined; - /** The ID of the Preview deployment resource. */ - deployment_id: string; - /** A list of URLs associated with the Preview deployment. */ - deployment_urls: string[] | undefined; -} - -interface OutputEntryAutoConfig extends OutputEntryBase<"autoconfig"> { - version: 1; - /** The command that triggered autoconfig */ - command: "setup" | "deploy"; - /** The summary of the autoconfig process */ - summary: AutoConfigSummary; -} - -interface OutputEntryPagesDeployment extends OutputEntryBase<"pages-deploy"> { - version: 1; - /** The name of the Pages project. */ - pages_project: string | null; - /** A GUID that identifies this Pages deployment. */ - deployment_id: string | null; - /** The URL associated with this deployment */ - url: string | undefined; -} - -interface OutputEntryPagesDeploymentDetailed extends OutputEntryBase<"pages-deploy-detailed"> { - version: 1; - /** The name of the Pages project. */ - pages_project: string | null; - /** A GUID that identifies this Pages deployment. */ - deployment_id: string | null; - /** The URL associated with this deployment */ - url: string | undefined; - /** The Alias url, if it exists */ - alias: string | undefined; - /** The environment being deployed to */ - environment: "production" | "preview"; - /** The production branch of the pages project */ - production_branch: string; - deployment_trigger: { - metadata: { - /** Commit hash of the deployment trigger metadata for the pages project */ - commit_hash: string; - }; - }; -} - -interface OutputEntryVersionUpload extends OutputEntryBase<"version-upload"> { - version: 1; - /** The name of the Worker. */ - worker_name: string | null; - /** The GUID that identifies the Worker. This never changes even if the name is changed. */ - worker_tag: string | null; - /** A GUID that identifies this uploaded, but not yet deployed, version of the Worker. This version will need to be "deployed" to receive traffic. */ - version_id: string | null; - /** The preview URL associated with this version upload */ - preview_url: string | undefined; - /** The ephemeral aliased preview URL associated with this version upload */ - preview_alias_url: string | undefined; - /** set if the worker's name was overridden */ - worker_name_overridden: boolean; - /** wrangler environment used */ - wrangler_environment: string | undefined; -} - -interface OutputEntryVersionDeployment extends OutputEntryBase<"version-deploy"> { - version: 1; - /** The name of the Worker. */ - worker_name: string | null; - /** The GUID that identifies the Worker. This never changes even if the name is changed. */ - worker_tag: string | null; - /** The ID of the gradual rollout deployment. */ - deployment_id: string; - /** The percentage of traffic that goes to each version. */ - version_traffic: Map; -} - -interface OutputEntryCommandFailed extends OutputEntryBase<"command-failed"> { - version: 1; - /** The code in the error. */ - code: number | undefined; - /** The message in the error. */ - message: string | undefined; - /** How many milliseconds to retry the operation after if available. */ - retry_after_ms: number | undefined; -} diff --git a/packages/wrangler/src/versions/upload.ts b/packages/wrangler/src/versions/upload.ts index 5bd4f187790..56bc3dd437d 100644 --- a/packages/wrangler/src/versions/upload.ts +++ b/packages/wrangler/src/versions/upload.ts @@ -15,7 +15,6 @@ import { } from "../deployment-bundle/merge-config-args"; import { experimentalNewConfigArg } from "../experimental-config/cli-flag"; import * as metrics from "../metrics"; -import { writeOutput } from "../output"; import { getScriptName } from "../utils/getScriptName"; export const versionsUploadCommand = createCommand({ @@ -59,33 +58,20 @@ export const versionsUploadCommand = createCommand({ try { // Derive workerNameOverridden by comparing pre-merge name with post-merge name const preMergeName = getScriptName(args, config); - const workerNameOverridden = + props.workerNameOverridden = props.name !== undefined && props.name !== preMergeName; const buildResult = await buildWorker(buildProps, config); - const { - versionId, - workerTag, - assetUploadStats: uploadStats, - versionPreviewUrl, - versionPreviewAliasUrl, - } = await versionsUpload(props, config, buildResult, { - analyseBundle: analyseBundle, - }); + const { assetUploadStats: uploadStats } = await versionsUpload( + props, + config, + buildResult, + { + analyseBundle: analyseBundle, + } + ); assetUploadStats = uploadStats; - - writeOutput({ - type: "version-upload", - version: 1, - worker_name: props.name ?? null, - worker_tag: workerTag, - version_id: versionId, - preview_url: versionPreviewUrl, - preview_alias_url: versionPreviewAliasUrl, - wrangler_environment: args.env, - worker_name_overridden: workerNameOverridden, - }); } finally { metrics.sendMetricsEvent( "upload worker version", From 38b46238c57f0d85dc1334ce374ba709eab13749 Mon Sep 17 00:00:00 2001 From: ANT Bot <116369605+workers-devprod@users.noreply.github.com> Date: Thu, 20 Aug 2026 18:19:50 +0100 Subject: [PATCH 3/4] Version Packages (#15250) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/c3-frameworks-update-15263.md | 11 --- .changeset/c3-frameworks-update-15265.md | 11 --- .changeset/connect-handlers.md | 30 ------ .changeset/curly-codemods-juggle.md | 8 -- .changeset/cyan-planets-ring.md | 13 --- .changeset/deep-icons-appear.md | 7 -- .changeset/default-analytics-engine-name.md | 8 -- .changeset/dependabot-update-15260.md | 13 --- .changeset/dependabot-update-15264.md | 13 --- .changeset/dependabot-update-15277.md | 13 --- .changeset/fix-esm-only-package-resolution.md | 7 -- .changeset/inherit-r2-creds-config.md | 7 -- .changeset/keyring-partial-native-install.md | 9 -- .changeset/kv-bulk-put-local-base64-binary.md | 9 -- .changeset/preview-provision-parent-worker.md | 8 -- .changeset/quiet-output-helpers.md | 7 -- .../remove-miniflare-vitest-assets-flag.md | 7 -- .changeset/remove-workflow-remote-config.md | 8 -- .changeset/rename-vitest-plugin.md | 23 ----- .../reshape-miniflare-r2-s3-credentials.md | 7 -- .changeset/temporary-account-multi-auth.md | 10 -- .changeset/tidy-ravens-test.md | 5 - .changeset/workflows-instances-delete.md | 11 --- packages/autoconfig/CHANGELOG.md | 8 ++ packages/autoconfig/package.json | 2 +- packages/build-output-utils/CHANGELOG.md | 7 ++ packages/build-output-utils/package.json | 2 +- packages/cli/CHANGELOG.md | 7 ++ packages/cli/package.json | 2 +- packages/codemods/CHANGELOG.md | 9 ++ packages/codemods/package.json | 2 +- packages/config/CHANGELOG.md | 42 ++++++++ packages/config/package.json | 2 +- packages/create-cloudflare/CHANGELOG.md | 22 +++++ packages/create-cloudflare/package.json | 2 +- packages/deploy-helpers/CHANGELOG.md | 29 ++++++ packages/deploy-helpers/package.json | 2 +- packages/miniflare/CHANGELOG.md | 77 +++++++++++++++ packages/miniflare/package.json | 2 +- packages/pages-shared/CHANGELOG.md | 7 ++ packages/pages-shared/package.json | 2 +- packages/remote-bindings/CHANGELOG.md | 11 +++ packages/remote-bindings/package.json | 2 +- packages/runtime-types/CHANGELOG.md | 7 ++ packages/runtime-types/package.json | 2 +- packages/vite-plugin-cloudflare/CHANGELOG.md | 8 ++ packages/vite-plugin-cloudflare/package.json | 2 +- packages/vitest-plugin/CHANGELOG.md | 36 +++++++ packages/vitest-plugin/package.json | 2 +- packages/workers-auth/CHANGELOG.md | 19 ++++ packages/workers-auth/package.json | 2 +- packages/workers-utils/CHANGELOG.md | 44 +++++++++ packages/workers-utils/package.json | 2 +- packages/workflows-shared/CHANGELOG.md | 10 ++ packages/workflows-shared/package.json | 2 +- packages/wrangler/CHANGELOG.md | 96 +++++++++++++++++++ packages/wrangler/package.json | 2 +- 57 files changed, 456 insertions(+), 262 deletions(-) delete mode 100644 .changeset/c3-frameworks-update-15263.md delete mode 100644 .changeset/c3-frameworks-update-15265.md delete mode 100644 .changeset/connect-handlers.md delete mode 100644 .changeset/curly-codemods-juggle.md delete mode 100644 .changeset/cyan-planets-ring.md delete mode 100644 .changeset/deep-icons-appear.md delete mode 100644 .changeset/default-analytics-engine-name.md delete mode 100644 .changeset/dependabot-update-15260.md delete mode 100644 .changeset/dependabot-update-15264.md delete mode 100644 .changeset/dependabot-update-15277.md delete mode 100644 .changeset/fix-esm-only-package-resolution.md delete mode 100644 .changeset/inherit-r2-creds-config.md delete mode 100644 .changeset/keyring-partial-native-install.md delete mode 100644 .changeset/kv-bulk-put-local-base64-binary.md delete mode 100644 .changeset/preview-provision-parent-worker.md delete mode 100644 .changeset/quiet-output-helpers.md delete mode 100644 .changeset/remove-miniflare-vitest-assets-flag.md delete mode 100644 .changeset/remove-workflow-remote-config.md delete mode 100644 .changeset/rename-vitest-plugin.md delete mode 100644 .changeset/reshape-miniflare-r2-s3-credentials.md delete mode 100644 .changeset/temporary-account-multi-auth.md delete mode 100644 .changeset/tidy-ravens-test.md delete mode 100644 .changeset/workflows-instances-delete.md create mode 100644 packages/codemods/CHANGELOG.md diff --git a/.changeset/c3-frameworks-update-15263.md b/.changeset/c3-frameworks-update-15263.md deleted file mode 100644 index be15dc44081..00000000000 --- a/.changeset/c3-frameworks-update-15263.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"create-cloudflare": patch ---- - -Update dependencies of "create-cloudflare" - -The following dependency versions have been updated: - -| Dependency | From | To | -| ----------- | ------- | ------- | -| create-vike | 0.0.675 | 0.0.677 | diff --git a/.changeset/c3-frameworks-update-15265.md b/.changeset/c3-frameworks-update-15265.md deleted file mode 100644 index e72868b97a6..00000000000 --- a/.changeset/c3-frameworks-update-15265.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"create-cloudflare": patch ---- - -Update dependencies of "create-cloudflare" - -The following dependency versions have been updated: - -| Dependency | From | To | -| ------------ | ------ | ------ | -| create-solid | 0.10.0 | 0.11.0 | diff --git a/.changeset/connect-handlers.md b/.changeset/connect-handlers.md deleted file mode 100644 index 777ac38e65d..00000000000 --- a/.changeset/connect-handlers.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -"wrangler": minor -"miniflare": minor -"@cloudflare/workers-utils": minor -"@cloudflare/config": minor ---- - -Add `connect` trigger for raw sockets - -You can now configure a Worker to receive raw socket connections during `wrangler dev`, delivered directly to the Worker's `connect(socket, env, ctx)` handler: - -```jsonc -{ - "connect": [{ "protocol": "tcp", "port": 5432 }], -} -``` - -Each entry opens a listening socket on `127.0.0.1` (or the given `address`) that forwards incoming connections straight to the Worker, bypassing the local dev HTTP entry point. This requires the `experimental` compatibility flag. Only `"tcp"` is supported at the moment. - -`@cloudflare/config` also supports declaring this trigger via `triggers.connect(...)`, which lowers to the `connect` field above: - -```ts -import { defineWorker, triggers } from "@cloudflare/config"; - -export default defineWorker({ - triggers: [ - triggers.connect({ protocol: "tcp", port: 5432, address: "127.0.0.1" }), - ], -}); -``` diff --git a/.changeset/curly-codemods-juggle.md b/.changeset/curly-codemods-juggle.md deleted file mode 100644 index 524a116060f..00000000000 --- a/.changeset/curly-codemods-juggle.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@cloudflare/codemods": minor -"@cloudflare/vitest-plugin": minor ---- - -Add a central CLI for Cloudflare codemods - -Run a codemod by name, e.g. `npx @cloudflare/codemods vitest:v3-to-v4`. The initial migrations cover Vitest v3 to v4 configuration (`vitest:v3-to-v4`) and the `@cloudflare/vitest-pool-workers` to `@cloudflare/vitest-plugin` v1 rename (`vitest:pool-workers-to-vitest-plugin`). The existing Vitest transform now lives in this dedicated package. diff --git a/.changeset/cyan-planets-ring.md b/.changeset/cyan-planets-ring.md deleted file mode 100644 index 9181b321929..00000000000 --- a/.changeset/cyan-planets-ring.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@cloudflare/deploy-helpers": minor -"@cloudflare/workers-utils": minor -"wrangler": minor ---- - -Add container support to worker previews - -Worker previews now support containers through a new `previews.containers` configuration block. Container configuration doesn't inherit, so declare containers explicitly in the `previews` block to enable them for previews. This mirrors how `previews.durable_objects` works today. Wrangler names each preview container application `{worker_name}_{preview_slug}_{class_name}`, normalising and shortening the result to what the API accepts. Either change appends a short digest of the composed name, so two names that would otherwise land on one stay distinct. An entry cannot set its own `name`, because application names are unique to an account and a fixed name would collide between two previews of the same Worker. A Durable Object class is backed by at most one container application, so the validator rejects two entries that share a `class_name`. Wrangler skips container applications bound to Durable Object classes that another Worker implements through `script_name`, because the implementing Worker owns its own container application. A binding is not required: a Durable Object declared through `migrations` or `exports` and reached only over `ctx.exports` can still back a container. Every entry must set `class_name`. A `previews.containers` entry whose `class_name` matches no Durable Object class at all is rejected before the preview deployment is created, so a typo fails loudly instead of producing a preview with no container. - -Wrangler creates the container applications on `wrangler preview`. Deleting a preview tears them down server side, so `wrangler preview delete` doesn't remove them. - -Container build and deploy progress prints to stdout. `wrangler preview --json` suppresses wrangler's own output so it doesn't interleave with the payload, and warnings and errors still go to stderr. Docker's build output and the progress spinner write to stdout directly and bypass that suppression, so parse `--json` from a non interactive shell, where the spinner is skipped, and prefer a prebuilt `image` over a Dockerfile. diff --git a/.changeset/deep-icons-appear.md b/.changeset/deep-icons-appear.md deleted file mode 100644 index 7d17966ae32..00000000000 --- a/.changeset/deep-icons-appear.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@cloudflare/deploy-helpers": patch ---- - -Refactor triggers deploy validation and dry-run - -Move more logic into the deploy-helpers package for easy reuse by `cf`. diff --git a/.changeset/default-analytics-engine-name.md b/.changeset/default-analytics-engine-name.md deleted file mode 100644 index 8420786e288..00000000000 --- a/.changeset/default-analytics-engine-name.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@cloudflare/config": patch -"miniflare": patch ---- - -Default local Analytics Engine dataset names in Miniflare - -Analytics Engine dataset bindings without an explicit `name` now fallback to the worker and binding name as a default. diff --git a/.changeset/dependabot-update-15260.md b/.changeset/dependabot-update-15260.md deleted file mode 100644 index 8e152785ec3..00000000000 --- a/.changeset/dependabot-update-15260.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"miniflare": patch -"wrangler": patch ---- - -Update dependencies of "miniflare", "wrangler" - -The following dependency versions have been updated: - -| Dependency | From | To | -| ------------------------- | ------------- | ------------- | -| @cloudflare/workers-types | ^5.20260815.1 | ^5.20260816.1 | -| workerd | 1.20260815.1 | 1.20260816.1 | diff --git a/.changeset/dependabot-update-15264.md b/.changeset/dependabot-update-15264.md deleted file mode 100644 index 9803d3c2f8c..00000000000 --- a/.changeset/dependabot-update-15264.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"miniflare": patch -"wrangler": patch ---- - -Update dependencies of "miniflare", "wrangler" - -The following dependency versions have been updated: - -| Dependency | From | To | -| ------------------------- | ------------- | ------------- | -| @cloudflare/workers-types | ^5.20260816.1 | ^5.20260819.1 | -| workerd | 1.20260816.1 | 1.20260819.1 | diff --git a/.changeset/dependabot-update-15277.md b/.changeset/dependabot-update-15277.md deleted file mode 100644 index e0d74cd21e0..00000000000 --- a/.changeset/dependabot-update-15277.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"miniflare": patch -"wrangler": patch ---- - -Update dependencies of "miniflare", "wrangler" - -The following dependency versions have been updated: - -| Dependency | From | To | -| ------------------------- | ------------- | ------------- | -| @cloudflare/workers-types | ^5.20260819.1 | ^5.20260820.1 | -| workerd | 1.20260819.1 | 1.20260820.1 | diff --git a/.changeset/fix-esm-only-package-resolution.md b/.changeset/fix-esm-only-package-resolution.md deleted file mode 100644 index 43226692a84..00000000000 --- a/.changeset/fix-esm-only-package-resolution.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@cloudflare/workers-utils": patch ---- - -Fix ESM-only packages missing from deploy metadata - -ESM-only package dependencies (such as `@cloudflare/think`) were silently omitted from the package dependency metadata reported during `wrangler deploy` and `wrangler versions upload`. These packages are now correctly detected and included. diff --git a/.changeset/inherit-r2-creds-config.md b/.changeset/inherit-r2-creds-config.md deleted file mode 100644 index 122e4b6c7f1..00000000000 --- a/.changeset/inherit-r2-creds-config.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@cloudflare/config": minor ---- - -Add R2 local S3 credentials to the shared config binding shape - -R2 bindings now support `localDev.experimentalS3Credentials`, matching Wrangler's existing local S3 endpoint credentials configuration. diff --git a/.changeset/keyring-partial-native-install.md b/.changeset/keyring-partial-native-install.md deleted file mode 100644 index 446d8cd0fc6..00000000000 --- a/.changeset/keyring-partial-native-install.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@cloudflare/workers-auth": patch ---- - -Recover from a partially installed keyring backend on Windows - -Choosing to keep your credentials in the OS keyring on Windows installs a native backend the first time you opt in. An install interrupted part-way through — by a dropped connection, a full disk, or an npm told to skip optional packages — could leave a broken backend behind that was nonetheless treated as working. Every login, token refresh, and credential read from then on failed with an internal error, and because the broken state was never re-examined, no amount of retrying would clear it. - -A broken backend is now spotted and reinstalled automatically. If the reinstall still cannot produce a working one, you get a single explanation of how to install it by hand and fall back to the plaintext credentials file for the rest of the session, rather than sitting through a fresh install attempt on every credential access. diff --git a/.changeset/kv-bulk-put-local-base64-binary.md b/.changeset/kv-bulk-put-local-base64-binary.md deleted file mode 100644 index 82658594da4..00000000000 --- a/.changeset/kv-bulk-put-local-base64-binary.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"wrangler": patch ---- - -Fixes `kv bulk put` corrupting binary values written to local KV - -Values marked `base64: true` were stored incorrectly whenever they contained bytes that do not form valid UTF-8, which covers images, compressed data and most other binary payloads. A Worker reading such a key back under `wrangler dev` got a different, longer value than the one that was written: a 12 byte PNG header came back as 20 bytes. - -`kv bulk put` writes to local KV by default, so the plain command was the affected one. Remote writes were never affected, and neither were entries without `base64` or values written with `kv key put`. diff --git a/.changeset/preview-provision-parent-worker.md b/.changeset/preview-provision-parent-worker.md deleted file mode 100644 index 97f983a36b6..00000000000 --- a/.changeset/preview-provision-parent-worker.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@cloudflare/deploy-helpers": minor -"wrangler": minor ---- - -[private beta]: Create the parent Worker automatically when `wrangler preview` targets one that doesn't exist yet - -Previews hang off a parent Worker, so running `wrangler preview` before the Worker had ever been deployed failed with a raw API error naming the Preview endpoint. Wrangler now offers to create an empty parent Worker and then carries on creating the Preview. The parent uses the same workers.dev and Preview URL settings that `wrangler deploy` would resolve, without applying routes or cron triggers. In non-interactive environments, Wrangler creates the Worker without asking. diff --git a/.changeset/quiet-output-helpers.md b/.changeset/quiet-output-helpers.md deleted file mode 100644 index 5edd25de04c..00000000000 --- a/.changeset/quiet-output-helpers.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"wrangler": patch -"@cloudflare/deploy-helpers": patch -"@cloudflare/workers-utils": patch ---- - -Move deploy output writing into shared deploy helpers diff --git a/.changeset/remove-miniflare-vitest-assets-flag.md b/.changeset/remove-miniflare-vitest-assets-flag.md deleted file mode 100644 index 4dd292d4332..00000000000 --- a/.changeset/remove-miniflare-vitest-assets-flag.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"miniflare": major ---- - -Remove the deprecated `hasAssetsAndIsVitest` option - -This internal assets testing option is no longer supported. diff --git a/.changeset/remove-workflow-remote-config.md b/.changeset/remove-workflow-remote-config.md deleted file mode 100644 index cf0e03b3e11..00000000000 --- a/.changeset/remove-workflow-remote-config.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"wrangler": patch -"@cloudflare/config": patch ---- - -Remove unsupported `remote` configuration from Workflow bindings - -Workflow bindings no longer accept `remote` in configuration, as remote Workflow bindings have never actually been supported. diff --git a/.changeset/rename-vitest-plugin.md b/.changeset/rename-vitest-plugin.md deleted file mode 100644 index d83e1860204..00000000000 --- a/.changeset/rename-vitest-plugin.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -"@cloudflare/vitest-plugin": major ---- - -Rename `@cloudflare/vitest-pool-workers` to `@cloudflare/vitest-plugin` for the v1 release - -The package has been renamed from `@cloudflare/vitest-pool-workers` to `@cloudflare/vitest-plugin` to be clearer about it's usage. - -To migrate, run the codemod from the root of your project: - -```sh -npx @cloudflare/codemods vitest:pool-workers-to-vitest-plugin -``` - -This handles the whole rename for you: - -- Replaces the dependency in your `package.json`, moving plain version ranges to `^1.0.0` while preserving `workspace:`/`catalog:`/`link:`/`file:` protocol references, along with any `overrides`, `resolutions` and `pnpm.overrides` entries. -- Rewrites imports such as `import { cloudflareTest } from "@cloudflare/vitest-pool-workers"` to `import { cloudflareTest } from "@cloudflare/vitest-plugin"`, preserving any subpaths. -- Updates the `types` entry in your test `tsconfig.json` from `@cloudflare/vitest-pool-workers/types` to `@cloudflare/vitest-plugin/types`. - -Pass `--dry-run` to preview the changes first, or `--files ` to restrict which files are considered. - -If you would rather migrate by hand, the only changes needed are the dependency name in `package.json`, the package specifier in any `import`/`require` of the plugin, and the `types` entry in your test `tsconfig.json`. diff --git a/.changeset/reshape-miniflare-r2-s3-credentials.md b/.changeset/reshape-miniflare-r2-s3-credentials.md deleted file mode 100644 index cedee802224..00000000000 --- a/.changeset/reshape-miniflare-r2-s3-credentials.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"miniflare": major ---- - -Change R2 local S3 credentials configuration - -R2 bindings now use `localDev.experimentalS3Credentials` instead of `s3Credentials` for local S3 endpoint credentials. diff --git a/.changeset/temporary-account-multi-auth.md b/.changeset/temporary-account-multi-auth.md deleted file mode 100644 index debae728c4c..00000000000 --- a/.changeset/temporary-account-multi-auth.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@cloudflare/workers-auth": patch -"wrangler": patch ---- - -Fix the `--temporary` error on commands that authenticate more than one time - -`wrangler d1 migrations apply --remote --temporary` failed with this error: `You're already authenticated with Cloudflare, so --temporary can't be used`. The failure occurred with no login and with no `CLOUDFLARE_API_TOKEN`. This command authenticates one time for each statement that it runs. The first authentication makes a temporary preview account. The second authentication read the token of this new account as an earlier login. - -Wrangler now uses again the temporary account from the same command run. Commands that authenticate more than one time now work as `wrangler deploy --temporary` works. If real credentials are available, `--temporary` is still an error. diff --git a/.changeset/tidy-ravens-test.md b/.changeset/tidy-ravens-test.md deleted file mode 100644 index ea786aafe4f..00000000000 --- a/.changeset/tidy-ravens-test.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-cloudflare": patch ---- - -Update Hello World templates to use `@cloudflare/vitest-pool-workers` 0.22 diff --git a/.changeset/workflows-instances-delete.md b/.changeset/workflows-instances-delete.md deleted file mode 100644 index 90ab8cf6ec8..00000000000 --- a/.changeset/workflows-instances-delete.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@cloudflare/workflows-shared": minor -"wrangler": minor -"miniflare": minor ---- - -Add individual and batch Workflow instance deletion to the runtime and SDK. - -- `WorkflowInstance.delete()` deletes one instance. Self-deletion stops the current execution. -- `env.MY_WORKFLOW.deleteBatch(instanceIds)` deletes up to 100 instances and returns `{ deleted, errors }` per input position. -- `wrangler workflows instances delete [id..]` deletes instances remotely or with `--local`; IDs can also come from a JSON array passed with `--filename`, with a combined limit of 100. diff --git a/packages/autoconfig/CHANGELOG.md b/packages/autoconfig/CHANGELOG.md index 287512528ee..0b59697de76 100644 --- a/packages/autoconfig/CHANGELOG.md +++ b/packages/autoconfig/CHANGELOG.md @@ -1,5 +1,13 @@ # @cloudflare/autoconfig +## 0.2.9 + +### Patch Changes + +- Updated dependencies [[`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae), [`c68f9cb`](https://github.com/cloudflare/workers-sdk/commit/c68f9cb866a2eae4416d20f584f733527189f18a), [`5c10e39`](https://github.com/cloudflare/workers-sdk/commit/5c10e398979c0a054f58dcf2751012cc99e977d2), [`39dcea6`](https://github.com/cloudflare/workers-sdk/commit/39dcea6c9362e2d651e3108fa769dbbc32db5a7b)]: + - @cloudflare/workers-utils@0.34.0 + - @cloudflare/cli-shared-helpers@0.1.25 + ## 0.2.8 ### Patch Changes diff --git a/packages/autoconfig/package.json b/packages/autoconfig/package.json index 09921015e43..a34b2972819 100644 --- a/packages/autoconfig/package.json +++ b/packages/autoconfig/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/autoconfig", - "version": "0.2.8", + "version": "0.2.9", "description": "Framework autoconfig detection and configuration for Cloudflare Workers", "license": "MIT OR Apache-2.0", "repository": { diff --git a/packages/build-output-utils/CHANGELOG.md b/packages/build-output-utils/CHANGELOG.md index 95ff84a5647..5ab4cc4018c 100644 --- a/packages/build-output-utils/CHANGELOG.md +++ b/packages/build-output-utils/CHANGELOG.md @@ -1,5 +1,12 @@ # @cloudflare/build-output-utils +## 0.1.3 + +### Patch Changes + +- Updated dependencies [[`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864)]: + - @cloudflare/config@0.7.0 + ## 0.1.2 ### Patch Changes diff --git a/packages/build-output-utils/package.json b/packages/build-output-utils/package.json index e181b9f6e5a..ef83e5d5000 100644 --- a/packages/build-output-utils/package.json +++ b/packages/build-output-utils/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/build-output-utils", - "version": "0.1.2", + "version": "0.1.3", "description": "Utilities for reading and writing the Build Output Specification directory. This is not yet stable enough for external use — APIs may change without notice.", "homepage": "https://github.com/cloudflare/workers-sdk/tree/main/packages/build-output-utils#readme", "bugs": { diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 3adf856c259..d54ca6f19e6 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,12 @@ # @cloudflare/cli +## 0.1.25 + +### Patch Changes + +- Updated dependencies [[`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae), [`c68f9cb`](https://github.com/cloudflare/workers-sdk/commit/c68f9cb866a2eae4416d20f584f733527189f18a), [`5c10e39`](https://github.com/cloudflare/workers-sdk/commit/5c10e398979c0a054f58dcf2751012cc99e977d2), [`39dcea6`](https://github.com/cloudflare/workers-sdk/commit/39dcea6c9362e2d651e3108fa769dbbc32db5a7b)]: + - @cloudflare/workers-utils@0.34.0 + ## 0.1.24 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 55f2ff21a75..be076d1df9d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/cli-shared-helpers", - "version": "0.1.24", + "version": "0.1.25", "description": "Internal shared CLI helpers for workers-sdk. Not intended for external use — APIs may change without notice.", "keywords": [ "cli", diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md new file mode 100644 index 00000000000..ab6d0f35114 --- /dev/null +++ b/packages/codemods/CHANGELOG.md @@ -0,0 +1,9 @@ +# @cloudflare/codemods + +## 0.1.0 + +### Minor Changes + +- [#14690](https://github.com/cloudflare/workers-sdk/pull/14690) [`d81fae7`](https://github.com/cloudflare/workers-sdk/commit/d81fae7487abee539d985c348dddf39bca3196f7) Thanks [@penalosa](https://github.com/penalosa)! - Add a central CLI for Cloudflare codemods + + Run a codemod by name, e.g. `npx @cloudflare/codemods vitest:v3-to-v4`. The initial migrations cover Vitest v3 to v4 configuration (`vitest:v3-to-v4`) and the `@cloudflare/vitest-pool-workers` to `@cloudflare/vitest-plugin` v1 rename (`vitest:pool-workers-to-vitest-plugin`). The existing Vitest transform now lives in this dedicated package. diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 770f588f6aa..38a391ab525 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/codemods", - "version": "0.0.0", + "version": "0.1.0", "description": "Codemods for upgrading Cloudflare developer projects", "keywords": [ "cloudflare", diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index 572458dd208..8ab0e678a6a 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,47 @@ # @cloudflare/config +## 0.7.0 + +### Minor Changes + +- [#14995](https://github.com/cloudflare/workers-sdk/pull/14995) [`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae) Thanks [@ThomasRubini](https://github.com/ThomasRubini)! - Add `connect` trigger for raw sockets + + You can now configure a Worker to receive raw socket connections during `wrangler dev`, delivered directly to the Worker's `connect(socket, env, ctx)` handler: + + ```jsonc + { + "connect": [{ "protocol": "tcp", "port": 5432 }] + } + ``` + + Each entry opens a listening socket on `127.0.0.1` (or the given `address`) that forwards incoming connections straight to the Worker, bypassing the local dev HTTP entry point. This requires the `experimental` compatibility flag. Only `"tcp"` is supported at the moment. + + `@cloudflare/config` also supports declaring this trigger via `triggers.connect(...)`, which lowers to the `connect` field above: + + ```ts + import { defineWorker, triggers } from "@cloudflare/config"; + + export default defineWorker({ + triggers: [ + triggers.connect({ protocol: "tcp", port: 5432, address: "127.0.0.1" }), + ], + }); + ``` + +- [#15130](https://github.com/cloudflare/workers-sdk/pull/15130) [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864) Thanks [@emily-shen](https://github.com/emily-shen)! - Add R2 local S3 credentials to the shared config binding shape + + R2 bindings now support `localDev.experimentalS3Credentials`, matching Wrangler's existing local S3 endpoint credentials configuration. + +### Patch Changes + +- [#15130](https://github.com/cloudflare/workers-sdk/pull/15130) [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864) Thanks [@emily-shen](https://github.com/emily-shen)! - Default local Analytics Engine dataset names in Miniflare + + Analytics Engine dataset bindings without an explicit `name` now fallback to the worker and binding name as a default. + +- [#15130](https://github.com/cloudflare/workers-sdk/pull/15130) [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864) Thanks [@emily-shen](https://github.com/emily-shen)! - Remove unsupported `remote` configuration from Workflow bindings + + Workflow bindings no longer accept `remote` in configuration, as remote Workflow bindings have never actually been supported. + ## 0.6.0 ### Minor Changes diff --git a/packages/config/package.json b/packages/config/package.json index 45066252e92..50b9cb125ab 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/config", - "version": "0.6.0", + "version": "0.7.0", "description": "Definitions and utilities for configuring Cloudflare Workers and related products. This is not yet stable enough for external use — APIs may change without notice.", "homepage": "https://github.com/cloudflare/workers-sdk/tree/main/packages/config#readme", "bugs": { diff --git a/packages/create-cloudflare/CHANGELOG.md b/packages/create-cloudflare/CHANGELOG.md index 833f76330f8..ed9437624cc 100644 --- a/packages/create-cloudflare/CHANGELOG.md +++ b/packages/create-cloudflare/CHANGELOG.md @@ -1,5 +1,27 @@ # create-cloudflare +## 2.72.2 + +### Patch Changes + +- [#15263](https://github.com/cloudflare/workers-sdk/pull/15263) [`29b8138`](https://github.com/cloudflare/workers-sdk/commit/29b813819ec66d06c6a4c734667eba85ccea6050) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update dependencies of "create-cloudflare" + + The following dependency versions have been updated: + + | Dependency | From | To | + | ----------- | ------- | ------- | + | create-vike | 0.0.675 | 0.0.677 | + +- [#15265](https://github.com/cloudflare/workers-sdk/pull/15265) [`0854f04`](https://github.com/cloudflare/workers-sdk/commit/0854f04d7f746a2294b37288a7db49a450a9756f) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update dependencies of "create-cloudflare" + + The following dependency versions have been updated: + + | Dependency | From | To | + | ------------ | ------ | ------ | + | create-solid | 0.10.0 | 0.11.0 | + +- [#15264](https://github.com/cloudflare/workers-sdk/pull/15264) [`4b52975`](https://github.com/cloudflare/workers-sdk/commit/4b52975aac295c8483d6b4001d0b50945293265a) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update Hello World templates to use `@cloudflare/vitest-pool-workers` 0.22 + ## 2.72.1 ### Patch Changes diff --git a/packages/create-cloudflare/package.json b/packages/create-cloudflare/package.json index 9185c1da613..010690edc5a 100644 --- a/packages/create-cloudflare/package.json +++ b/packages/create-cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "create-cloudflare", - "version": "2.72.1", + "version": "2.72.2", "description": "A CLI for creating and deploying new applications to Cloudflare.", "keywords": [ "cloudflare", diff --git a/packages/deploy-helpers/CHANGELOG.md b/packages/deploy-helpers/CHANGELOG.md index 94fdbb59041..6b2ef4b2091 100644 --- a/packages/deploy-helpers/CHANGELOG.md +++ b/packages/deploy-helpers/CHANGELOG.md @@ -1,5 +1,34 @@ # @cloudflare/deploy-helpers +## 0.8.0 + +### Minor Changes + +- [#15172](https://github.com/cloudflare/workers-sdk/pull/15172) [`c68f9cb`](https://github.com/cloudflare/workers-sdk/commit/c68f9cb866a2eae4416d20f584f733527189f18a) Thanks [@WillTaylorDev](https://github.com/WillTaylorDev)! - Add container support to worker previews + + Worker previews now support containers through a new `previews.containers` configuration block. Container configuration doesn't inherit, so declare containers explicitly in the `previews` block to enable them for previews. This mirrors how `previews.durable_objects` works today. Wrangler names each preview container application `{worker_name}_{preview_slug}_{class_name}`, normalising and shortening the result to what the API accepts. Either change appends a short digest of the composed name, so two names that would otherwise land on one stay distinct. An entry cannot set its own `name`, because application names are unique to an account and a fixed name would collide between two previews of the same Worker. A Durable Object class is backed by at most one container application, so the validator rejects two entries that share a `class_name`. Wrangler skips container applications bound to Durable Object classes that another Worker implements through `script_name`, because the implementing Worker owns its own container application. A binding is not required: a Durable Object declared through `migrations` or `exports` and reached only over `ctx.exports` can still back a container. Every entry must set `class_name`. A `previews.containers` entry whose `class_name` matches no Durable Object class at all is rejected before the preview deployment is created, so a typo fails loudly instead of producing a preview with no container. + + Wrangler creates the container applications on `wrangler preview`. Deleting a preview tears them down server side, so `wrangler preview delete` doesn't remove them. + + Container build and deploy progress prints to stdout. `wrangler preview --json` suppresses wrangler's own output so it doesn't interleave with the payload, and warnings and errors still go to stderr. Docker's build output and the progress spinner write to stdout directly and bypass that suppression, so parse `--json` from a non interactive shell, where the spinner is skipped, and prefer a prebuilt `image` over a Dockerfile. + +- [#15174](https://github.com/cloudflare/workers-sdk/pull/15174) [`649f667`](https://github.com/cloudflare/workers-sdk/commit/649f667bd871061da945881ce953ef8f81caea1a) Thanks [@WillTaylorDev](https://github.com/WillTaylorDev)! - [private beta]: Create the parent Worker automatically when `wrangler preview` targets one that doesn't exist yet + + Previews hang off a parent Worker, so running `wrangler preview` before the Worker had ever been deployed failed with a raw API error naming the Preview endpoint. Wrangler now offers to create an empty parent Worker and then carries on creating the Preview. The parent uses the same workers.dev and Preview URL settings that `wrangler deploy` would resolve, without applying routes or cron triggers. In non-interactive environments, Wrangler creates the Worker without asking. + +### Patch Changes + +- [#15253](https://github.com/cloudflare/workers-sdk/pull/15253) [`630048b`](https://github.com/cloudflare/workers-sdk/commit/630048b3d8a9e8f76b57be23f81743a3fb7de177) Thanks [@emily-shen](https://github.com/emily-shen)! - Refactor triggers deploy validation and dry-run + + Move more logic into the deploy-helpers package for easy reuse by `cf`. + +- [#15284](https://github.com/cloudflare/workers-sdk/pull/15284) [`39dcea6`](https://github.com/cloudflare/workers-sdk/commit/39dcea6c9362e2d651e3108fa769dbbc32db5a7b) Thanks [@emily-shen](https://github.com/emily-shen)! - Move deploy output writing into shared deploy helpers + +- Updated dependencies [[`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae), [`c68f9cb`](https://github.com/cloudflare/workers-sdk/commit/c68f9cb866a2eae4416d20f584f733527189f18a), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`5ae9d5b`](https://github.com/cloudflare/workers-sdk/commit/5ae9d5b205fea31516559f7ad89a21eda671af2f), [`4b52975`](https://github.com/cloudflare/workers-sdk/commit/4b52975aac295c8483d6b4001d0b50945293265a), [`ce9b151`](https://github.com/cloudflare/workers-sdk/commit/ce9b1510abf5c1152aedc94456f4d7ffe9402248), [`5c10e39`](https://github.com/cloudflare/workers-sdk/commit/5c10e398979c0a054f58dcf2751012cc99e977d2), [`39dcea6`](https://github.com/cloudflare/workers-sdk/commit/39dcea6c9362e2d651e3108fa769dbbc32db5a7b), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`30c2d47`](https://github.com/cloudflare/workers-sdk/commit/30c2d47965c51350aca6b2c70db8fc6496bdaa17)]: + - miniflare@5.20260820.0-alpha + - @cloudflare/workers-utils@0.34.0 + - @cloudflare/cli-shared-helpers@0.1.25 + ## 0.7.1 ### Patch Changes diff --git a/packages/deploy-helpers/package.json b/packages/deploy-helpers/package.json index 39a6023e2a9..f0a79be1c63 100644 --- a/packages/deploy-helpers/package.json +++ b/packages/deploy-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/deploy-helpers", - "version": "0.7.1", + "version": "0.8.0", "description": "Internal deploy helpers for workers-sdk. Not intended for external use — APIs may change without notice.", "homepage": "https://github.com/cloudflare/workers-sdk/tree/main/packages/deploy-helpers#readme", "bugs": { diff --git a/packages/miniflare/CHANGELOG.md b/packages/miniflare/CHANGELOG.md index 265cfae1aa9..03d461dee55 100644 --- a/packages/miniflare/CHANGELOG.md +++ b/packages/miniflare/CHANGELOG.md @@ -1,5 +1,82 @@ # miniflare +## 5.20260820.0-alpha + +### Major Changes + +- [#15130](https://github.com/cloudflare/workers-sdk/pull/15130) [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864) Thanks [@emily-shen](https://github.com/emily-shen)! - Remove the deprecated `hasAssetsAndIsVitest` option + + This internal assets testing option is no longer supported. + +- [#15130](https://github.com/cloudflare/workers-sdk/pull/15130) [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864) Thanks [@emily-shen](https://github.com/emily-shen)! - Change R2 local S3 credentials configuration + + R2 bindings now use `localDev.experimentalS3Credentials` instead of `s3Credentials` for local S3 endpoint credentials. + +### Minor Changes + +- [#14995](https://github.com/cloudflare/workers-sdk/pull/14995) [`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae) Thanks [@ThomasRubini](https://github.com/ThomasRubini)! - Add `connect` trigger for raw sockets + + You can now configure a Worker to receive raw socket connections during `wrangler dev`, delivered directly to the Worker's `connect(socket, env, ctx)` handler: + + ```jsonc + { + "connect": [{ "protocol": "tcp", "port": 5432 }] + } + ``` + + Each entry opens a listening socket on `127.0.0.1` (or the given `address`) that forwards incoming connections straight to the Worker, bypassing the local dev HTTP entry point. This requires the `experimental` compatibility flag. Only `"tcp"` is supported at the moment. + + `@cloudflare/config` also supports declaring this trigger via `triggers.connect(...)`, which lowers to the `connect` field above: + + ```ts + import { defineWorker, triggers } from "@cloudflare/config"; + + export default defineWorker({ + triggers: [ + triggers.connect({ protocol: "tcp", port: 5432, address: "127.0.0.1" }), + ], + }); + ``` + +- [#14735](https://github.com/cloudflare/workers-sdk/pull/14735) [`30c2d47`](https://github.com/cloudflare/workers-sdk/commit/30c2d47965c51350aca6b2c70db8fc6496bdaa17) Thanks [@vaishnav-mk](https://github.com/vaishnav-mk)! - Add individual and batch Workflow instance deletion to the runtime and SDK. + + - `WorkflowInstance.delete()` deletes one instance. Self-deletion stops the current execution. + - `env.MY_WORKFLOW.deleteBatch(instanceIds)` deletes up to 100 instances and returns `{ deleted, errors }` per input position. + - `wrangler workflows instances delete [id..]` deletes instances remotely or with `--local`; IDs can also come from a JSON array passed with `--filename`, with a combined limit of 100. + +### Patch Changes + +- [#15130](https://github.com/cloudflare/workers-sdk/pull/15130) [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864) Thanks [@emily-shen](https://github.com/emily-shen)! - Default local Analytics Engine dataset names in Miniflare + + Analytics Engine dataset bindings without an explicit `name` now fallback to the worker and binding name as a default. + +- [#15260](https://github.com/cloudflare/workers-sdk/pull/15260) [`5ae9d5b`](https://github.com/cloudflare/workers-sdk/commit/5ae9d5b205fea31516559f7ad89a21eda671af2f) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update dependencies of "miniflare", "wrangler" + + The following dependency versions have been updated: + + | Dependency | From | To | + | ------------------------- | ------------- | ------------- | + | @cloudflare/workers-types | ^5.20260815.1 | ^5.20260816.1 | + | workerd | 1.20260815.1 | 1.20260816.1 | + +- [#15264](https://github.com/cloudflare/workers-sdk/pull/15264) [`4b52975`](https://github.com/cloudflare/workers-sdk/commit/4b52975aac295c8483d6b4001d0b50945293265a) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update dependencies of "miniflare", "wrangler" + + The following dependency versions have been updated: + + | Dependency | From | To | + | ------------------------- | ------------- | ------------- | + | @cloudflare/workers-types | ^5.20260816.1 | ^5.20260819.1 | + | workerd | 1.20260816.1 | 1.20260819.1 | + +- [#15277](https://github.com/cloudflare/workers-sdk/pull/15277) [`ce9b151`](https://github.com/cloudflare/workers-sdk/commit/ce9b1510abf5c1152aedc94456f4d7ffe9402248) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update dependencies of "miniflare", "wrangler" + + The following dependency versions have been updated: + + | Dependency | From | To | + | ------------------------- | ------------- | ------------- | + | @cloudflare/workers-types | ^5.20260819.1 | ^5.20260820.1 | + | workerd | 1.20260819.1 | 1.20260820.1 | + ## 5.20260815.0-alpha ### Patch Changes diff --git a/packages/miniflare/package.json b/packages/miniflare/package.json index 1319edf67ec..18300c4b914 100644 --- a/packages/miniflare/package.json +++ b/packages/miniflare/package.json @@ -1,6 +1,6 @@ { "name": "miniflare", - "version": "5.20260815.0-alpha", + "version": "5.20260820.0-alpha", "description": "Fun, full-featured, fully-local simulator for Cloudflare Workers", "keywords": [ "cloudflare", diff --git a/packages/pages-shared/CHANGELOG.md b/packages/pages-shared/CHANGELOG.md index f515d0624b5..f562b98d2fd 100644 --- a/packages/pages-shared/CHANGELOG.md +++ b/packages/pages-shared/CHANGELOG.md @@ -1,5 +1,12 @@ # @cloudflare/pages-shared +## 0.13.171 + +### Patch Changes + +- Updated dependencies [[`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`5ae9d5b`](https://github.com/cloudflare/workers-sdk/commit/5ae9d5b205fea31516559f7ad89a21eda671af2f), [`4b52975`](https://github.com/cloudflare/workers-sdk/commit/4b52975aac295c8483d6b4001d0b50945293265a), [`ce9b151`](https://github.com/cloudflare/workers-sdk/commit/ce9b1510abf5c1152aedc94456f4d7ffe9402248), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`30c2d47`](https://github.com/cloudflare/workers-sdk/commit/30c2d47965c51350aca6b2c70db8fc6496bdaa17)]: + - miniflare@5.20260820.0-alpha + ## 0.13.170 ### Patch Changes diff --git a/packages/pages-shared/package.json b/packages/pages-shared/package.json index 8efc0f844cf..a6f69363a6e 100644 --- a/packages/pages-shared/package.json +++ b/packages/pages-shared/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/pages-shared", - "version": "0.13.170", + "version": "0.13.171", "repository": { "type": "git", "url": "https://github.com/cloudflare/workers-sdk.git", diff --git a/packages/remote-bindings/CHANGELOG.md b/packages/remote-bindings/CHANGELOG.md index 625ac6e7130..b6eae155d6b 100644 --- a/packages/remote-bindings/CHANGELOG.md +++ b/packages/remote-bindings/CHANGELOG.md @@ -1,5 +1,16 @@ # @cloudflare/remote-bindings +## 0.0.14 + +### Patch Changes + +- Updated dependencies [[`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae), [`c68f9cb`](https://github.com/cloudflare/workers-sdk/commit/c68f9cb866a2eae4416d20f584f733527189f18a), [`630048b`](https://github.com/cloudflare/workers-sdk/commit/630048b3d8a9e8f76b57be23f81743a3fb7de177), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`5ae9d5b`](https://github.com/cloudflare/workers-sdk/commit/5ae9d5b205fea31516559f7ad89a21eda671af2f), [`4b52975`](https://github.com/cloudflare/workers-sdk/commit/4b52975aac295c8483d6b4001d0b50945293265a), [`ce9b151`](https://github.com/cloudflare/workers-sdk/commit/ce9b1510abf5c1152aedc94456f4d7ffe9402248), [`5c10e39`](https://github.com/cloudflare/workers-sdk/commit/5c10e398979c0a054f58dcf2751012cc99e977d2), [`8a04946`](https://github.com/cloudflare/workers-sdk/commit/8a04946b4b1ac41d1fbde7badc2615130b9c1544), [`649f667`](https://github.com/cloudflare/workers-sdk/commit/649f667bd871061da945881ce953ef8f81caea1a), [`39dcea6`](https://github.com/cloudflare/workers-sdk/commit/39dcea6c9362e2d651e3108fa769dbbc32db5a7b), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`f2437e6`](https://github.com/cloudflare/workers-sdk/commit/f2437e606fc69891009285831d94b49bf44f6aff), [`30c2d47`](https://github.com/cloudflare/workers-sdk/commit/30c2d47965c51350aca6b2c70db8fc6496bdaa17)]: + - miniflare@5.20260820.0-alpha + - @cloudflare/workers-utils@0.34.0 + - @cloudflare/deploy-helpers@0.8.0 + - @cloudflare/workers-auth@0.6.5 + - @cloudflare/cli-shared-helpers@0.1.25 + ## 0.0.13 ### Patch Changes diff --git a/packages/remote-bindings/package.json b/packages/remote-bindings/package.json index cb5c588b3ff..f54cc4c01e9 100644 --- a/packages/remote-bindings/package.json +++ b/packages/remote-bindings/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/remote-bindings", - "version": "0.0.13", + "version": "0.0.14", "private": true, "homepage": "https://github.com/cloudflare/workers-sdk/tree/main/packages/remote-bindings#readme", "bugs": { diff --git a/packages/runtime-types/CHANGELOG.md b/packages/runtime-types/CHANGELOG.md index 495f1339843..3535fdc791c 100644 --- a/packages/runtime-types/CHANGELOG.md +++ b/packages/runtime-types/CHANGELOG.md @@ -1,5 +1,12 @@ # @cloudflare/runtime-types +## 0.0.16 + +### Patch Changes + +- Updated dependencies [[`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`5ae9d5b`](https://github.com/cloudflare/workers-sdk/commit/5ae9d5b205fea31516559f7ad89a21eda671af2f), [`4b52975`](https://github.com/cloudflare/workers-sdk/commit/4b52975aac295c8483d6b4001d0b50945293265a), [`ce9b151`](https://github.com/cloudflare/workers-sdk/commit/ce9b1510abf5c1152aedc94456f4d7ffe9402248), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`30c2d47`](https://github.com/cloudflare/workers-sdk/commit/30c2d47965c51350aca6b2c70db8fc6496bdaa17)]: + - miniflare@5.20260820.0-alpha + ## 0.0.15 ### Patch Changes diff --git a/packages/runtime-types/package.json b/packages/runtime-types/package.json index 1067715f779..47c496f6dda 100644 --- a/packages/runtime-types/package.json +++ b/packages/runtime-types/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/runtime-types", - "version": "0.0.15", + "version": "0.0.16", "private": true, "homepage": "https://github.com/cloudflare/workers-sdk/tree/main/packages/runtime-types#readme", "bugs": { diff --git a/packages/vite-plugin-cloudflare/CHANGELOG.md b/packages/vite-plugin-cloudflare/CHANGELOG.md index 1aced1665ba..fc83e185966 100644 --- a/packages/vite-plugin-cloudflare/CHANGELOG.md +++ b/packages/vite-plugin-cloudflare/CHANGELOG.md @@ -1,5 +1,13 @@ # @cloudflare/vite-plugin +## 1.53.1 + +### Patch Changes + +- Updated dependencies [[`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae), [`c68f9cb`](https://github.com/cloudflare/workers-sdk/commit/c68f9cb866a2eae4416d20f584f733527189f18a), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`5ae9d5b`](https://github.com/cloudflare/workers-sdk/commit/5ae9d5b205fea31516559f7ad89a21eda671af2f), [`4b52975`](https://github.com/cloudflare/workers-sdk/commit/4b52975aac295c8483d6b4001d0b50945293265a), [`ce9b151`](https://github.com/cloudflare/workers-sdk/commit/ce9b1510abf5c1152aedc94456f4d7ffe9402248), [`ef73a28`](https://github.com/cloudflare/workers-sdk/commit/ef73a28c1e7a208d730c6de64566bc96f683ca7b), [`649f667`](https://github.com/cloudflare/workers-sdk/commit/649f667bd871061da945881ce953ef8f81caea1a), [`39dcea6`](https://github.com/cloudflare/workers-sdk/commit/39dcea6c9362e2d651e3108fa769dbbc32db5a7b), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`f2437e6`](https://github.com/cloudflare/workers-sdk/commit/f2437e606fc69891009285831d94b49bf44f6aff), [`30c2d47`](https://github.com/cloudflare/workers-sdk/commit/30c2d47965c51350aca6b2c70db8fc6496bdaa17)]: + - wrangler@4.125.0 + - miniflare@5.20260820.0-alpha + ## 1.53.0 ### Minor Changes diff --git a/packages/vite-plugin-cloudflare/package.json b/packages/vite-plugin-cloudflare/package.json index edfce18d51f..e2ad6c5a84b 100644 --- a/packages/vite-plugin-cloudflare/package.json +++ b/packages/vite-plugin-cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/vite-plugin", - "version": "1.53.0", + "version": "1.53.1", "description": "Cloudflare plugin for Vite", "keywords": [ "cloudflare", diff --git a/packages/vitest-plugin/CHANGELOG.md b/packages/vitest-plugin/CHANGELOG.md index 0b365e51414..c2bacd59ec8 100644 --- a/packages/vitest-plugin/CHANGELOG.md +++ b/packages/vitest-plugin/CHANGELOG.md @@ -1,5 +1,41 @@ # @cloudflare/vitest-pool-workers +## 1.0.0 + +### Major Changes + +- [#15074](https://github.com/cloudflare/workers-sdk/pull/15074) [`bfcc442`](https://github.com/cloudflare/workers-sdk/commit/bfcc4423fa5d2dc007ebdc41293bd68f863777f6) Thanks [@penalosa](https://github.com/penalosa)! - Rename `@cloudflare/vitest-pool-workers` to `@cloudflare/vitest-plugin` for the v1 release + + The package has been renamed from `@cloudflare/vitest-pool-workers` to `@cloudflare/vitest-plugin` to be clearer about it's usage. + + To migrate, run the codemod from the root of your project: + + ```sh + npx @cloudflare/codemods vitest:pool-workers-to-vitest-plugin + ``` + + This handles the whole rename for you: + + - Replaces the dependency in your `package.json`, moving plain version ranges to `^1.0.0` while preserving `workspace:`/`catalog:`/`link:`/`file:` protocol references, along with any `overrides`, `resolutions` and `pnpm.overrides` entries. + - Rewrites imports such as `import { cloudflareTest } from "@cloudflare/vitest-pool-workers"` to `import { cloudflareTest } from "@cloudflare/vitest-plugin"`, preserving any subpaths. + - Updates the `types` entry in your test `tsconfig.json` from `@cloudflare/vitest-pool-workers/types` to `@cloudflare/vitest-plugin/types`. + + Pass `--dry-run` to preview the changes first, or `--files ` to restrict which files are considered. + + If you would rather migrate by hand, the only changes needed are the dependency name in `package.json`, the package specifier in any `import`/`require` of the plugin, and the `types` entry in your test `tsconfig.json`. + +### Minor Changes + +- [#14690](https://github.com/cloudflare/workers-sdk/pull/14690) [`d81fae7`](https://github.com/cloudflare/workers-sdk/commit/d81fae7487abee539d985c348dddf39bca3196f7) Thanks [@penalosa](https://github.com/penalosa)! - Add a central CLI for Cloudflare codemods + + Run a codemod by name, e.g. `npx @cloudflare/codemods vitest:v3-to-v4`. The initial migrations cover Vitest v3 to v4 configuration (`vitest:v3-to-v4`) and the `@cloudflare/vitest-pool-workers` to `@cloudflare/vitest-plugin` v1 rename (`vitest:pool-workers-to-vitest-plugin`). The existing Vitest transform now lives in this dedicated package. + +### Patch Changes + +- Updated dependencies [[`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae), [`c68f9cb`](https://github.com/cloudflare/workers-sdk/commit/c68f9cb866a2eae4416d20f584f733527189f18a), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`5ae9d5b`](https://github.com/cloudflare/workers-sdk/commit/5ae9d5b205fea31516559f7ad89a21eda671af2f), [`4b52975`](https://github.com/cloudflare/workers-sdk/commit/4b52975aac295c8483d6b4001d0b50945293265a), [`ce9b151`](https://github.com/cloudflare/workers-sdk/commit/ce9b1510abf5c1152aedc94456f4d7ffe9402248), [`ef73a28`](https://github.com/cloudflare/workers-sdk/commit/ef73a28c1e7a208d730c6de64566bc96f683ca7b), [`649f667`](https://github.com/cloudflare/workers-sdk/commit/649f667bd871061da945881ce953ef8f81caea1a), [`39dcea6`](https://github.com/cloudflare/workers-sdk/commit/39dcea6c9362e2d651e3108fa769dbbc32db5a7b), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`f2437e6`](https://github.com/cloudflare/workers-sdk/commit/f2437e606fc69891009285831d94b49bf44f6aff), [`30c2d47`](https://github.com/cloudflare/workers-sdk/commit/30c2d47965c51350aca6b2c70db8fc6496bdaa17)]: + - wrangler@4.125.0 + - miniflare@5.20260820.0-alpha + ## 0.22.0 ### Minor Changes diff --git a/packages/vitest-plugin/package.json b/packages/vitest-plugin/package.json index 781c7536ad4..92fdb532d9b 100644 --- a/packages/vitest-plugin/package.json +++ b/packages/vitest-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/vitest-plugin", - "version": "0.22.0", + "version": "1.0.0", "description": "Workers Vitest integration for writing Vitest unit and integration tests that run inside the Workers runtime", "keywords": [ "cloudflare", diff --git a/packages/workers-auth/CHANGELOG.md b/packages/workers-auth/CHANGELOG.md index 57cbb817e88..65d7eb8c450 100644 --- a/packages/workers-auth/CHANGELOG.md +++ b/packages/workers-auth/CHANGELOG.md @@ -1,5 +1,24 @@ # @cloudflare/workers-auth +## 0.6.5 + +### Patch Changes + +- [#15223](https://github.com/cloudflare/workers-sdk/pull/15223) [`8a04946`](https://github.com/cloudflare/workers-sdk/commit/8a04946b4b1ac41d1fbde7badc2615130b9c1544) Thanks [@petebacondarwin](https://github.com/petebacondarwin)! - Recover from a partially installed keyring backend on Windows + + Choosing to keep your credentials in the OS keyring on Windows installs a native backend the first time you opt in. An install interrupted part-way through — by a dropped connection, a full disk, or an npm told to skip optional packages — could leave a broken backend behind that was nonetheless treated as working. Every login, token refresh, and credential read from then on failed with an internal error, and because the broken state was never re-examined, no amount of retrying would clear it. + + A broken backend is now spotted and reinstalled automatically. If the reinstall still cannot produce a working one, you get a single explanation of how to install it by hand and fall back to the plaintext credentials file for the rest of the session, rather than sitting through a fresh install attempt on every credential access. + +- [#15278](https://github.com/cloudflare/workers-sdk/pull/15278) [`f2437e6`](https://github.com/cloudflare/workers-sdk/commit/f2437e606fc69891009285831d94b49bf44f6aff) Thanks [@Sosokker](https://github.com/Sosokker)! - Fix the `--temporary` error on commands that authenticate more than one time + + `wrangler d1 migrations apply --remote --temporary` failed with this error: `You're already authenticated with Cloudflare, so --temporary can't be used`. The failure occurred with no login and with no `CLOUDFLARE_API_TOKEN`. This command authenticates one time for each statement that it runs. The first authentication makes a temporary preview account. The second authentication read the token of this new account as an earlier login. + + Wrangler now uses again the temporary account from the same command run. Commands that authenticate more than one time now work as `wrangler deploy --temporary` works. If real credentials are available, `--temporary` is still an error. + +- Updated dependencies [[`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae), [`c68f9cb`](https://github.com/cloudflare/workers-sdk/commit/c68f9cb866a2eae4416d20f584f733527189f18a), [`5c10e39`](https://github.com/cloudflare/workers-sdk/commit/5c10e398979c0a054f58dcf2751012cc99e977d2), [`39dcea6`](https://github.com/cloudflare/workers-sdk/commit/39dcea6c9362e2d651e3108fa769dbbc32db5a7b)]: + - @cloudflare/workers-utils@0.34.0 + ## 0.6.4 ### Patch Changes diff --git a/packages/workers-auth/package.json b/packages/workers-auth/package.json index 502b7a47bb2..30d23560d85 100644 --- a/packages/workers-auth/package.json +++ b/packages/workers-auth/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/workers-auth", - "version": "0.6.4", + "version": "0.6.5", "description": "Internal OAuth 2.0 + PKCE flow for Cloudflare CLIs. Not intended for external use — APIs may change without notice.", "homepage": "https://github.com/cloudflare/workers-sdk/tree/main/packages/workers-auth#readme", "bugs": { diff --git a/packages/workers-utils/CHANGELOG.md b/packages/workers-utils/CHANGELOG.md index e2f05d76ed1..d2756443531 100644 --- a/packages/workers-utils/CHANGELOG.md +++ b/packages/workers-utils/CHANGELOG.md @@ -1,5 +1,49 @@ # @cloudflare/workers-utils +## 0.34.0 + +### Minor Changes + +- [#14995](https://github.com/cloudflare/workers-sdk/pull/14995) [`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae) Thanks [@ThomasRubini](https://github.com/ThomasRubini)! - Add `connect` trigger for raw sockets + + You can now configure a Worker to receive raw socket connections during `wrangler dev`, delivered directly to the Worker's `connect(socket, env, ctx)` handler: + + ```jsonc + { + "connect": [{ "protocol": "tcp", "port": 5432 }] + } + ``` + + Each entry opens a listening socket on `127.0.0.1` (or the given `address`) that forwards incoming connections straight to the Worker, bypassing the local dev HTTP entry point. This requires the `experimental` compatibility flag. Only `"tcp"` is supported at the moment. + + `@cloudflare/config` also supports declaring this trigger via `triggers.connect(...)`, which lowers to the `connect` field above: + + ```ts + import { defineWorker, triggers } from "@cloudflare/config"; + + export default defineWorker({ + triggers: [ + triggers.connect({ protocol: "tcp", port: 5432, address: "127.0.0.1" }), + ], + }); + ``` + +- [#15172](https://github.com/cloudflare/workers-sdk/pull/15172) [`c68f9cb`](https://github.com/cloudflare/workers-sdk/commit/c68f9cb866a2eae4416d20f584f733527189f18a) Thanks [@WillTaylorDev](https://github.com/WillTaylorDev)! - Add container support to worker previews + + Worker previews now support containers through a new `previews.containers` configuration block. Container configuration doesn't inherit, so declare containers explicitly in the `previews` block to enable them for previews. This mirrors how `previews.durable_objects` works today. Wrangler names each preview container application `{worker_name}_{preview_slug}_{class_name}`, normalising and shortening the result to what the API accepts. Either change appends a short digest of the composed name, so two names that would otherwise land on one stay distinct. An entry cannot set its own `name`, because application names are unique to an account and a fixed name would collide between two previews of the same Worker. A Durable Object class is backed by at most one container application, so the validator rejects two entries that share a `class_name`. Wrangler skips container applications bound to Durable Object classes that another Worker implements through `script_name`, because the implementing Worker owns its own container application. A binding is not required: a Durable Object declared through `migrations` or `exports` and reached only over `ctx.exports` can still back a container. Every entry must set `class_name`. A `previews.containers` entry whose `class_name` matches no Durable Object class at all is rejected before the preview deployment is created, so a typo fails loudly instead of producing a preview with no container. + + Wrangler creates the container applications on `wrangler preview`. Deleting a preview tears them down server side, so `wrangler preview delete` doesn't remove them. + + Container build and deploy progress prints to stdout. `wrangler preview --json` suppresses wrangler's own output so it doesn't interleave with the payload, and warnings and errors still go to stderr. Docker's build output and the progress spinner write to stdout directly and bypass that suppression, so parse `--json` from a non interactive shell, where the spinner is skipped, and prefer a prebuilt `image` over a Dockerfile. + +### Patch Changes + +- [#15251](https://github.com/cloudflare/workers-sdk/pull/15251) [`5c10e39`](https://github.com/cloudflare/workers-sdk/commit/5c10e398979c0a054f58dcf2751012cc99e977d2) Thanks [@dario-piotrowicz](https://github.com/dario-piotrowicz)! - Fix ESM-only packages missing from deploy metadata + + ESM-only package dependencies (such as `@cloudflare/think`) were silently omitted from the package dependency metadata reported during `wrangler deploy` and `wrangler versions upload`. These packages are now correctly detected and included. + +- [#15284](https://github.com/cloudflare/workers-sdk/pull/15284) [`39dcea6`](https://github.com/cloudflare/workers-sdk/commit/39dcea6c9362e2d651e3108fa769dbbc32db5a7b) Thanks [@emily-shen](https://github.com/emily-shen)! - Move deploy output writing into shared deploy helpers + ## 0.33.1 ### Patch Changes diff --git a/packages/workers-utils/package.json b/packages/workers-utils/package.json index 7373d06a2e8..e0ca0ddf2e1 100644 --- a/packages/workers-utils/package.json +++ b/packages/workers-utils/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/workers-utils", - "version": "0.33.1", + "version": "0.34.0", "description": "Internal utility package for workers-sdk. Not intended for external use — APIs may change without notice.", "homepage": "https://github.com/cloudflare/workers-sdk/tree/main/packages/workers-utils#readme", "bugs": { diff --git a/packages/workflows-shared/CHANGELOG.md b/packages/workflows-shared/CHANGELOG.md index e1bb4dfd547..9e04b3b0a68 100644 --- a/packages/workflows-shared/CHANGELOG.md +++ b/packages/workflows-shared/CHANGELOG.md @@ -1,5 +1,15 @@ # @cloudflare/workflows-shared +## 0.13.0 + +### Minor Changes + +- [#14735](https://github.com/cloudflare/workers-sdk/pull/14735) [`30c2d47`](https://github.com/cloudflare/workers-sdk/commit/30c2d47965c51350aca6b2c70db8fc6496bdaa17) Thanks [@vaishnav-mk](https://github.com/vaishnav-mk)! - Add individual and batch Workflow instance deletion to the runtime and SDK. + + - `WorkflowInstance.delete()` deletes one instance. Self-deletion stops the current execution. + - `env.MY_WORKFLOW.deleteBatch(instanceIds)` deletes up to 100 instances and returns `{ deleted, errors }` per input position. + - `wrangler workflows instances delete [id..]` deletes instances remotely or with `--local`; IDs can also come from a JSON array passed with `--filename`, with a combined limit of 100. + ## 0.12.2 ### Patch Changes diff --git a/packages/workflows-shared/package.json b/packages/workflows-shared/package.json index 1bab034d46e..2d7dc447b6b 100644 --- a/packages/workflows-shared/package.json +++ b/packages/workflows-shared/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/workflows-shared", - "version": "0.12.2", + "version": "0.13.0", "private": true, "description": "Package that is used at Cloudflare to power some internal features of Cloudflare Workflows.", "keywords": [ diff --git a/packages/wrangler/CHANGELOG.md b/packages/wrangler/CHANGELOG.md index af3581d47e7..e4d7ee815a2 100644 --- a/packages/wrangler/CHANGELOG.md +++ b/packages/wrangler/CHANGELOG.md @@ -1,5 +1,101 @@ # wrangler +## 4.125.0 + +### Minor Changes + +- [#14995](https://github.com/cloudflare/workers-sdk/pull/14995) [`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae) Thanks [@ThomasRubini](https://github.com/ThomasRubini)! - Add `connect` trigger for raw sockets + + You can now configure a Worker to receive raw socket connections during `wrangler dev`, delivered directly to the Worker's `connect(socket, env, ctx)` handler: + + ```jsonc + { + "connect": [{ "protocol": "tcp", "port": 5432 }] + } + ``` + + Each entry opens a listening socket on `127.0.0.1` (or the given `address`) that forwards incoming connections straight to the Worker, bypassing the local dev HTTP entry point. This requires the `experimental` compatibility flag. Only `"tcp"` is supported at the moment. + + `@cloudflare/config` also supports declaring this trigger via `triggers.connect(...)`, which lowers to the `connect` field above: + + ```ts + import { defineWorker, triggers } from "@cloudflare/config"; + + export default defineWorker({ + triggers: [ + triggers.connect({ protocol: "tcp", port: 5432, address: "127.0.0.1" }), + ], + }); + ``` + +- [#15172](https://github.com/cloudflare/workers-sdk/pull/15172) [`c68f9cb`](https://github.com/cloudflare/workers-sdk/commit/c68f9cb866a2eae4416d20f584f733527189f18a) Thanks [@WillTaylorDev](https://github.com/WillTaylorDev)! - Add container support to worker previews + + Worker previews now support containers through a new `previews.containers` configuration block. Container configuration doesn't inherit, so declare containers explicitly in the `previews` block to enable them for previews. This mirrors how `previews.durable_objects` works today. Wrangler names each preview container application `{worker_name}_{preview_slug}_{class_name}`, normalising and shortening the result to what the API accepts. Either change appends a short digest of the composed name, so two names that would otherwise land on one stay distinct. An entry cannot set its own `name`, because application names are unique to an account and a fixed name would collide between two previews of the same Worker. A Durable Object class is backed by at most one container application, so the validator rejects two entries that share a `class_name`. Wrangler skips container applications bound to Durable Object classes that another Worker implements through `script_name`, because the implementing Worker owns its own container application. A binding is not required: a Durable Object declared through `migrations` or `exports` and reached only over `ctx.exports` can still back a container. Every entry must set `class_name`. A `previews.containers` entry whose `class_name` matches no Durable Object class at all is rejected before the preview deployment is created, so a typo fails loudly instead of producing a preview with no container. + + Wrangler creates the container applications on `wrangler preview`. Deleting a preview tears them down server side, so `wrangler preview delete` doesn't remove them. + + Container build and deploy progress prints to stdout. `wrangler preview --json` suppresses wrangler's own output so it doesn't interleave with the payload, and warnings and errors still go to stderr. Docker's build output and the progress spinner write to stdout directly and bypass that suppression, so parse `--json` from a non interactive shell, where the spinner is skipped, and prefer a prebuilt `image` over a Dockerfile. + +- [#15174](https://github.com/cloudflare/workers-sdk/pull/15174) [`649f667`](https://github.com/cloudflare/workers-sdk/commit/649f667bd871061da945881ce953ef8f81caea1a) Thanks [@WillTaylorDev](https://github.com/WillTaylorDev)! - [private beta]: Create the parent Worker automatically when `wrangler preview` targets one that doesn't exist yet + + Previews hang off a parent Worker, so running `wrangler preview` before the Worker had ever been deployed failed with a raw API error naming the Preview endpoint. Wrangler now offers to create an empty parent Worker and then carries on creating the Preview. The parent uses the same workers.dev and Preview URL settings that `wrangler deploy` would resolve, without applying routes or cron triggers. In non-interactive environments, Wrangler creates the Worker without asking. + +- [#14735](https://github.com/cloudflare/workers-sdk/pull/14735) [`30c2d47`](https://github.com/cloudflare/workers-sdk/commit/30c2d47965c51350aca6b2c70db8fc6496bdaa17) Thanks [@vaishnav-mk](https://github.com/vaishnav-mk)! - Add individual and batch Workflow instance deletion to the runtime and SDK. + + - `WorkflowInstance.delete()` deletes one instance. Self-deletion stops the current execution. + - `env.MY_WORKFLOW.deleteBatch(instanceIds)` deletes up to 100 instances and returns `{ deleted, errors }` per input position. + - `wrangler workflows instances delete [id..]` deletes instances remotely or with `--local`; IDs can also come from a JSON array passed with `--filename`, with a combined limit of 100. + +### Patch Changes + +- [#15260](https://github.com/cloudflare/workers-sdk/pull/15260) [`5ae9d5b`](https://github.com/cloudflare/workers-sdk/commit/5ae9d5b205fea31516559f7ad89a21eda671af2f) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update dependencies of "miniflare", "wrangler" + + The following dependency versions have been updated: + + | Dependency | From | To | + | ------------------------- | ------------- | ------------- | + | @cloudflare/workers-types | ^5.20260815.1 | ^5.20260816.1 | + | workerd | 1.20260815.1 | 1.20260816.1 | + +- [#15264](https://github.com/cloudflare/workers-sdk/pull/15264) [`4b52975`](https://github.com/cloudflare/workers-sdk/commit/4b52975aac295c8483d6b4001d0b50945293265a) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update dependencies of "miniflare", "wrangler" + + The following dependency versions have been updated: + + | Dependency | From | To | + | ------------------------- | ------------- | ------------- | + | @cloudflare/workers-types | ^5.20260816.1 | ^5.20260819.1 | + | workerd | 1.20260816.1 | 1.20260819.1 | + +- [#15277](https://github.com/cloudflare/workers-sdk/pull/15277) [`ce9b151`](https://github.com/cloudflare/workers-sdk/commit/ce9b1510abf5c1152aedc94456f4d7ffe9402248) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update dependencies of "miniflare", "wrangler" + + The following dependency versions have been updated: + + | Dependency | From | To | + | ------------------------- | ------------- | ------------- | + | @cloudflare/workers-types | ^5.20260819.1 | ^5.20260820.1 | + | workerd | 1.20260819.1 | 1.20260820.1 | + +- [#15192](https://github.com/cloudflare/workers-sdk/pull/15192) [`ef73a28`](https://github.com/cloudflare/workers-sdk/commit/ef73a28c1e7a208d730c6de64566bc96f683ca7b) Thanks [@ondraulehla](https://github.com/ondraulehla)! - Fixes `kv bulk put` corrupting binary values written to local KV + + Values marked `base64: true` were stored incorrectly whenever they contained bytes that do not form valid UTF-8, which covers images, compressed data and most other binary payloads. A Worker reading such a key back under `wrangler dev` got a different, longer value than the one that was written: a 12 byte PNG header came back as 20 bytes. + + `kv bulk put` writes to local KV by default, so the plain command was the affected one. Remote writes were never affected, and neither were entries without `base64` or values written with `kv key put`. + +- [#15284](https://github.com/cloudflare/workers-sdk/pull/15284) [`39dcea6`](https://github.com/cloudflare/workers-sdk/commit/39dcea6c9362e2d651e3108fa769dbbc32db5a7b) Thanks [@emily-shen](https://github.com/emily-shen)! - Move deploy output writing into shared deploy helpers + +- [#15130](https://github.com/cloudflare/workers-sdk/pull/15130) [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864) Thanks [@emily-shen](https://github.com/emily-shen)! - Remove unsupported `remote` configuration from Workflow bindings + + Workflow bindings no longer accept `remote` in configuration, as remote Workflow bindings have never actually been supported. + +- [#15278](https://github.com/cloudflare/workers-sdk/pull/15278) [`f2437e6`](https://github.com/cloudflare/workers-sdk/commit/f2437e606fc69891009285831d94b49bf44f6aff) Thanks [@Sosokker](https://github.com/Sosokker)! - Fix the `--temporary` error on commands that authenticate more than one time + + `wrangler d1 migrations apply --remote --temporary` failed with this error: `You're already authenticated with Cloudflare, so --temporary can't be used`. The failure occurred with no login and with no `CLOUDFLARE_API_TOKEN`. This command authenticates one time for each statement that it runs. The first authentication makes a temporary preview account. The second authentication read the token of this new account as an earlier login. + + Wrangler now uses again the temporary account from the same command run. Commands that authenticate more than one time now work as `wrangler deploy --temporary` works. If real credentials are available, `--temporary` is still an error. + +- Updated dependencies [[`59872c4`](https://github.com/cloudflare/workers-sdk/commit/59872c41d4417d9b8c2efddb4b35662453efcaae), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`5ae9d5b`](https://github.com/cloudflare/workers-sdk/commit/5ae9d5b205fea31516559f7ad89a21eda671af2f), [`4b52975`](https://github.com/cloudflare/workers-sdk/commit/4b52975aac295c8483d6b4001d0b50945293265a), [`ce9b151`](https://github.com/cloudflare/workers-sdk/commit/ce9b1510abf5c1152aedc94456f4d7ffe9402248), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`99a1f49`](https://github.com/cloudflare/workers-sdk/commit/99a1f49d7c037a25d4a19a3fe3054337e7201864), [`30c2d47`](https://github.com/cloudflare/workers-sdk/commit/30c2d47965c51350aca6b2c70db8fc6496bdaa17)]: + - miniflare@5.20260820.0-alpha + ## 4.124.0 ### Minor Changes diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index 867d7113085..5b7c1ec5fe2 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -1,6 +1,6 @@ { "name": "wrangler", - "version": "4.124.0", + "version": "4.125.0", "description": "Command-line interface for all things Cloudflare Workers", "keywords": [ "assembly", From c66d2d5303393381632d1ec474b8e7622dafe30a Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Thu, 20 Aug 2026 13:21:19 -0500 Subject: [PATCH 4/4] Configure workerd for FUSE when safe to do so (#15134) --- .changeset/local-container-fuse-privileges.md | 9 + packages/containers-shared/src/knobs.ts | 17 ++ packages/containers-shared/src/utils.ts | 92 +++++++++- .../containers-shared/tests/utils.test.ts | 162 +++++++++++++++++- packages/miniflare/src/index.ts | 3 + .../miniflare/src/plugins/core/container.ts | 59 +++++++ packages/miniflare/src/plugins/core/index.ts | 57 ++---- packages/miniflare/src/plugins/do/index.ts | 15 +- .../miniflare/src/plugins/do/namespaces.ts | 84 +++++++++ packages/miniflare/src/plugins/index.ts | 4 + .../miniflare/src/plugins/shared/constants.ts | 12 +- .../miniflare/src/plugins/shared/index.ts | 2 + .../src/plugins/shared/unsafe-unique-key.ts | 10 ++ .../miniflare/src/runtime/config/workerd.ts | 18 ++ .../test/plugins/core/container.spec.ts | 126 ++++++++++++++ .../test/plugins/do/namespaces.spec.ts | 52 ++++++ .../miniflare/test/runtime/config.spec.ts | 38 ++++ 17 files changed, 693 insertions(+), 67 deletions(-) create mode 100644 .changeset/local-container-fuse-privileges.md create mode 100644 packages/miniflare/src/plugins/core/container.ts create mode 100644 packages/miniflare/src/plugins/do/namespaces.ts create mode 100644 packages/miniflare/src/plugins/shared/unsafe-unique-key.ts create mode 100644 packages/miniflare/test/plugins/core/container.spec.ts create mode 100644 packages/miniflare/test/plugins/do/namespaces.spec.ts create mode 100644 packages/miniflare/test/runtime/config.spec.ts diff --git a/.changeset/local-container-fuse-privileges.md b/.changeset/local-container-fuse-privileges.md new file mode 100644 index 00000000000..ce0232c0be7 --- /dev/null +++ b/.changeset/local-container-fuse-privileges.md @@ -0,0 +1,9 @@ +--- +"wrangler": minor +"miniflare": minor +"@cloudflare/vite-plugin": minor +--- + +Enable FUSE-capable local container development + +Miniflare now automatically passes the Docker privileges needed for FUSE to local Durable Object containers when using local rootless Docker on Linux with `/dev/fuse` available, or a local Docker engine on macOS or through WSL where Linux containers run in a VM. This applies to Wrangler, the Cloudflare Vite plugin, and direct Miniflare use. diff --git a/packages/containers-shared/src/knobs.ts b/packages/containers-shared/src/knobs.ts index 78fdb8d320c..b02dc182a2e 100644 --- a/packages/containers-shared/src/knobs.ts +++ b/packages/containers-shared/src/knobs.ts @@ -31,3 +31,20 @@ export const getCloudflareContainerRegistry = ( export const getDevContainerImageName = (name: string, tag: string) => { return `${MF_DEV_CONTAINER_PREFIX}/${name.toLowerCase()}:${tag}`; }; + +/** + * Docker's FUSE requirements: expose the device, allow mounts through + * `SYS_ADMIN`, and disable the entire default AppArmor profile because it + * blocks FUSE mounts. + */ +export const FUSE_CONTAINER_PRIVILEGES = { + capabilities: ["SYS_ADMIN"], + devices: [ + { + pathOnHost: "/dev/fuse", + pathInContainer: "/dev/fuse", + cgroupPermissions: "rwm", + }, + ], + securityOpt: ["apparmor:unconfined"], +}; diff --git a/packages/containers-shared/src/utils.ts b/packages/containers-shared/src/utils.ts index f27e3722bc1..36e3e173968 100644 --- a/packages/containers-shared/src/utils.ts +++ b/packages/containers-shared/src/utils.ts @@ -1,9 +1,15 @@ -import { execFileSync, spawn } from "node:child_process"; +import { + execFile, + execFileSync, + spawn, + type StdioOptions, +} from "node:child_process"; import { randomUUID } from "node:crypto"; +import { existsSync } from "node:fs"; +import { release } from "node:os"; import { UserError } from "@cloudflare/workers-utils"; import { dockerImageInspect } from "./inspect"; import type { ContainerDevOptions } from "./types"; -import type { StdioOptions } from "node:child_process"; /** helper for simple docker command call that don't require any io handling */ export const runDockerCmd = ( @@ -92,6 +98,88 @@ export const runDockerCmdWithOutput = (dockerPath: string, args: string[]) => { } }; +/** + * Permit elevated container options only when the selected daemon adds an + * isolation boundary. Rootless Docker limits `SYS_ADMIN` to its user + * namespace. Local Docker engines on macOS and through WSL run their Linux + * daemon in a VM. + */ +export async function containerPrivilegesAllowed( + dockerHost: string, + dockerPath = "docker" +): Promise { + const platform = process.platform; + if ( + !isLocalDockerEndpoint(dockerHost) || + (platform !== "darwin" && platform !== "linux") + ) { + return false; + } + + const securityOptions = await getDockerSecurityOptions( + dockerPath, + dockerHost + ); + const rootless = + Array.isArray(securityOptions) && + securityOptions.some( + (option) => option === "name=rootless" || option === "rootless" + ); + const localVm = + platform === "darwin" || (platform === "linux" && detectWsl()); + const localRootlessLinux = + platform === "linux" && existsSync("/dev/fuse") && rootless; + + return localVm || localRootlessLinux; +} + +/** Checks that workerd will connect to a local Unix socket. */ +function isLocalDockerEndpoint(dockerHost: string): boolean { + try { + const url = new URL(dockerHost); + return ( + url.protocol === "unix:" && + url.hostname === "" && + url.pathname.startsWith("/") + ); + } catch { + return false; + } +} + +/** Reads the daemon security configuration with the selected Docker CLI. */ +async function getDockerSecurityOptions( + dockerPath: string, + dockerHost: string +): Promise { + const output = await new Promise((resolve, reject) => { + execFile( + dockerPath, + ["--host", dockerHost, "info", "--format", "{{json .SecurityOptions}}"], + { encoding: "utf8", timeout: 5_000 }, + (error, stdout) => { + if (error === null) { + resolve(stdout); + } else { + reject(error); + } + } + ); + }); + return JSON.parse(output); +} + +/** + * WSL is detected from the kernel release because its environment variables + * can be removed or changed by the calling process. + */ +function detectWsl(): boolean { + return ( + process.platform === "linux" && + release().toLowerCase().includes("microsoft") + ); +} + /** Checks whether docker is running on the system */ export const isDockerRunning = async (dockerPath: string) => { try { diff --git a/packages/containers-shared/tests/utils.test.ts b/packages/containers-shared/tests/utils.test.ts index bc8b65d4868..3f8ce2b1828 100644 --- a/packages/containers-shared/tests/utils.test.ts +++ b/packages/containers-shared/tests/utils.test.ts @@ -1,16 +1,37 @@ -import { execFileSync, spawn } from "node:child_process"; +import { + execFile, + execFileSync, + spawn, + type ChildProcess, +} from "node:child_process"; import { EventEmitter } from "node:events"; -import { beforeEach, describe, it, vi } from "vitest"; +import { existsSync } from "node:fs"; +import { release } from "node:os"; +import { afterEach, beforeEach, describe, it, vi } from "vitest"; import { checkExposedPorts, cleanupDuplicateImageTags, + containerPrivilegesAllowed, verifyDockerInstalled, } from "./../src/utils"; import type { ContainerDevOptions } from "../src/types"; +type DockerExecFile = ( + file: string, + args: readonly string[], + options: object, + callback: (error: Error | null, stdout: string) => void +) => ChildProcess; + +const dockerExecFile = execFile as DockerExecFile; let docketImageInspectResult = "0"; vi.mock("node:child_process"); +vi.mock("node:fs"); +vi.mock("node:os", async (importOriginal) => ({ + ...(await importOriginal()), + release: vi.fn(), +})); vi.mock("../src/inspect", async (importOriginal) => { const mod: object = await importOriginal(); @@ -97,6 +118,143 @@ describe("cleanupDuplicateImageTags", () => { }); }); +describe("containerPrivilegesAllowed", () => { + let commandError: Error | null; + let rawResponse: string | undefined; + let securityOptions: unknown; + + beforeEach(() => { + commandError = null; + rawResponse = undefined; + securityOptions = ["name=seccomp"]; + vi.mocked(existsSync).mockReturnValue(true); + vi.mocked(release).mockReturnValue("linux"); + vi.spyOn(process, "platform", "get").mockReturnValue("linux"); + vi.mocked(dockerExecFile).mockReset(); + vi.mocked(dockerExecFile).mockImplementation( + (_file, _args, _options, callback) => { + callback(commandError, rawResponse ?? JSON.stringify(securityOptions)); + return new EventEmitter() as ChildProcess; + } + ); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("allows privileges with rootless Docker on Linux", async ({ expect }) => { + securityOptions = ["name=seccomp", "name=rootless"]; + + await expect( + containerPrivilegesAllowed("unix:///run/user/1000/docker.sock") + ).resolves.toBe(true); + expect(dockerExecFile).toHaveBeenCalledWith( + "docker", + [ + "--host", + "unix:///run/user/1000/docker.sock", + "info", + "--format", + "{{json .SecurityOptions}}", + ], + { encoding: "utf8", timeout: 5_000 }, + expect.any(Function) + ); + }); + + it("allows local VM-backed Docker engines on macOS", async ({ expect }) => { + vi.spyOn(process, "platform", "get").mockReturnValue("darwin"); + + await expect( + containerPrivilegesAllowed("unix:///var/run/docker.sock") + ).resolves.toBe(true); + }); + + it("allows Colima-like Linux guests on macOS", async ({ expect }) => { + vi.spyOn(process, "platform", "get").mockReturnValue("darwin"); + securityOptions = []; + + await expect( + containerPrivilegesAllowed( + "unix:///Users/example/.colima/default/docker.sock" + ) + ).resolves.toBe(true); + }); + + it("blocks remote Docker engines", async ({ expect }) => { + vi.spyOn(process, "platform", "get").mockReturnValue("darwin"); + + await expect( + containerPrivilegesAllowed("tcp://docker.example.com:2375") + ).resolves.toBe(false); + expect(dockerExecFile).not.toHaveBeenCalled(); + }); + + it("blocks Windows until workerd supports named pipes", async ({ + expect, + }) => { + vi.spyOn(process, "platform", "get").mockReturnValue("win32"); + + await expect( + containerPrivilegesAllowed("//./pipe/docker_engine") + ).resolves.toBe(false); + }); + + it("blocks unsupported hosts", async ({ expect }) => { + vi.spyOn(process, "platform", "get").mockReturnValue("freebsd"); + + await expect( + containerPrivilegesAllowed("unix:///var/run/docker.sock") + ).resolves.toBe(false); + }); + + it("allows VM-backed Docker engines through WSL", async ({ expect }) => { + vi.mocked(release).mockReturnValue("6.6.87.2-microsoft-standard-WSL2"); + + await expect( + containerPrivilegesAllowed("unix:///var/run/docker.sock") + ).resolves.toBe(true); + }); + + it("fails when Docker rejects the information request", async ({ + expect, + }) => { + commandError = new Error("Docker is unavailable"); + + await expect( + containerPrivilegesAllowed("unix:///var/run/docker.sock") + ).rejects.toThrow("Docker is unavailable"); + }); + + it("fails when Docker returns malformed daemon information", async ({ + expect, + }) => { + rawResponse = "not JSON"; + + await expect( + containerPrivilegesAllowed("unix:///var/run/docker.sock") + ).rejects.toThrow(); + }); + + it("blocks rootless Docker on Linux without /dev/fuse", async ({ + expect, + }) => { + vi.mocked(existsSync).mockReturnValue(false); + securityOptions = ["name=rootless"]; + + await expect( + containerPrivilegesAllowed("unix:///run/user/1000/docker.sock") + ).resolves.toBe(false); + }); + + it("blocks rootful Docker on Linux", async ({ expect }) => { + await expect( + containerPrivilegesAllowed("unix:///var/run/docker.sock") + ).resolves.toBe(false); + }); +}); + /** * Creates a fake child process that emits a `close` event with the given exit code. * diff --git a/packages/miniflare/src/index.ts b/packages/miniflare/src/index.ts index b4b24f59165..a745fca4542 100644 --- a/packages/miniflare/src/index.ts +++ b/packages/miniflare/src/index.ts @@ -81,6 +81,7 @@ import { JsonErrorSchema, reviveError, } from "./plugins/core"; +import { ContainerPrivilegesCache } from "./plugins/core/container"; import { InspectorProxyController } from "./plugins/core/inspector-proxy"; import { isModuleFallbackRequest } from "./plugins/core/module-fallback"; import { HyperdriveProxyController } from "./plugins/hyperdrive/hyperdrive-proxy"; @@ -815,6 +816,7 @@ export class Miniflare { #maybeInspectorProxyController?: InspectorProxyController; #previousRuntimeInspectorPort?: number; + #containerPrivilegesCache = new ContainerPrivilegesCache(); #hyperdriveProxyController: HyperdriveProxyController = new HyperdriveProxyController(); @@ -2066,6 +2068,7 @@ export class Miniflare { queueProducers, queueConsumers, devRegistryEnabled, + containerPrivilegesCache: this.#containerPrivilegesCache, hyperdriveProxyController: this.#hyperdriveProxyController, }; for (const [key, plugin] of this.#mergedPluginEntries) { diff --git a/packages/miniflare/src/plugins/core/container.ts b/packages/miniflare/src/plugins/core/container.ts new file mode 100644 index 00000000000..b328420b8cf --- /dev/null +++ b/packages/miniflare/src/plugins/core/container.ts @@ -0,0 +1,59 @@ +import { + containerPrivilegesAllowed, + FUSE_CONTAINER_PRIVILEGES, +} from "@cloudflare/containers-shared"; +import { getDockerPath } from "@cloudflare/workers-utils"; +import type { + Worker_ContainerEngine, + Worker_DurableObjectNamespace_ContainerOptions_ContainerPrivileges, +} from "../../runtime/config/workerd"; + +export type ContainerPrivileges = + | Worker_DurableObjectNamespace_ContainerOptions_ContainerPrivileges + | undefined; + +/** Caches privilege detection for the current container engine. */ +export class ContainerPrivilegesCache { + #socketPath?: string; + #privileges?: Promise; + + /** Clears cached privileges when Miniflare selects a different engine. */ + setEngine(containerEngine: Worker_ContainerEngine): void { + const socketPath = containerEngine.localDocker.socketPath; + if (socketPath !== this.#socketPath) { + this.#socketPath = socketPath; + this.#privileges = undefined; + } + } + + /** Returns the privilege decision cached for the selected engine. */ + async get( + containerEngine: Worker_ContainerEngine + ): Promise { + this.setEngine(containerEngine); + if (this.#privileges === undefined) { + this.#privileges = getContainerPrivileges(containerEngine); + } + try { + return await this.#privileges; + } catch { + this.#privileges = undefined; + return undefined; + } + } +} + +/** + * Returns FUSE privileges only when the selected Docker daemon has a safe + * isolation boundary. + */ +export async function getContainerPrivileges( + containerEngine: Worker_ContainerEngine +): Promise { + return (await containerPrivilegesAllowed( + containerEngine.localDocker.socketPath, + getDockerPath() + )) + ? FUSE_CONTAINER_PRIVILEGES + : undefined; +} diff --git a/packages/miniflare/src/plugins/core/index.ts b/packages/miniflare/src/plugins/core/index.ts index a402bf996f1..4dbd5727783 100644 --- a/packages/miniflare/src/plugins/core/index.ts +++ b/packages/miniflare/src/plugins/core/index.ts @@ -29,10 +29,8 @@ import { MiniflareCoreError, type Log } from "../../shared"; import { getDevControlDurableObjectBindingName } from "../../shared/dev-control"; import { CoreBindings, CoreHeaders, viewToBuffer } from "../../workers"; import { getCacheServiceName } from "../cache"; -import { - DURABLE_OBJECTS_STORAGE_SERVICE_NAME, - getDurableObjectUniqueKey, -} from "../do"; +import { DURABLE_OBJECTS_STORAGE_SERVICE_NAME } from "../do"; +import { getDurableObjectNamespaces } from "../do/namespaces"; import { IMAGES_PLUGIN_NAME } from "../images"; import { getR2PublicService, @@ -86,7 +84,6 @@ import type { ServiceDesignator, Worker_Binding, Worker_ContainerEngine, - Worker_DurableObjectNamespace, Worker_Module, } from "../../runtime"; import type { Awaitable } from "../../workers"; @@ -483,6 +480,7 @@ export const CORE_PLUGIN: Plugin = { workerBindings, workerIndex, durableObjectClassNames, + containerPrivilegesCache, additionalModules, loopbackHost, loopbackPort, @@ -523,6 +521,14 @@ export const CORE_PLUGIN: Plugin = { const serviceName = getUserServiceName(config.name); const classNames = durableObjectClassNames.get(serviceName); const classNamesEntries = Array.from(classNames ?? []); + const containerEngine = getContainerEngine(sharedOptions.containerEngine); + containerPrivilegesCache.setEngine(containerEngine); + const hasContainers = classNamesEntries.some( + ([, { container }]) => container !== undefined + ); + const containerPrivileges = hasContainers + ? await containerPrivilegesCache.get(containerEngine) + : undefined; // Wrap Durable Object classes for the local explorer // This injects a method onto user defined DO classes to allow @@ -604,40 +610,11 @@ export const CORE_PLUGIN: Plugin = { compatibilityDate, compatibilityFlags, bindings: workerBindings, - durableObjectNamespaces: - classNamesEntries.map( - ([ - className, - { - enableSql, - unsafeUniqueKey, - unsafePreventEviction: preventEviction, - container, - }, - ]) => { - const uniqueKey = getDurableObjectUniqueKey( - className, - config.name, - unsafeUniqueKey - ); - - return uniqueKey === undefined - ? { - className, - enableSql, - ephemeralLocal: kVoid, - preventEviction, - container, - } - : { - className, - enableSql, - uniqueKey, - preventEviction, - container, - }; - } - ), + durableObjectNamespaces: getDurableObjectNamespaces( + classNames, + config.name, + containerPrivileges + ), durableObjectStorage: classNamesEntries.length === 0 ? undefined @@ -657,7 +634,7 @@ export const CORE_PLUGIN: Plugin = { streamingTails: tailConsumers .filter((consumer) => consumer.streaming) .map(getTailServiceDesignator), - containerEngine: getContainerEngine(sharedOptions.containerEngine), + containerEngine, ...(dev?.access ? { accessBlobHeader: CoreHeaders.ACCESS_BLOB, diff --git a/packages/miniflare/src/plugins/do/index.ts b/packages/miniflare/src/plugins/do/index.ts index 49611e6b0ca..aebf3ac4dad 100644 --- a/packages/miniflare/src/plugins/do/index.ts +++ b/packages/miniflare/src/plugins/do/index.ts @@ -4,11 +4,10 @@ import { getUserServiceName } from "../core"; import { getEnvBindingsOfType, getPersistPath, - kUnsafeEphemeralUniqueKey, ProxyNodeBinding, } from "../shared"; import type { Worker_Binding } from "../../runtime"; -import type { Plugin, UnsafeUniqueKey } from "../shared"; +import type { Plugin } from "../shared"; // Options for a container attached to the DO export const DOContainerOptionsSchema = z.object({ @@ -16,17 +15,7 @@ export const DOContainerOptionsSchema = z.object({ }); export type DOContainerOptions = z.infer; -export function getDurableObjectUniqueKey( - className: string, - workerName: string | undefined, - unsafeUniqueKey: UnsafeUniqueKey | undefined -): string | undefined { - if (unsafeUniqueKey === kUnsafeEphemeralUniqueKey) { - return undefined; - } - - return unsafeUniqueKey ?? `${workerName ?? ""}-${className}`; -} +export { getDurableObjectUniqueKey } from "./namespaces"; export const DURABLE_OBJECTS_PLUGIN_NAME = "do"; diff --git a/packages/miniflare/src/plugins/do/namespaces.ts b/packages/miniflare/src/plugins/do/namespaces.ts new file mode 100644 index 00000000000..9bd718e7bb7 --- /dev/null +++ b/packages/miniflare/src/plugins/do/namespaces.ts @@ -0,0 +1,84 @@ +import { kVoid } from "../../runtime/config/workerd"; +import { kUnsafeEphemeralUniqueKey } from "../shared/unsafe-unique-key"; +import type { + Worker_DurableObjectNamespace, + Worker_DurableObjectNamespace_ContainerOptions_ContainerPrivileges, +} from "../../runtime/config/workerd"; +import type { DurableObjectClassNames } from "../shared"; +import type { UnsafeUniqueKey } from "../shared/unsafe-unique-key"; + +type DurableObjectClasses = NonNullable< + ReturnType +>; + +/** + * Returns the key workerd uses to identify a Durable Object class. + * + * @param className Durable Object class name. + * @param workerName Worker containing the class. + * @param unsafeUniqueKey Explicit key override. + */ +export function getDurableObjectUniqueKey( + className: string, + workerName: string | undefined, + unsafeUniqueKey: UnsafeUniqueKey | undefined +): string | undefined { + if (unsafeUniqueKey === kUnsafeEphemeralUniqueKey) { + return undefined; + } + + return unsafeUniqueKey ?? `${workerName ?? ""}-${className}`; +} + +/** + * Converts parsed Durable Object classes into workerd namespaces. + * + * @param classNames Classes exported by one worker. + * @param workerName Worker containing the classes. + * @param containerPrivileges Privileges allowed for local containers. + */ +export function getDurableObjectNamespaces( + classNames: DurableObjectClasses | undefined, + workerName: string | undefined, + containerPrivileges: + | Worker_DurableObjectNamespace_ContainerOptions_ContainerPrivileges + | undefined +): Worker_DurableObjectNamespace[] { + return Array.from(classNames ?? []).map( + ([ + className, + { + enableSql, + unsafeUniqueKey, + unsafePreventEviction: preventEviction, + container, + }, + ]) => { + const uniqueKey = getDurableObjectUniqueKey( + className, + workerName, + unsafeUniqueKey + ); + const containerOptions = + container === undefined + ? undefined + : { ...container, privileges: containerPrivileges }; + + return uniqueKey === undefined + ? { + className, + enableSql, + ephemeralLocal: kVoid, + preventEviction, + container: containerOptions, + } + : { + className, + enableSql, + uniqueKey, + preventEviction, + container: containerOptions, + }; + } + ); +} diff --git a/packages/miniflare/src/plugins/index.ts b/packages/miniflare/src/plugins/index.ts index b352c18e987..9040f90e353 100644 --- a/packages/miniflare/src/plugins/index.ts +++ b/packages/miniflare/src/plugins/index.ts @@ -113,6 +113,10 @@ export type { GlobalServicesOptions, NodeJSCompatMode, } from "./core"; +export type { + ContainerPrivileges, + ContainerPrivilegesCache, +} from "./core/container"; export type * from "./core/proxy/types"; export * from "./d1"; export * from "./do"; diff --git a/packages/miniflare/src/plugins/shared/constants.ts b/packages/miniflare/src/plugins/shared/constants.ts index a5a1dec14d9..886c07634bb 100644 --- a/packages/miniflare/src/plugins/shared/constants.ts +++ b/packages/miniflare/src/plugins/shared/constants.ts @@ -167,13 +167,5 @@ export function buildRemoteProxyProps( }; } -// Value of `unsafeUniqueKey` that forces the use of "colo local" ephemeral -// namespaces. These namespaces only provide a `get(id: string): Fetcher` method -// and construct objects without a `state` parameter. See the schema for details: -// https://github.com/cloudflare/workerd/blob/v1.20231206.0/src/workerd/server/workerd.capnp#L529-L543 -// Using `Symbol.for()` instead of `Symbol()` in case multiple copies of -// `miniflare` are loaded (e.g. when configuring Vitest and when running pool) -export const kUnsafeEphemeralUniqueKey = Symbol.for( - "miniflare.kUnsafeEphemeralUniqueKey" -); -export type UnsafeUniqueKey = string | typeof kUnsafeEphemeralUniqueKey; +export { kUnsafeEphemeralUniqueKey } from "./unsafe-unique-key"; +export type { UnsafeUniqueKey } from "./unsafe-unique-key"; diff --git a/packages/miniflare/src/plugins/shared/index.ts b/packages/miniflare/src/plugins/shared/index.ts index 00d6f2084b1..0f0e1d0c089 100644 --- a/packages/miniflare/src/plugins/shared/index.ts +++ b/packages/miniflare/src/plugins/shared/index.ts @@ -18,6 +18,7 @@ import type { QueueConsumerSchema, QueueProducerSchema, } from "../../workers"; +import type { ContainerPrivilegesCache } from "../core/container"; import type { DOContainerOptions } from "../do"; import type { HyperdriveProxyController } from "../hyperdrive/hyperdrive-proxy"; import type { UnsafeUniqueKey } from "./constants"; @@ -75,6 +76,7 @@ export interface PluginServicesOptions { // the dev-registry proxy worker, e.g. so the queue broker can deliver // messages to a consumer in another `wrangler dev` process. devRegistryEnabled: boolean; + containerPrivilegesCache: ContainerPrivilegesCache; hyperdriveProxyController: HyperdriveProxyController; } diff --git a/packages/miniflare/src/plugins/shared/unsafe-unique-key.ts b/packages/miniflare/src/plugins/shared/unsafe-unique-key.ts new file mode 100644 index 00000000000..a73d84953f5 --- /dev/null +++ b/packages/miniflare/src/plugins/shared/unsafe-unique-key.ts @@ -0,0 +1,10 @@ +// Value of `unsafeUniqueKey` that forces the use of "colo local" ephemeral +// namespaces. These namespaces only provide a `get(id: string): Fetcher` method +// and construct objects without a `state` parameter. See the schema for details: +// https://github.com/cloudflare/workerd/blob/v1.20231206.0/src/workerd/server/workerd.capnp#L529-L543 +// Using `Symbol.for()` instead of `Symbol()` in case multiple copies of +// `miniflare` are loaded (e.g. when configuring Vitest and when running pool) +export const kUnsafeEphemeralUniqueKey = Symbol.for( + "miniflare.kUnsafeEphemeralUniqueKey" +); +export type UnsafeUniqueKey = string | typeof kUnsafeEphemeralUniqueKey; diff --git a/packages/miniflare/src/runtime/config/workerd.ts b/packages/miniflare/src/runtime/config/workerd.ts index 56ddffc7615..cc0f47ed5f6 100644 --- a/packages/miniflare/src/runtime/config/workerd.ts +++ b/packages/miniflare/src/runtime/config/workerd.ts @@ -201,8 +201,26 @@ export type Worker_DurableObjectNamespace = { className?: string; preventEviction?: boolean; enableSql?: boolean; + container?: Worker_DurableObjectNamespace_ContainerOptions; } & ({ uniqueKey?: string } | { ephemeralLocal?: Void }); +export interface Worker_DurableObjectNamespace_ContainerOptions { + imageName?: string; + privileges?: Worker_DurableObjectNamespace_ContainerOptions_ContainerPrivileges; +} + +export interface Worker_DurableObjectNamespace_ContainerOptions_ContainerPrivileges { + capabilities?: string[]; + devices?: Worker_DurableObjectNamespace_ContainerOptions_ContainerPrivileges_Device[]; + securityOpt?: string[]; +} + +export interface Worker_DurableObjectNamespace_ContainerOptions_ContainerPrivileges_Device { + pathOnHost?: string; + pathInContainer?: string; + cgroupPermissions?: string; +} + export type ExternalServer = { address?: string } & ( | { http: HttpOptions } | { https: ExternalServer_Https } diff --git a/packages/miniflare/test/plugins/core/container.spec.ts b/packages/miniflare/test/plugins/core/container.spec.ts new file mode 100644 index 00000000000..6071b0f8e8a --- /dev/null +++ b/packages/miniflare/test/plugins/core/container.spec.ts @@ -0,0 +1,126 @@ +import { + containerPrivilegesAllowed, + FUSE_CONTAINER_PRIVILEGES, +} from "@cloudflare/containers-shared"; +import { getDockerPath } from "@cloudflare/workers-utils"; +import { beforeEach, test, vi } from "vitest"; +import { + ContainerPrivilegesCache, + getContainerPrivileges, +} from "../../../src/plugins/core/container"; + +vi.mock("@cloudflare/containers-shared", async (importOriginal) => ({ + ...(await importOriginal()), + containerPrivilegesAllowed: vi.fn(), +})); +vi.mock("@cloudflare/workers-utils", async (importOriginal) => ({ + ...(await importOriginal()), + getDockerPath: vi.fn(), +})); + +beforeEach(() => { + vi.mocked(containerPrivilegesAllowed).mockReset(); + vi.mocked(getDockerPath).mockReturnValue("custom-docker"); +}); + +test("enables FUSE privileges for a safe container engine", async ({ + expect, +}) => { + vi.mocked(containerPrivilegesAllowed).mockResolvedValue(true); + const containerEngine = { + localDocker: { socketPath: "unix:///custom/docker.sock" }, + }; + + await expect(getContainerPrivileges(containerEngine)).resolves.toBe( + FUSE_CONTAINER_PRIVILEGES + ); + expect(containerPrivilegesAllowed).toHaveBeenCalledWith( + "unix:///custom/docker.sock", + "custom-docker" + ); +}); + +test("omits FUSE privileges for an unsafe container engine", async ({ + expect, +}) => { + vi.mocked(containerPrivilegesAllowed).mockResolvedValue(false); + + await expect( + getContainerPrivileges({ + localDocker: { socketPath: "unix:///var/run/docker.sock" }, + }) + ).resolves.toBeUndefined(); +}); + +test("propagates container engine inspection errors", async ({ expect }) => { + vi.mocked(containerPrivilegesAllowed).mockRejectedValue( + new Error("Docker is unavailable") + ); + + await expect( + getContainerPrivileges({ + localDocker: { socketPath: "unix:///var/run/docker.sock" }, + }) + ).rejects.toThrow("Docker is unavailable"); +}); + +test("retries failed inspection", async ({ expect }) => { + vi.mocked(containerPrivilegesAllowed) + .mockRejectedValueOnce(new Error("Docker is unavailable")) + .mockResolvedValueOnce(true); + const cache = new ContainerPrivilegesCache(); + const containerEngine = { + localDocker: { socketPath: "unix:///var/run/docker.sock" }, + }; + + const first = cache.get(containerEngine); + const second = cache.get(containerEngine); + await expect(Promise.all([first, second])).resolves.toEqual([ + undefined, + undefined, + ]); + expect(containerPrivilegesAllowed).toHaveBeenCalledTimes(1); + + await expect(cache.get(containerEngine)).resolves.toBe( + FUSE_CONTAINER_PRIVILEGES + ); + expect(containerPrivilegesAllowed).toHaveBeenCalledTimes(2); +}); + +test("caches an unsafe engine decision", async ({ expect }) => { + vi.mocked(containerPrivilegesAllowed).mockResolvedValue(false); + const cache = new ContainerPrivilegesCache(); + const containerEngine = { + localDocker: { socketPath: "unix:///var/run/docker.sock" }, + }; + + await cache.get(containerEngine); + await cache.get(containerEngine); + + expect(containerPrivilegesAllowed).toHaveBeenCalledTimes(1); +}); + +test("caches privileges until the container engine changes", async ({ + expect, +}) => { + vi.mocked(containerPrivilegesAllowed).mockResolvedValue(true); + const cache = new ContainerPrivilegesCache(); + const containerEngine = { + localDocker: { socketPath: "unix:///var/run/docker.sock" }, + }; + + await Promise.all([cache.get(containerEngine), cache.get(containerEngine)]); + expect(containerPrivilegesAllowed).toHaveBeenCalledTimes(1); + + await cache.get({ + localDocker: { socketPath: "unix:///run/user/1000/docker.sock" }, + }); + expect(containerPrivilegesAllowed).toHaveBeenCalledTimes(2); + + cache.setEngine({ + localDocker: { socketPath: "unix:///another/docker.sock" }, + }); + cache.setEngine(containerEngine); + await cache.get(containerEngine); + expect(containerPrivilegesAllowed).toHaveBeenCalledTimes(3); +}); diff --git a/packages/miniflare/test/plugins/do/namespaces.spec.ts b/packages/miniflare/test/plugins/do/namespaces.spec.ts new file mode 100644 index 00000000000..bfe28d868ff --- /dev/null +++ b/packages/miniflare/test/plugins/do/namespaces.spec.ts @@ -0,0 +1,52 @@ +import { FUSE_CONTAINER_PRIVILEGES } from "@cloudflare/containers-shared"; +import { test } from "vitest"; +import { getDurableObjectNamespaces } from "../../../src/plugins/do/namespaces"; +import type { DurableObjectClassNames } from "../../../src/plugins/shared"; + +type DurableObjectClasses = NonNullable< + ReturnType +>; + +test("builds Durable Object namespaces with detected container privileges", ({ + expect, +}) => { + const classNames: DurableObjectClasses = new Map([ + ["ContainerObject", { container: { imageName: "example:latest" } }], + ["RegularObject", {}], + ]); + const namespaces = getDurableObjectNamespaces( + classNames, + "worker", + FUSE_CONTAINER_PRIVILEGES + ); + const containerObject = namespaces.find( + ({ className }) => className === "ContainerObject" + ); + const regularObject = namespaces.find( + ({ className }) => className === "RegularObject" + ); + + expect(containerObject).toMatchObject({ + className: "ContainerObject", + uniqueKey: "worker-ContainerObject", + container: { + imageName: "example:latest", + privileges: FUSE_CONTAINER_PRIVILEGES, + }, + }); + expect(regularObject?.container).toBeUndefined(); + + const namespacesWithoutPrivileges = getDurableObjectNamespaces( + classNames, + "worker", + undefined + ); + const containerWithoutPrivileges = namespacesWithoutPrivileges.find( + ({ className }) => className === "ContainerObject" + ); + + expect(containerWithoutPrivileges?.container?.imageName).toBe( + "example:latest" + ); + expect(containerWithoutPrivileges?.container?.privileges).toBeUndefined(); +}); diff --git a/packages/miniflare/test/runtime/config.spec.ts b/packages/miniflare/test/runtime/config.spec.ts new file mode 100644 index 00000000000..f8e789c7ac9 --- /dev/null +++ b/packages/miniflare/test/runtime/config.spec.ts @@ -0,0 +1,38 @@ +import { FUSE_CONTAINER_PRIVILEGES } from "@cloudflare/containers-shared"; +import { Message } from "capnp-es"; +import { test } from "vitest"; +import { serializeConfig } from "../../src/runtime/config"; +import { Config as CapnpConfig } from "../../src/runtime/config/generated/workerd"; + +test("serializes Durable Object container privileges", ({ expect }) => { + const buffer = serializeConfig({ + services: [ + { + name: "worker", + worker: { + durableObjectNamespaces: [ + { + className: "ExampleContainer", + uniqueKey: "example", + container: { + imageName: "example:latest", + privileges: FUSE_CONTAINER_PRIVILEGES, + }, + }, + ], + }, + }, + ], + }); + const config = new Message(buffer, false).getRoot(CapnpConfig); + const privileges = config.services + .get(0) + .worker.durableObjectNamespaces.get(0).container.privileges; + const device = privileges.devices.get(0); + + expect(privileges.capabilities.get(0)).toBe("SYS_ADMIN"); + expect(device.pathOnHost).toBe("/dev/fuse"); + expect(device.pathInContainer).toBe("/dev/fuse"); + expect(device.cgroupPermissions).toBe("rwm"); + expect(privileges.securityOpt.get(0)).toBe("apparmor:unconfined"); +});