diff --git a/.changeset/consolidate-miniflare-persist-options.md b/.changeset/consolidate-miniflare-persist-options.md new file mode 100644 index 0000000000..00c2d81736 --- /dev/null +++ b/.changeset/consolidate-miniflare-persist-options.md @@ -0,0 +1,16 @@ +--- +"miniflare": major +--- + +Consolidate persistence and temporary directory options + +The per-resource persistence options (`kvPersist`, `r2Persist`, `d1Persist`, `cachePersist`, `durableObjectsPersist`, `workflowsPersist`, `secretsStorePersist`, `analyticsEngineDatasetsPersist`, `streamPersist`, `imagesPersist`, and `helloWorldPersist`) have been removed, along with `defaultPersistRoot` and `defaultProjectTmpPath`. Persistence is now configured with two options: + +```js +new Miniflare({ + resourcePersistencePath: ".wrangler/state/v3", + resourceTmpPath: ".wrangler/tmp", +}); +``` + +When `resourcePersistencePath` is set, each resource persists to a subdirectory named after its plugin (e.g. `.wrangler/state/v3/kv`). When it is omitted, resources are ephemeral and their data is cleared on dispose. The `boolean`, `memory:`, `file:` and relative-path forms of the old per-resource options, the `.mf` default directory, and the `Miniflare.unsafeGetPersistPaths()` method have also been removed. diff --git a/.changeset/drop-cache-warn-usage.md b/.changeset/drop-cache-warn-usage.md new file mode 100644 index 0000000000..fad1959bc4 --- /dev/null +++ b/.changeset/drop-cache-warn-usage.md @@ -0,0 +1,7 @@ +--- +"miniflare": major +--- + +Remove the `cacheWarnUsage` option + +The `cacheWarnUsage` Worker option, which logged a warning when cache operations were used, has been removed. diff --git a/.changeset/drop-fetch-mock.md b/.changeset/drop-fetch-mock.md new file mode 100644 index 0000000000..899ea332b1 --- /dev/null +++ b/.changeset/drop-fetch-mock.md @@ -0,0 +1,5 @@ +--- +"miniflare": major +--- + +Remove the `fetchMock` option and `createFetchMock` export diff --git a/.changeset/drop-https-key-cert-path.md b/.changeset/drop-https-key-cert-path.md new file mode 100644 index 0000000000..89854346f7 --- /dev/null +++ b/.changeset/drop-https-key-cert-path.md @@ -0,0 +1,7 @@ +--- +"miniflare": major +--- + +Remove the `httpsKeyPath` and `httpsCertPath` options + +The `httpsKeyPath` and `httpsCertPath` options have been removed. To use a custom certificate, read the files and pass their contents via the existing `httpsKey` and `httpsCert` options. diff --git a/.changeset/drop-runtime-stdio-and-structured-logs-option.md b/.changeset/drop-runtime-stdio-and-structured-logs-option.md new file mode 100644 index 0000000000..2aa033314b --- /dev/null +++ b/.changeset/drop-runtime-stdio-and-structured-logs-option.md @@ -0,0 +1,9 @@ +--- +"miniflare": major +--- + +Only support structured workerd logs + +The `handleRuntimeStdio` option (for handling the raw `workerd` stdout/stderr streams) and the `structuredWorkerdLogs` option (for toggling structured `workerd` logs) have been removed. Structured logging is now always enabled. + +To receive `workerd`'s output, use `handleStructuredLogs`, which is passed parsed structured log entries. When no `handleStructuredLogs` handler is provided, logs are written to the console by default (`warn`/`error` to stderr, everything else to stdout). diff --git a/.changeset/drop-unsafe-sticky-blobs.md b/.changeset/drop-unsafe-sticky-blobs.md new file mode 100644 index 0000000000..6c6bc1949d --- /dev/null +++ b/.changeset/drop-unsafe-sticky-blobs.md @@ -0,0 +1,7 @@ +--- +"miniflare": major +--- + +Drop the `unsafeStickyBlobs` option + +This prevented blob files from being deleted when overwriting or deleting keys, and only existed to support the Durable Object isolated storage feature in `@cloudflare/vitest-pool-workers`, which was removed in 0.13.0. Blobs are now always cleaned up as expected. diff --git a/.changeset/drop-wrapped-bindings.md b/.changeset/drop-wrapped-bindings.md new file mode 100644 index 0000000000..aac9471e5c --- /dev/null +++ b/.changeset/drop-wrapped-bindings.md @@ -0,0 +1,5 @@ +--- +"miniflare": major +--- + +Remove the `wrappedBindings` option diff --git a/.changeset/miniflare-container-engine-shared-option.md b/.changeset/miniflare-container-engine-shared-option.md new file mode 100644 index 0000000000..6cd5b07acf --- /dev/null +++ b/.changeset/miniflare-container-engine-shared-option.md @@ -0,0 +1,16 @@ +--- +"miniflare": major +--- + +Move `containerEngine` from a per-worker option to a top-level option + +`containerEngine` is now a top-level Miniflare option rather than a per-worker option, reflecting that it configures a single container engine for the whole Miniflare instance. + +```js +new Miniflare({ + containerEngine: "unix:///var/run/docker.sock", + workers: [{ name: "my-worker", script: "..." }], +}); +``` + +Previously it was set inside each worker's options. diff --git a/.changeset/rename-cache-option-to-cache-api.md b/.changeset/rename-cache-option-to-cache-api.md new file mode 100644 index 0000000000..7ea88c8cf5 --- /dev/null +++ b/.changeset/rename-cache-option-to-cache-api.md @@ -0,0 +1,7 @@ +--- +"miniflare": major +--- + +Rename the `cache` Worker option to `cacheAPI` + +The boolean option controlling whether the Cache API caches anything has been renamed from `cache` to `cacheAPI`. This is to distinguish it from Workers Cache. diff --git a/.changeset/vitest-pool-drop-sticky-blobs.md b/.changeset/vitest-pool-drop-sticky-blobs.md new file mode 100644 index 0000000000..53a53c3dfb --- /dev/null +++ b/.changeset/vitest-pool-drop-sticky-blobs.md @@ -0,0 +1,7 @@ +--- +"@cloudflare/vitest-pool-workers": patch +--- + +Stop enabling Miniflare's removed `unsafeStickyBlobs` option + +The pool no longer sets the `unsafeStickyBlobs` Miniflare option, which has been removed. This option was only needed for the Durable Object isolated storage feature that was dropped in 0.13.0, so there is no change in behaviour. diff --git a/.changeset/wrangler-unstable-worker-options-container-engine.md b/.changeset/wrangler-unstable-worker-options-container-engine.md new file mode 100644 index 0000000000..7f73a13a60 --- /dev/null +++ b/.changeset/wrangler-unstable-worker-options-container-engine.md @@ -0,0 +1,7 @@ +--- +"wrangler": minor +--- + +Remove `containerEngine` from the worker options returned by `unstable_getMiniflareWorkerOptions` + +`unstable_getMiniflareWorkerOptions` no longer includes `containerEngine` in the returned `workerOptions`, since the container engine is a Miniflare instance-wide setting rather than a per-worker one. Callers that build a Miniflare instance from these options should set `containerEngine` at the top level instead. diff --git a/fixtures/unsafe-external-plugin/src/plugins/unsafe-service.ts b/fixtures/unsafe-external-plugin/src/plugins/unsafe-service.ts index 1218e9aaf6..d901e5e1a0 100644 --- a/fixtures/unsafe-external-plugin/src/plugins/unsafe-service.ts +++ b/fixtures/unsafe-external-plugin/src/plugins/unsafe-service.ts @@ -57,12 +57,7 @@ export const UNSAFE_SERVICE_PLUGIN: Plugin< options?.map((binding) => [binding.name, new ProxyNodeBinding()]) ?? [] ); }, - async getServices({ - options, - tmpPath, - defaultPersistRoot, - unsafeStickyBlobs, - }) { + async getServices({ options, tmpPath, resourcePersistencePath }) { if (!options || options.length === 0) { return []; } @@ -70,8 +65,7 @@ export const UNSAFE_SERVICE_PLUGIN: Plugin< const persistPath = getPersistPath( UNSAFE_PLUGIN_NAME, tmpPath, - defaultPersistRoot, - undefined + resourcePersistencePath ); await fs.mkdir(persistPath, { recursive: true }); @@ -110,7 +104,7 @@ export const UNSAFE_SERVICE_PLUGIN: Plugin< name: SharedBindings.MAYBE_SERVICE_LOOPBACK, service: { name: SERVICE_LOOPBACK }, }, - ...getMiniflareObjectBindings(unsafeStickyBlobs), + ...getMiniflareObjectBindings(), ], }, } satisfies Service; diff --git a/packages/miniflare/scripts/build.mjs b/packages/miniflare/scripts/build.mjs index bdd44cbe30..36fa200d16 100644 --- a/packages/miniflare/scripts/build.mjs +++ b/packages/miniflare/scripts/build.mjs @@ -104,6 +104,7 @@ const localExplorerWorkerPath = path.join( */ const fixtureBuilds = [ path.join(pkgRoot, "test/fixtures/unsafe-plugin/index.ts"), + path.join(pkgRoot, "test/fixtures/echo-plugin/index.ts"), ]; /** diff --git a/packages/miniflare/src/http/server.ts b/packages/miniflare/src/http/server.ts index 744d5069dc..b55ceab6f6 100644 --- a/packages/miniflare/src/http/server.ts +++ b/packages/miniflare/src/http/server.ts @@ -1,8 +1,6 @@ -import fs from "node:fs/promises"; import { CERT, KEY } from "./cert"; import type { CORE_PLUGIN } from "../plugins"; import type { HttpOptions, Socket_Https } from "../runtime"; -import type { Awaitable } from "../workers"; import type { z } from "zod"; export async function getEntrySocketHttpOptions( @@ -11,15 +9,9 @@ export async function getEntrySocketHttpOptions( let privateKey: string | undefined = undefined; let certificateChain: string | undefined = undefined; - if ( - (coreOpts.httpsKey || coreOpts.httpsKeyPath) && - (coreOpts.httpsCert || coreOpts.httpsCertPath) - ) { - privateKey = await valueOrFile(coreOpts.httpsKey, coreOpts.httpsKeyPath); - certificateChain = await valueOrFile( - coreOpts.httpsCert, - coreOpts.httpsCertPath - ); + if (coreOpts.httpsKey && coreOpts.httpsCert) { + privateKey = coreOpts.httpsKey; + certificateChain = coreOpts.httpsCert; } else if (coreOpts.https) { privateKey = KEY; certificateChain = CERT; @@ -40,10 +32,3 @@ export async function getEntrySocketHttpOptions( return { http: {} }; } } - -function valueOrFile( - value?: string, - filePath?: string -): Awaitable { - return value ?? (filePath && fs.readFile(filePath, "utf8")); -} diff --git a/packages/miniflare/src/index.ts b/packages/miniflare/src/index.ts index e348974fff..f6b1d34a58 100644 --- a/packages/miniflare/src/index.ts +++ b/packages/miniflare/src/index.ts @@ -75,7 +75,6 @@ import { getUserServiceName, handlePrettyErrorRequest, JsonErrorSchema, - maybeWrappedModuleToWorkerName, reviveError, } from "./plugins/core"; import { InspectorProxyController } from "./plugins/core/inspector-proxy"; @@ -93,7 +92,6 @@ import { serializeConfig, } from "./runtime"; import { - _isCyclic, isFileNotFoundError, MiniflareCoreError, NoOpLog, @@ -121,7 +119,6 @@ import type { DispatchFetch, RequestInit } from "./http"; import type { DurableObjectClassNames, Plugin, - Plugins, PluginServicesOptions, PluginSharedOptions, PluginWorkerOptions, @@ -130,7 +127,6 @@ import type { ReplaceWorkersTypes, SharedOptions, WorkerOptions, - WrappedBindingNames, } from "./plugins"; import type { NameSourceOptions, @@ -661,47 +657,6 @@ function getExternalServiceEntrypoints(allWorkerOpts: PluginWorkerOptions[]) { return externalServices; } -function invalidWrappedAsBound(name: string, bindingType: string): never { - const stringName = JSON.stringify(name); - throw new MiniflareCoreError( - "ERR_INVALID_WRAPPED", - `Cannot use ${stringName} for wrapped binding because it is bound to with ${bindingType} bindings.\nEnsure other workers don't define ${bindingType} bindings to ${stringName}.` - ); -} -function getWrappedBindingNames( - allWorkerOpts: PluginWorkerOptions[], - durableObjectClassNames: DurableObjectClassNames -): WrappedBindingNames { - // Build set of all worker names bound to as wrapped bindings. - // Also check these "workers" aren't bound to as services/Durable Objects. - // We won't add them as regular workers so these bindings would fail. - const wrappedBindingWorkerNames = new Set(); - for (const workerOpts of allWorkerOpts) { - for (const designator of Object.values( - workerOpts.core.wrappedBindings ?? {} - )) { - const scriptName = - typeof designator === "object" ? designator.scriptName : designator; - if (durableObjectClassNames.has(getUserServiceName(scriptName))) { - invalidWrappedAsBound(scriptName, "Durable Object"); - } - wrappedBindingWorkerNames.add(scriptName); - } - } - // Need to collect all wrapped bindings before checking service bindings - for (const workerOpts of allWorkerOpts) { - for (const designator of Object.values( - workerOpts.core.serviceBindings ?? {} - )) { - if (typeof designator !== "string") continue; - if (wrappedBindingWorkerNames.has(designator)) { - invalidWrappedAsBound(designator, "service"); - } - } - } - return wrappedBindingWorkerNames; -} - function getQueueProducers( allWorkerOpts: PluginWorkerOptions[] ): QueueProducers { @@ -789,13 +744,11 @@ function getQueueConsumers( // Collects all routes from all worker services function getWorkerRoutes( - allWorkerOpts: PluginWorkerOptions[], - wrappedBindingNames: Set + allWorkerOpts: PluginWorkerOptions[] ): Map { const allRoutes = new Map(); for (const workerOpts of allWorkerOpts) { const name = workerOpts.core.name ?? ""; - if (wrappedBindingNames.has(name)) continue; // Wrapped bindings un-routable assert(!allRoutes.has(name)); // Validated unique names earlier allRoutes.set(name, workerOpts.core.routes ?? []); } @@ -1001,8 +954,6 @@ export class Miniflare { #runtimeDispatcher?: Dispatcher; #proxyClient?: ProxyClient; - #structuredWorkerdLogs: boolean; - #cfObject?: Record = {}; // Path to temporary directory for use as scratch space/"in-memory" Durable @@ -1070,11 +1021,6 @@ export class Miniflare { this.#log = this.#sharedOpts.core.log ?? new NoOpLog(); this.#hyperdriveProxyController.log = this.#log; - this.#structuredWorkerdLogs = - this.#sharedOpts.core.structuredWorkerdLogs ?? - // If there is a `handleStructuredLogs` set then `structuredWorkerdLogs` defaults - // to `true`, otherwise it defaults to `false` - (this.#sharedOpts.core.handleStructuredLogs ? true : false); // If we're in a JavaScript Debug terminal, Miniflare will send the inspector ports directly to VSCode for registration // As such, we don't need our inspector proxy and in fact including it causes issue with multiple clients connected to the @@ -1154,7 +1100,7 @@ export class Miniflare { // project temp path is supplied, these live inside `#tmpPath` and are // already removed above. const emailPaths = getEmailPathsToClean( - this.#sharedOpts.core.defaultProjectTmpPath, + this.#sharedOpts.core.resourceTmpPath, this.#tmpPath ); if (emailPaths) { @@ -1331,13 +1277,11 @@ export class Miniflare { ); assert(namespaceId, "Namespace ID is required"); - const doSharedOpts = this.#sharedOpts.do; const coreSharedOpts = this.#sharedOpts.core; const doPersistPath = getPersistPath( DURABLE_OBJECTS_PLUGIN_NAME, this.#tmpPath, - coreSharedOpts.defaultPersistRoot, - doSharedOpts.durableObjectsPersist + coreSharedOpts.resourcePersistencePath ); const namespacePath = path.join(doPersistPath, namespaceId); @@ -1380,13 +1324,11 @@ export class Miniflare { ); assert(workflowName, "Workflow name is required"); - const workflowsSharedOpts = this.#sharedOpts.workflows; const coreSharedOpts = this.#sharedOpts.core; const workflowsPersistPath = getPersistPath( WORKFLOWS_PLUGIN_NAME, this.#tmpPath, - coreSharedOpts.defaultPersistRoot, - workflowsSharedOpts.workflowsPersist + coreSharedOpts.resourcePersistencePath ); // Engine DOs are stored under: /miniflare-workflows-/.sqlite @@ -1461,13 +1403,11 @@ export class Miniflare { assert(workflowName, "Workflow name is required"); - const workflowsSharedOpts = this.#sharedOpts.workflows; const coreSharedOpts = this.#sharedOpts.core; const workflowsPersistPath = getPersistPath( WORKFLOWS_PLUGIN_NAME, this.#tmpPath, - coreSharedOpts.defaultPersistRoot, - workflowsSharedOpts.workflowsPersist + coreSharedOpts.resourcePersistencePath ); const uniqueKey = `miniflare-workflows-${workflowName}`; @@ -1979,10 +1919,6 @@ export class Miniflare { : null; const durableObjectClassNames = getDurableObjectClassNames(allWorkerOpts); - const wrappedBindingNames = getWrappedBindingNames( - allWorkerOpts, - durableObjectClassNames - ); const queueProducers = getQueueProducers(allWorkerOpts); const queueConsumers = getQueueConsumers(allWorkerOpts); // When the dev registry is enabled, queue brokers bind to the dev-registry @@ -1990,7 +1926,7 @@ export class Miniflare { // (see `ExternalQueueProxy`), so the proxy worker must exist whenever // there are queues. const hasQueues = queueProducers.size > 0 || queueConsumers.size > 0; - const allWorkerRoutes = getWorkerRoutes(allWorkerOpts, wrappedBindingNames); + const allWorkerRoutes = getWorkerRoutes(allWorkerOpts); const workerNames = [...allWorkerRoutes.keys()]; // Use Map to dedupe services by name @@ -2029,12 +1965,6 @@ export class Miniflare { */ const proxyBindings: Worker_Binding[] = []; - const allWorkerBindings = new Map(); - const wrappedBindingsToPopulate: { - workerName: string; - innerBindings: Worker_Binding[]; - }[] = []; - for (const [key, plugin] of this.#mergedPluginEntries) { const pluginExtensions = await plugin.getExtensions?.({ // @ts-expect-error `CoreOptionsSchema` has required options which are @@ -2068,7 +1998,6 @@ export class Miniflare { // Collect all bindings from this worker const workerBindings: Worker_Binding[] = []; - allWorkerBindings.set(workerName, workerBindings); const additionalModules: Worker_Module[] = []; for (const [key, plugin] of this.#mergedPluginEntries) { @@ -2102,24 +2031,6 @@ export class Miniflare { if (isNativeTargetBinding(binding)) { proxyBindings.push(buildProxyBinding(key, workerName, binding)); } - // If this is a wrapped binding to a wrapped binding worker, record - // it, so we can populate its inner bindings with all the wrapped - // binding worker's bindings. - if ( - "wrapped" in binding && - binding.wrapped?.moduleName !== undefined && - binding.wrapped.innerBindings !== undefined - ) { - const workerName = maybeWrappedModuleToWorkerName( - binding.wrapped.moduleName - ); - if (workerName !== undefined) { - wrappedBindingsToPopulate.push({ - workerName, - innerBindings: binding.wrapped.innerBindings, - }); - } - } if ("service" in binding) { const targetWorkerName = binding.service?.name?.replace( "core:user:", @@ -2145,7 +2056,6 @@ export class Miniflare { } // Collect all services required by this worker - const unsafeStickyBlobs = sharedOpts.core.unsafeStickyBlobs ?? false; const unsafeEphemeralDurableObjects = workerOpts.core.unsafeEphemeralDurableObjects ?? false; // Store publicUrl so the /core/public-url loopback route can return it. @@ -2162,14 +2072,12 @@ export class Miniflare { workerIndex: i, additionalModules, tmpPath: this.#tmpPath, - defaultPersistRoot: sharedOpts.core.defaultPersistRoot, - defaultProjectTmpPath: sharedOpts.core.defaultProjectTmpPath, + resourcePersistencePath: sharedOpts.core.resourcePersistencePath, + resourceTmpPath: sharedOpts.core.resourceTmpPath, workerNames, loopbackHost, loopbackPort, publicUrl: sharedOpts.core.publicUrl, - unsafeStickyBlobs, - wrappedBindingNames, durableObjectClassNames, unsafeEphemeralDurableObjects, queueProducers, @@ -2392,35 +2300,16 @@ export class Miniflare { services.set(service.name, service); } - // Populate wrapped binding inner bindings with bound worker's bindings - for (const toPopulate of wrappedBindingsToPopulate) { - const bindings = allWorkerBindings.get(toPopulate.workerName); - if (bindings === undefined) continue; - const existingBindingNames = new Set( - toPopulate.innerBindings.map(({ name }) => name) - ); - toPopulate.innerBindings.push( - // If there's already an inner binding with this name, don't add again - ...bindings.filter(({ name }) => !existingBindingNames.has(name)) - ); - } - // If we populated wrapped bindings, we may have created cycles in the - // `services` array. Attempting to serialise these will lead to unbounded - // recursion, so make sure we don't have any const servicesArray = Array.from(services.values()); - if (wrappedBindingsToPopulate.length > 0 && _isCyclic(servicesArray)) { - throw new MiniflareCoreError( - "ERR_CYCLIC", - "Generated workerd config contains cycles. " + - "Ensure wrapped bindings don't have bindings to themselves." - ); - } return { services: servicesArray, sockets, extensions, - structuredLogging: this.#structuredWorkerdLogs, + // Structured logging is always enabled: workerd emits structured logs + // that Miniflare parses and either forwards to a `handleStructuredLogs` + // handler or writes to the console by default. + structuredLogging: true, autogates: process.env.MINIFLARE_WORKERD_AUTOGATES ? process.env.MINIFLARE_WORKERD_AUTOGATES.split(" ") : [], @@ -2509,7 +2398,6 @@ export class Miniflare { ? "127.0.0.1:0" : undefined, verbose: this.#sharedOpts.core.verbose, - handleRuntimeStdio: this.#sharedOpts.core.handleRuntimeStdio, handleStructuredLogs: this.#sharedOpts.core.handleStructuredLogs, runtimeEnv: this.#sharedOpts.core.unsafeRuntimeEnv, }; @@ -2844,9 +2732,6 @@ export class Miniflare { this.#workerOpts = workerOpts; this.#log = this.#sharedOpts.core.log ?? this.#log; this.#hyperdriveProxyController.log = this.#log; - this.#structuredWorkerdLogs = - this.#sharedOpts.core.structuredWorkerdLogs ?? - this.#structuredWorkerdLogs; const newExternalOnUpdate = sharedOpts.core.unsafeHandleDevRegistryUpdate; await this.#devRegistry.updateRegistryPath( @@ -3021,16 +2906,13 @@ export class Miniflare { const bindingName = CoreBindings.SERVICE_USER_ROUTE_PREFIX + workerName; const fetcher = proxyClient.env[bindingName]; - if (fetcher === undefined) { - // `#findAndAssertWorkerIndex()` will throw if a "worker" doesn't exist - // with the specified name. If this "worker" was used as a wrapped binding - // though, it won't be added as a service binding, and so will be - // undefined here. In this case, throw a more specific error. - const stringName = JSON.stringify(workerName); - throw new TypeError( - `${stringName} is being used as a wrapped binding, and cannot be accessed as a worker` - ); - } + // `#findAndAssertWorkerIndex()` throws if a worker doesn't exist with the + // specified name, so by this point the worker is guaranteed to have a + // corresponding route service binding. + assert( + fetcher !== undefined, + `Expected ${bindingName} service binding for worker ${JSON.stringify(workerName)}` + ); return fetcher as ReplaceWorkersTypes; } @@ -3241,8 +3123,7 @@ export class Miniflare { const durableObjectsPersistPath = getPersistPath( DURABLE_OBJECTS_PLUGIN_NAME, this.#tmpPath, - this.#sharedOpts.core.defaultPersistRoot, - this.#sharedOpts.do.durableObjectsPersist + this.#sharedOpts.core.resourcePersistencePath ); try { @@ -3346,17 +3227,6 @@ export class Miniflare { return this.#getProxy(`${pluginName}-internal`, className, serviceName); } - unsafeGetPersistPaths(): Map { - const result = new Map(); - for (const [key, plugin] of PLUGIN_ENTRIES) { - const sharedOpts = this.#sharedOpts[key]; - // @ts-expect-error `sharedOptions` will match the plugin's type here - const maybePath = plugin.getPersistPath?.(sharedOpts, this.#tmpPath); - if (maybePath !== undefined) result.set(key, maybePath); - } - return result; - } - get #mergedPluginEntries() { return [...PLUGIN_ENTRIES, ...this.#externalPlugins.entries()]; } @@ -3407,7 +3277,7 @@ export class Miniflare { // project temp path is supplied, these live inside `#tmpPath` and are // already removed above. const emailPaths = getEmailPathsToClean( - this.#sharedOpts.core.defaultProjectTmpPath, + this.#sharedOpts.core.resourceTmpPath, this.#tmpPath ); if (emailPaths) { diff --git a/packages/miniflare/src/plugins/analytics-engine/index.ts b/packages/miniflare/src/plugins/analytics-engine/index.ts index 98e48db482..74fcc66030 100644 --- a/packages/miniflare/src/plugins/analytics-engine/index.ts +++ b/packages/miniflare/src/plugins/analytics-engine/index.ts @@ -1,6 +1,6 @@ import ANALYTICS_ENGINE from "worker:analytics-engine/analytics-engine"; import { z } from "zod"; -import { PersistenceSchema, ProxyNodeBinding } from "../shared"; +import { ProxyNodeBinding } from "../shared"; import type { Worker_Binding } from "../../runtime"; import type { Plugin } from "../shared"; @@ -15,18 +15,12 @@ export const AnalyticsEngineSchemaOptionsSchema = z.object({ analyticsEngineDatasets: AnalyticsEngineSchema.optional(), }); -export const AnalyticsEngineSchemaSharedOptionsSchema = z.object({ - analyticsEngineDatasetsPersist: PersistenceSchema, -}); - export const ANALYTICS_ENGINE_PLUGIN_NAME = "analytics-engine"; export const ANALYTICS_ENGINE_PLUGIN: Plugin< - typeof AnalyticsEngineSchemaOptionsSchema, - typeof AnalyticsEngineSchemaSharedOptionsSchema + typeof AnalyticsEngineSchemaOptionsSchema > = { options: AnalyticsEngineSchemaOptionsSchema, - sharedOptions: AnalyticsEngineSchemaSharedOptionsSchema, bindingTypeDescription: "Analytics Engine dataset", async getBindings(options) { if (!options.analyticsEngineDatasets) { diff --git a/packages/miniflare/src/plugins/cache/index.ts b/packages/miniflare/src/plugins/cache/index.ts index 7f74670444..b7e7192c15 100644 --- a/packages/miniflare/src/plugins/cache/index.ts +++ b/packages/miniflare/src/plugins/cache/index.ts @@ -3,11 +3,10 @@ import SCRIPT_CACHE_OBJECT from "worker:cache/cache"; import SCRIPT_CACHE_ENTRY from "worker:cache/cache-entry"; import SCRIPT_CACHE_ENTRY_NOOP from "worker:cache/cache-entry-noop"; import { z } from "zod"; -import { CacheBindings, SharedBindings } from "../../workers"; +import { SharedBindings } from "../../workers"; import { getMiniflareObjectBindings, getPersistPath, - PersistenceSchema, SERVICE_LOOPBACK, } from "../shared"; import type { @@ -18,11 +17,7 @@ import type { import type { Plugin } from "../shared"; export const CacheOptionsSchema = z.object({ - cache: z.boolean().optional(), - cacheWarnUsage: z.boolean().optional(), -}); -export const CacheSharedOptionsSchema = z.object({ - cachePersist: PersistenceSchema, + cacheAPI: z.boolean().optional(), }); export const CACHE_PLUGIN_NAME = "cache"; @@ -39,12 +34,8 @@ export function getCacheServiceName(workerIndex: number) { return `${CACHE_PLUGIN_NAME}:${workerIndex}`; } -export const CACHE_PLUGIN: Plugin< - typeof CacheOptionsSchema, - typeof CacheSharedOptionsSchema -> = { +export const CACHE_PLUGIN: Plugin = { options: CacheOptionsSchema, - sharedOptions: CacheSharedOptionsSchema, getBindings() { return []; }, @@ -52,15 +43,12 @@ export const CACHE_PLUGIN: Plugin< return {}; }, async getServices({ - sharedOptions, options, workerIndex, tmpPath, - defaultPersistRoot, - unsafeStickyBlobs, + resourcePersistencePath, }) { - const cache = options.cache ?? true; - const cacheWarnUsage = options.cacheWarnUsage ?? false; + const cache = options.cacheAPI ?? true; let entryWorker: Worker; if (cache) { @@ -75,10 +63,6 @@ export const CACHE_PLUGIN: Plugin< name: SharedBindings.DURABLE_OBJECT_NAMESPACE_OBJECT, durableObjectNamespace: CACHE_OBJECT, }, - { - name: CacheBindings.MAYBE_JSON_CACHE_WARN_USAGE, - json: JSON.stringify(cacheWarnUsage), - }, ], }; } else { @@ -100,12 +84,10 @@ export const CACHE_PLUGIN: Plugin< if (cache) { const uniqueKey = `miniflare-${CACHE_OBJECT_CLASS_NAME}`; - const persist = sharedOptions.cachePersist; const persistPath = getPersistPath( CACHE_PLUGIN_NAME, tmpPath, - defaultPersistRoot, - persist + resourcePersistencePath ); await fs.mkdir(persistPath, { recursive: true }); const storageService: Service = { @@ -141,7 +123,7 @@ export const CACHE_PLUGIN: Plugin< name: SharedBindings.MAYBE_SERVICE_LOOPBACK, service: { name: SERVICE_LOOPBACK }, }, - ...getMiniflareObjectBindings(unsafeStickyBlobs), + ...getMiniflareObjectBindings(), ], }, }; @@ -153,7 +135,4 @@ export const CACHE_PLUGIN: Plugin< return services; }, - getPersistPath({ cachePersist }, tmpPath) { - return getPersistPath(CACHE_PLUGIN_NAME, tmpPath, undefined, cachePersist); - }, }; diff --git a/packages/miniflare/src/plugins/core/index.ts b/packages/miniflare/src/plugins/core/index.ts index 9e1f1d6991..fb8d895371 100644 --- a/packages/miniflare/src/plugins/core/index.ts +++ b/packages/miniflare/src/plugins/core/index.ts @@ -10,17 +10,14 @@ import { } from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; -import { Readable } from "node:stream"; import tls from "node:tls"; import { TextEncoder } from "node:util"; import { DEFAULT_CONTAINER_EGRESS_INTERCEPTOR_IMAGE } from "@cloudflare/containers-shared"; import { getTodaysCompatDate, removeDirSync } from "@cloudflare/workers-utils"; -import { MockAgent } from "undici"; import SCRIPT_DEV_CONTROL from "worker:core/dev-control"; import SCRIPT_ENTRY from "worker:core/entry"; import OUTBOUND_WORKER from "worker:core/outbound"; import { z } from "zod"; -import { fetch } from "../../http"; import { kVoid } from "../../runtime"; import { JsonSchema, Log, MiniflareCoreError, PathSchema } from "../../shared"; import { getDevControlDurableObjectBindingName } from "../../shared/dev-control"; @@ -122,16 +119,6 @@ if (process.env.NODE_EXTRA_CA_CERTS !== undefined) { const encoder = new TextEncoder(); -export function createFetchMock() { - return new MockAgent(); -} - -const WrappedBindingSchema = z.object({ - scriptName: z.string(), - entrypoint: z.string().optional(), - bindings: z.record(z.string(), JsonSchema).optional(), -}); - // Validate as string, but don't include in parsed output const UnusableStringSchema = z.string().transform(() => undefined); @@ -170,12 +157,8 @@ const CoreOptionsSchemaInput = z.intersection( .record(z.string(), z.union([PathSchema, z.instanceof(Uint8Array)])) .optional(), serviceBindings: z.record(z.string(), ServiceDesignatorSchema).optional(), - wrappedBindings: z - .record(z.string(), z.union([z.string(), WrappedBindingSchema])) - .optional(), outboundService: ServiceDesignatorSchema.optional(), - fetchMock: z.instanceof(MockAgent).optional(), // TODO(soon): remove this in favour of per-object `unsafeUniqueKey: kEphemeralUniqueKey` unsafeEphemeralDurableObjects: z.boolean().optional(), @@ -214,19 +197,6 @@ const CoreOptionsSchemaInput = z.intersection( // If not specified, defaults to `${worker-name}.example.com` zone: z.string().optional(), - /** Configuration used to connect to the container engine */ - containerEngine: z - .union([ - z.object({ - localDocker: z.object({ - socketPath: z.string(), - containerEgressInterceptorImage: z.string().optional(), - }), - }), - z.string(), - ]) - .optional(), - unsafeBindings: z .array( z.object({ @@ -239,24 +209,7 @@ const CoreOptionsSchemaInput = z.intersection( .optional(), }) ); -export const CoreOptionsSchema = CoreOptionsSchemaInput.transform((value) => { - const fetchMock = value.fetchMock; - if (fetchMock !== undefined) { - if (value.outboundService !== undefined) { - throw new MiniflareCoreError( - "ERR_MULTIPLE_OUTBOUNDS", - "Only one of `outboundService` or `fetchMock` may be specified per worker" - ); - } - - // The `fetchMock` option is used to construct the `outboundService` only - // Removing it from the output allows us to re-parse the options later - // This allows us to validate the options and then feed them into Miniflare without issue. - value.fetchMock = undefined; - value.outboundService = (req) => fetch(req, { dispatcher: fetchMock }); - } - return value; -}); +export const CoreOptionsSchema = CoreOptionsSchemaInput; export type WorkerdStructuredLog = z.infer; @@ -266,126 +219,113 @@ export const WorkerdStructuredLogSchema = z.object({ message: z.string(), }); -export const CoreSharedOptionsSchema = z - .object({ - rootPath: UnusableStringSchema.optional(), - - host: z.string().optional(), - port: z.number().optional(), - - https: z.boolean().optional(), - httpsKey: z.string().optional(), - httpsKeyPath: z.string().optional(), - httpsCert: z.string().optional(), - httpsCertPath: z.string().optional(), - - inspectorPort: z.number().optional(), - inspectorHost: z.string().optional(), - - verbose: z.boolean().optional(), - - log: z.instanceof(Log).optional(), - handleRuntimeStdio: z - .function({ - input: [z.instanceof(Readable), z.instanceof(Readable)], - }) - .optional(), - - handleStructuredLogs: z - .function({ - input: [WorkerdStructuredLogSchema], - output: z.void(), - }) - .optional(), - - // Deliberately not `z.function()`: parsing that schema replaces the - // callback with a validating wrapper. When the callback is `async` - // (assignable to a `void` return), the wrapper calls it, rejects the - // returned promise as an invalid `void` return value, and drops that - // promise un-awaited — so if the callback later rejects, no caller - // holds the promise and the rejection crashes the process as an - // unhandled rejection. `z.custom()` passes the function through - // unwrapped; the call site in `handlePrettyErrorRequest` contains - // both throwing and rejecting callbacks. - handleUncaughtError: z - .custom<(error: Error) => void>((value) => typeof value === "function") - .optional(), - - upstream: z.string().optional(), - // TODO: add back validation of cf object - cf: z - .union([z.boolean(), z.string(), z.record(z.string(), z.any())]) - .optional(), +export const CoreSharedOptionsSchema = z.object({ + rootPath: UnusableStringSchema.optional(), - // Enable auto service / durable objects discovery with the dev registry - unsafeDevRegistryPath: z.string().optional(), - // Called when external workers this instance depends on are updated in the dev registry - unsafeHandleDevRegistryUpdate: z - .function({ - input: [z.custom()], - }) - .optional(), - // This is a shared secret between a proxy server and miniflare that can be - // passed in a header to prove that the request came from the proxy and not - // some malicious attacker. - unsafeProxySharedSecret: z.string().optional(), - unsafeModuleFallbackService: CustomFetchServiceSchema.optional(), - // Keep blobs when deleting/overwriting keys, required for stacked storage - unsafeStickyBlobs: z.boolean().optional(), - // Enable directly triggering user Worker handlers with paths like `/cdn-cgi/local/scheduled` - unsafeTriggerHandlers: z.boolean().optional(), - // Extra environment variables to set on the spawned `workerd` subprocess. - // Merged on top of `process.env` and Miniflare's own defaults - // (e.g. `TZ=UTC`, `FORCE_COLOR`), so callers can override those defaults - // (for example, to test timezone-dependent behaviour). - unsafeRuntimeEnv: z.record(z.string(), z.string()).optional(), - // Enable the local explorer at /cdn-cgi/local/explorer - unsafeLocalExplorer: z.boolean().optional(), - // Enable RPC-based Durable Object introspection APIs - unsafeInspectDurableObjects: z.boolean().optional(), - // Enable logging requests - logRequests: z.boolean().default(true), - - // Path to the root directory for persisting data - // Used as the default for all plugins with the plugin name as the subdirectory name - defaultPersistRoot: z.string().optional(), - // Path to the project temporary directory for plugins that need it - // (e.g. email logs). Falls back to a subdirectory of tmpPath if not set. - defaultProjectTmpPath: z.string().optional(), - // Strip the MF-DISABLE_PRETTY_ERROR header from user request - stripDisablePrettyError: z.boolean().default(true), - - // Whether to get structured logs from workerd or not (defaults to `true` is a - // `handleStructuredLogs` is set, to `false` otherwise) - // This option is useful in combination with a custom handleRuntimeStdio. - structuredWorkerdLogs: z.boolean().optional(), - - // Enable telemetry for the local explorer. - telemetry: z - .object({ - enabled: z.boolean().default(false), - deviceId: z.string().optional(), - }) - .default({ enabled: false }), + host: z.string().optional(), + port: z.number().optional(), - // The stable, externally-reachable URL for this Miniflare instance - // (e.g. the Wrangler proxy URL or Vite dev server URL). Used by - // plugins like Stream to generate preview URLs that outlive runtime - // restarts. If not set, plugins fall back to the runtime entry URL. - publicUrl: z.url().optional(), - }) - .refine( - ({ structuredWorkerdLogs, handleStructuredLogs }) => { - if (structuredWorkerdLogs === false && handleStructuredLogs) { - return false; - } - return true; - }, - { - message: - "A `handleStructuredLogs` has been provided but `structuredWorkerdLogs` is set to `false`", - } - ); + https: z.boolean().optional(), + httpsKey: z.string().optional(), + httpsCert: z.string().optional(), + + inspectorPort: z.number().optional(), + inspectorHost: z.string().optional(), + + verbose: z.boolean().optional(), + + log: z.instanceof(Log).optional(), + + handleStructuredLogs: z + .function({ + input: [WorkerdStructuredLogSchema], + output: z.void(), + }) + .optional(), + + // Deliberately not `z.function()`: parsing that schema replaces the + // callback with a validating wrapper. When the callback is `async` + // (assignable to a `void` return), the wrapper calls it, rejects the + // returned promise as an invalid `void` return value, and drops that + // promise un-awaited — so if the callback later rejects, no caller + // holds the promise and the rejection crashes the process as an + // unhandled rejection. `z.custom()` passes the function through + // unwrapped; the call site in `handlePrettyErrorRequest` contains + // both throwing and rejecting callbacks. + handleUncaughtError: z + .custom<(error: Error) => void>((value) => typeof value === "function") + .optional(), + + upstream: z.string().optional(), + // TODO: add back validation of cf object + cf: z + .union([z.boolean(), z.string(), z.record(z.string(), z.any())]) + .optional(), + + // Enable auto service / durable objects discovery with the dev registry + unsafeDevRegistryPath: z.string().optional(), + // Called when external workers this instance depends on are updated in the dev registry + unsafeHandleDevRegistryUpdate: z + .function({ + input: [z.custom()], + }) + .optional(), + // This is a shared secret between a proxy server and miniflare that can be + // passed in a header to prove that the request came from the proxy and not + // some malicious attacker. + unsafeProxySharedSecret: z.string().optional(), + unsafeModuleFallbackService: CustomFetchServiceSchema.optional(), + // Enable directly triggering user Worker handlers with paths like `/cdn-cgi/local/scheduled` + unsafeTriggerHandlers: z.boolean().optional(), + // Extra environment variables to set on the spawned `workerd` subprocess. + // Merged on top of `process.env` and Miniflare's own defaults + // (e.g. `TZ=UTC`, `FORCE_COLOR`), so callers can override those defaults + // (for example, to test timezone-dependent behaviour). + unsafeRuntimeEnv: z.record(z.string(), z.string()).optional(), + // Enable the local explorer at /cdn-cgi/local/explorer + unsafeLocalExplorer: z.boolean().optional(), + // Enable RPC-based Durable Object introspection APIs + unsafeInspectDurableObjects: z.boolean().optional(), + // Enable logging requests + logRequests: z.boolean().default(true), + + // Path to the root directory for persisting resource data (e.g. `.wrangler/state/v3`). + // Each plugin persists under a subdirectory named after the plugin. When unset, + // persistence is disabled and data is stored in an ephemeral tmp directory. + resourcePersistencePath: z.string().optional(), + // Path to the project temporary directory for plugins that need it + // (e.g. `.wrangler/tmp` for email logs). Falls back to a subdirectory of tmpPath if not set. + resourceTmpPath: z.string().optional(), + // Strip the MF-DISABLE_PRETTY_ERROR header from user request + stripDisablePrettyError: z.boolean().default(true), + + // Enable telemetry for the local explorer. + telemetry: z + .object({ + enabled: z.boolean().default(false), + deviceId: z.string().optional(), + }) + .default({ enabled: false }), + + // The stable, externally-reachable URL for this Miniflare instance + // (e.g. the Wrangler proxy URL or Vite dev server URL). Used by + // plugins like Stream to generate preview URLs that outlive runtime + // restarts. If not set, plugins fall back to the runtime entry URL. + publicUrl: z.url().optional(), + + /** Configuration used to connect to the container engine */ + containerEngine: z + .union([ + z.object({ + localDocker: z.object({ + socketPath: z.string(), + containerEgressInterceptorImage: z.string().optional(), + }), + }), + z.string(), + ]) + .optional(), +}); export const CORE_PLUGIN_NAME = "core"; @@ -583,18 +523,6 @@ function getDevControlBindings( return Array.from(bindings.values()); } -const WRAPPED_MODULE_PREFIX = "miniflare-internal:wrapped:"; -function workerNameToWrappedModule(workerName: string): string { - return WRAPPED_MODULE_PREFIX + workerName; -} -export function maybeWrappedModuleToWorkerName( - name: string -): string | undefined { - if (name.startsWith(WRAPPED_MODULE_PREFIX)) { - return name.substring(WRAPPED_MODULE_PREFIX.length); - } -} - function getOutboundInterceptorName(workerIndex: number) { return `outbound:${workerIndex}`; } @@ -669,29 +597,6 @@ export const CORE_PLUGIN: Plugin< }) ); } - if (options.wrappedBindings !== undefined) { - bindings.push( - ...Object.entries(options.wrappedBindings).map(([name, designator]) => { - // Normalise designator - const isObject = typeof designator === "object"; - const scriptName = isObject ? designator.scriptName : designator; - const entrypoint = isObject ? designator.entrypoint : undefined; - const bindings = isObject ? designator.bindings : undefined; - - // Build binding - const moduleName = workerNameToWrappedModule(scriptName); - const innerBindings = - bindings === undefined ? [] : buildBindings(bindings); - // `scriptName`'s bindings will be added to `innerBindings` when - // assembling the config - return { - name, - wrapped: { moduleName, entrypoint, innerBindings }, - }; - }) - ); - } - if (options.unsafeEvalBinding !== undefined) { bindings.push({ name: options.unsafeEvalBinding, @@ -747,15 +652,6 @@ export const CORE_PLUGIN: Plugin< ]) ); } - if (options.wrappedBindings !== undefined) { - bindingEntries.push( - ...Object.keys(options.wrappedBindings).map((name) => [ - name, - new ProxyNodeBinding(), - ]) - ); - } - return Object.fromEntries(await Promise.all(bindingEntries)); }, async getServices({ @@ -764,7 +660,6 @@ export const CORE_PLUGIN: Plugin< sharedOptions, workerBindings, workerIndex, - wrappedBindingNames, durableObjectClassNames, additionalModules, loopbackHost, @@ -837,119 +732,75 @@ export const CORE_PLUGIN: Plugin< options.compatibilityDate ?? FALLBACK_COMPATIBILITY_DATE ); - const isWrappedBinding = wrappedBindingNames.has(name); - const services: Service[] = []; const extensions: Extension[] = []; - if (isWrappedBinding) { - const stringName = JSON.stringify(name); - function invalidWrapped(reason: string): never { - const message = `Cannot use ${stringName} for wrapped binding because ${reason}`; - throw new MiniflareCoreError("ERR_INVALID_WRAPPED", message); - } - if (workerIndex === 0) { - invalidWrapped( - `it's the entrypoint.\nEnsure ${stringName} isn't the first entry in the \`workers\` array.` - ); - } - if (!("modules" in workerScript)) { - invalidWrapped( - `it's a service worker.\nEnsure ${stringName} sets \`modules\` to \`true\` or an array of modules` - ); - } - if (workerScript.modules.length !== 1) { - invalidWrapped( - `it isn't a single module.\nEnsure ${stringName} doesn't include unbundled \`import\`s.` - ); - } - const firstModule = workerScript.modules[0]; - if (!("esModule" in firstModule)) { - invalidWrapped("it isn't a single ES module"); - } - if (options.compatibilityDate !== undefined) { - invalidWrapped( - "it defines a compatibility date.\nWrapped bindings use the compatibility date of the worker with the binding." - ); - } - if (options.compatibilityFlags?.length) { - invalidWrapped( - "it defines compatibility flags.\nWrapped bindings use the compatibility flags of the worker with the binding." - ); - } - if (options.outboundService !== undefined) { - invalidWrapped( - "it defines an outbound service.\nWrapped bindings use the outbound service of the worker with the binding." - ); - } - // We validate this "worker" isn't bound to for services/Durable Objects - // in `getWrappedBindingNames()`. - - extensions.push({ - modules: [ - { - name: workerNameToWrappedModule(name), - esModule: firstModule.esModule, - internal: true, - }, - ], - }); - } else { - services.push({ - name: serviceName, - worker: { - ...workerScript, - compatibilityDate, - compatibilityFlags: options.compatibilityFlags, - bindings: workerBindings, - durableObjectNamespaces: - classNamesEntries.map( - ([ + services.push({ + name: serviceName, + worker: { + ...workerScript, + compatibilityDate, + compatibilityFlags: options.compatibilityFlags, + bindings: workerBindings, + durableObjectNamespaces: + classNamesEntries.map( + ([ + className, + { + enableSql, + unsafeUniqueKey, + unsafePreventEviction: preventEviction, + container, + }, + ]) => { + const uniqueKey = getDurableObjectUniqueKey( className, - { - enableSql, - unsafeUniqueKey, - unsafePreventEviction: preventEviction, - container, - }, - ]) => { - const uniqueKey = getDurableObjectUniqueKey( - className, - options.name, - unsafeUniqueKey - ); - - return uniqueKey === undefined - ? { - className, - enableSql, - ephemeralLocal: kVoid, - preventEviction, - container, - } - : { - className, - enableSql, - uniqueKey, - preventEviction, - container, - }; - } - ), - durableObjectStorage: - classNamesEntries.length === 0 - ? undefined - : options.unsafeEphemeralDurableObjects - ? { inMemory: kVoid } - : { localDisk: DURABLE_OBJECTS_STORAGE_SERVICE_NAME }, - globalOutbound: { name: getOutboundInterceptorName(workerIndex) }, - cacheApiOutbound: { name: getCacheServiceName(workerIndex) }, - moduleFallback: - options.unsafeUseModuleFallbackService && - sharedOptions.unsafeModuleFallbackService !== undefined - ? `${loopbackHost}:${loopbackPort}` - : undefined, - tails: options.tails?.map((service) => { + options.name, + unsafeUniqueKey + ); + + return uniqueKey === undefined + ? { + className, + enableSql, + ephemeralLocal: kVoid, + preventEviction, + container, + } + : { + className, + enableSql, + uniqueKey, + preventEviction, + container, + }; + } + ), + durableObjectStorage: + classNamesEntries.length === 0 + ? undefined + : options.unsafeEphemeralDurableObjects + ? { inMemory: kVoid } + : { localDisk: DURABLE_OBJECTS_STORAGE_SERVICE_NAME }, + globalOutbound: { name: getOutboundInterceptorName(workerIndex) }, + cacheApiOutbound: { name: getCacheServiceName(workerIndex) }, + moduleFallback: + options.unsafeUseModuleFallbackService && + sharedOptions.unsafeModuleFallbackService !== undefined + ? `${loopbackHost}:${loopbackPort}` + : undefined, + tails: options.tails?.map((service) => { + return getCustomServiceDesignator( + /* referrer */ options.name, + workerIndex, + CustomServiceKind.UNKNOWN, + name, + service, + options.hasAssetsAndIsVitest + ); + }), + streamingTails: options.streamingTails?.map( + (service) => { return getCustomServiceDesignator( /* referrer */ options.name, workerIndex, @@ -958,23 +809,11 @@ export const CORE_PLUGIN: Plugin< service, options.hasAssetsAndIsVitest ); - }), - streamingTails: options.streamingTails?.map( - (service) => { - return getCustomServiceDesignator( - /* referrer */ options.name, - workerIndex, - CustomServiceKind.UNKNOWN, - name, - service, - options.hasAssetsAndIsVitest - ); - } - ), - containerEngine: getContainerEngine(options.containerEngine), - }, - }); - } + } + ), + containerEngine: getContainerEngine(sharedOptions.containerEngine), + }, + }); // Define custom `fetch` services if set if (options.serviceBindings !== undefined) { diff --git a/packages/miniflare/src/plugins/d1/index.ts b/packages/miniflare/src/plugins/d1/index.ts index ab3f73da53..77f0a6c4dc 100644 --- a/packages/miniflare/src/plugins/d1/index.ts +++ b/packages/miniflare/src/plugins/d1/index.ts @@ -10,7 +10,6 @@ import { namespaceEntries, namespaceKeys, objectEntryWorker, - PersistenceSchema, ProxyNodeBinding, remoteProxyClientWorker, SERVICE_LOOPBACK, @@ -41,10 +40,6 @@ export const D1OptionsSchema = z.object({ ]) .optional(), }); -export const D1SharedOptionsSchema = z.object({ - d1Persist: PersistenceSchema, -}); - export const D1_PLUGIN_NAME = "d1"; const D1_STORAGE_SERVICE_NAME = `${D1_PLUGIN_NAME}:storage`; const D1_DATABASE_SERVICE_PREFIX = `${D1_PLUGIN_NAME}:db`; @@ -54,12 +49,8 @@ const D1_DATABASE_OBJECT: Worker_Binding_DurableObjectNamespaceDesignator = { className: D1_DATABASE_OBJECT_CLASS_NAME, }; -export const D1_PLUGIN: Plugin< - typeof D1OptionsSchema, - typeof D1SharedOptionsSchema -> = { +export const D1_PLUGIN: Plugin = { options: D1OptionsSchema, - sharedOptions: D1SharedOptionsSchema, bindingTypeDescription: "D1 database", getBindings(options) { const databases = namespaceEntries(options.d1Databases); @@ -108,14 +99,7 @@ export const D1_PLUGIN: Plugin< databases.map((name) => [name, new ProxyNodeBinding()]) ); }, - async getServices({ - options, - sharedOptions, - tmpPath, - defaultPersistRoot, - unsafeStickyBlobs, - }) { - const persist = sharedOptions.d1Persist; + async getServices({ options, tmpPath, resourcePersistencePath }) { const databases = namespaceEntries(options.d1Databases); const services = databases.map( ([name, { id, remoteProxyConnectionString }]) => ({ @@ -135,8 +119,7 @@ export const D1_PLUGIN: Plugin< const persistPath = getPersistPath( D1_PLUGIN_NAME, tmpPath, - defaultPersistRoot, - persist + resourcePersistencePath ); await fs.mkdir(persistPath, { recursive: true }); @@ -173,7 +156,7 @@ export const D1_PLUGIN: Plugin< name: SharedBindings.MAYBE_SERVICE_LOOPBACK, service: { name: SERVICE_LOOPBACK }, }, - ...getMiniflareObjectBindings(unsafeStickyBlobs), + ...getMiniflareObjectBindings(), ], }, }; @@ -182,7 +165,4 @@ export const D1_PLUGIN: Plugin< return services; }, - getPersistPath({ d1Persist }, tmpPath) { - return getPersistPath(D1_PLUGIN_NAME, tmpPath, undefined, d1Persist); - }, }; diff --git a/packages/miniflare/src/plugins/do/index.ts b/packages/miniflare/src/plugins/do/index.ts index 9a847d1764..8efbdeadcb 100644 --- a/packages/miniflare/src/plugins/do/index.ts +++ b/packages/miniflare/src/plugins/do/index.ts @@ -4,7 +4,6 @@ import { getUserServiceName } from "../core"; import { getPersistPath, kUnsafeEphemeralUniqueKey, - PersistenceSchema, ProxyNodeBinding, } from "../shared"; import type { Worker_Binding } from "../../runtime"; @@ -52,9 +51,6 @@ export const DurableObjectsOptionsSchema = z.object({ // These might just be configured via migrations, but should still be allocated storage for e.g. ctx.exports support additionalUnboundDurableObjects: z.array(DurableObject).optional(), }); -export const DurableObjectsSharedOptionsSchema = z.object({ - durableObjectsPersist: PersistenceSchema, -}); export function normaliseDurableObject( designator: NonNullable< @@ -115,11 +111,9 @@ export const DURABLE_OBJECTS_PLUGIN_NAME = "do"; export const DURABLE_OBJECTS_STORAGE_SERVICE_NAME = `${DURABLE_OBJECTS_PLUGIN_NAME}:storage`; export const DURABLE_OBJECTS_PLUGIN: Plugin< - typeof DurableObjectsOptionsSchema, - typeof DurableObjectsSharedOptionsSchema + typeof DurableObjectsOptionsSchema > = { options: DurableObjectsOptionsSchema, - sharedOptions: DurableObjectsSharedOptionsSchema, bindingTypeDescription: "Durable Object namespace", getBindings(options) { return Object.entries(options.durableObjects ?? {}).map( @@ -139,9 +133,8 @@ export const DURABLE_OBJECTS_PLUGIN: Plugin< ); }, async getServices({ - sharedOptions, tmpPath, - defaultPersistRoot, + resourcePersistencePath, durableObjectClassNames, unsafeEphemeralDurableObjects, }) { @@ -164,8 +157,7 @@ export const DURABLE_OBJECTS_PLUGIN: Plugin< const storagePath = getPersistPath( DURABLE_OBJECTS_PLUGIN_NAME, tmpPath, - defaultPersistRoot, - sharedOptions.durableObjectsPersist + resourcePersistencePath ); // `workerd` requires the `disk.path` to exist. Setting `recursive: true` // is like `mkdir -p`: it won't fail if the directory already exists, and it @@ -181,12 +173,4 @@ export const DURABLE_OBJECTS_PLUGIN: Plugin< }, ]; }, - getPersistPath({ durableObjectsPersist }, tmpPath) { - return getPersistPath( - DURABLE_OBJECTS_PLUGIN_NAME, - tmpPath, - undefined, - durableObjectsPersist - ); - }, }; diff --git a/packages/miniflare/src/plugins/email/index.ts b/packages/miniflare/src/plugins/email/index.ts index 908d19c44e..b5857b4307 100644 --- a/packages/miniflare/src/plugins/email/index.ts +++ b/packages/miniflare/src/plugins/email/index.ts @@ -55,27 +55,27 @@ function buildJsonBindings(bindings: Record): Worker_Binding[] { } function getEmailProjectParentDirectory( - defaultProjectTmpPath: string | undefined + resourceTmpPath: string | undefined ): string | undefined { - if (defaultProjectTmpPath === undefined) { + if (resourceTmpPath === undefined) { return undefined; } - return path.join(defaultProjectTmpPath, EMAIL_PLUGIN_NAME); + return path.join(resourceTmpPath, EMAIL_PLUGIN_NAME); } /** * Returns the session directory for email files. - * Path: `/email/` + * Path: `/email/` * Example: `/path/to/project/.wrangler/tmp/email/dev-abc123` * When an email is logged, it is stored under this directory using a type indicator * and a unique ID. * Path: `//.` */ function getEmailProjectSessionDirectory( - defaultProjectTmpPath: string | undefined, + resourceTmpPath: string | undefined, tmpPath: string ): string | undefined { - const parentDir = getEmailProjectParentDirectory(defaultProjectTmpPath); + const parentDir = getEmailProjectParentDirectory(resourceTmpPath); if (parentDir === undefined) { return undefined; } @@ -83,17 +83,14 @@ function getEmailProjectSessionDirectory( } export function getEmailPathsToClean( - defaultProjectTmpPath: string | undefined, + resourceTmpPath: string | undefined, tmpPath: string ): { sessionDir: string; parentDir: string } | undefined { - if (defaultProjectTmpPath === undefined) { + if (resourceTmpPath === undefined) { return undefined; } - const sessionDir = getEmailProjectSessionDirectory( - defaultProjectTmpPath, - tmpPath - ); - const parentDir = getEmailProjectParentDirectory(defaultProjectTmpPath); + const sessionDir = getEmailProjectSessionDirectory(resourceTmpPath, tmpPath); + const parentDir = getEmailProjectParentDirectory(resourceTmpPath); if (sessionDir === undefined || parentDir === undefined) { return undefined; } @@ -140,7 +137,7 @@ export const EMAIL_PLUGIN: Plugin = { await mkdir(emailSystemDirectory, { recursive: true }); // Map binding disk services to names and paths, for concise access when storing emails as files. - // When defaultProjectTmpPath is unset, only create system service to avoid duplicates + // When resourceTmpPath is unset, only create system service to avoid duplicates const diskServices: Array<{ location: "system" | "project"; bindingName: string; @@ -155,9 +152,9 @@ export const EMAIL_PLUGIN: Plugin = { }, ]; - if (args.defaultProjectTmpPath) { + if (args.resourceTmpPath) { const emailProjectSessionDirectory = getEmailProjectSessionDirectory( - args.defaultProjectTmpPath, + args.resourceTmpPath, args.tmpPath ); if (emailProjectSessionDirectory !== undefined) { diff --git a/packages/miniflare/src/plugins/hello-world/index.ts b/packages/miniflare/src/plugins/hello-world/index.ts index 3b3d1fc110..25317de453 100644 --- a/packages/miniflare/src/plugins/hello-world/index.ts +++ b/packages/miniflare/src/plugins/hello-world/index.ts @@ -6,7 +6,6 @@ import { SharedBindings } from "../../workers"; import { getMiniflareObjectBindings, getPersistPath, - PersistenceSchema, ProxyNodeBinding, SERVICE_LOOPBACK, } from "../shared"; @@ -26,16 +25,8 @@ export const HelloWorldOptionsSchema = z.object({ .optional(), }); -export const HelloWorldSharedOptionsSchema = z.object({ - helloWorldPersist: PersistenceSchema, -}); - -export const HELLO_WORLD_PLUGIN: Plugin< - typeof HelloWorldOptionsSchema, - typeof HelloWorldSharedOptionsSchema -> = { +export const HELLO_WORLD_PLUGIN: Plugin = { options: HelloWorldOptionsSchema, - sharedOptions: HelloWorldSharedOptionsSchema, bindingTypeDescription: "Hello World", async getBindings(options) { if (!options.helloWorld) { @@ -66,13 +57,7 @@ export const HELLO_WORLD_PLUGIN: Plugin< ]) ); }, - async getServices({ - options, - sharedOptions, - tmpPath, - defaultPersistRoot, - unsafeStickyBlobs, - }) { + async getServices({ options, tmpPath, resourcePersistencePath }) { const configs = options.helloWorld ? Object.values(options.helloWorld) : []; if (configs.length === 0) { @@ -82,8 +67,7 @@ export const HELLO_WORLD_PLUGIN: Plugin< const persistPath = getPersistPath( HELLO_WORLD_PLUGIN_NAME, tmpPath, - defaultPersistRoot, - sharedOptions.helloWorldPersist + resourcePersistencePath ); await fs.mkdir(persistPath, { recursive: true }); @@ -120,7 +104,7 @@ export const HELLO_WORLD_PLUGIN: Plugin< name: SharedBindings.MAYBE_SERVICE_LOOPBACK, service: { name: SERVICE_LOOPBACK }, }, - ...getMiniflareObjectBindings(unsafeStickyBlobs), + ...getMiniflareObjectBindings(), ], }, } satisfies Service; @@ -152,12 +136,4 @@ export const HELLO_WORLD_PLUGIN: Plugin< return [...services, storageService, objectService]; }, - getPersistPath(sharedOptions, tmpPath) { - return getPersistPath( - HELLO_WORLD_PLUGIN_NAME, - tmpPath, - undefined, - sharedOptions.helloWorldPersist - ); - }, }; diff --git a/packages/miniflare/src/plugins/images/index.ts b/packages/miniflare/src/plugins/images/index.ts index c729aa38e7..9c15022ffb 100644 --- a/packages/miniflare/src/plugins/images/index.ts +++ b/packages/miniflare/src/plugins/images/index.ts @@ -9,7 +9,6 @@ import { getPersistPath, getUserBindingServiceName, objectEntryWorker, - PersistenceSchema, ProxyNodeBinding, remoteProxyClientWorker, SERVICE_LOOPBACK, @@ -29,18 +28,10 @@ export const ImagesOptionsSchema = z.object({ images: ImagesSchema.optional(), }); -export const ImagesSharedOptionsSchema = z.object({ - imagesPersist: PersistenceSchema, -}); - export const IMAGES_PLUGIN_NAME = "images"; -export const IMAGES_PLUGIN: Plugin< - typeof ImagesOptionsSchema, - typeof ImagesSharedOptionsSchema -> = { +export const IMAGES_PLUGIN: Plugin = { options: ImagesOptionsSchema, - sharedOptions: ImagesSharedOptionsSchema, bindingTypeDescription: "Images", async getBindings(options) { if (!options.images) { @@ -76,13 +67,7 @@ export const IMAGES_PLUGIN: Plugin< [options.images.binding]: new ProxyNodeBinding(), }; }, - async getServices({ - options, - sharedOptions, - tmpPath, - defaultPersistRoot, - unsafeStickyBlobs, - }) { + async getServices({ options, tmpPath, resourcePersistencePath }) { if (!options.images) { return []; } @@ -108,8 +93,7 @@ export const IMAGES_PLUGIN: Plugin< const persistPath = getPersistPath( IMAGES_PLUGIN_NAME, tmpPath, - defaultPersistRoot, - sharedOptions.imagesPersist + resourcePersistencePath ); await fs.mkdir(persistPath, { recursive: true }); @@ -146,7 +130,7 @@ export const IMAGES_PLUGIN: Plugin< name: SharedBindings.MAYBE_SERVICE_LOOPBACK, service: { name: SERVICE_LOOPBACK }, }, - ...getMiniflareObjectBindings(unsafeStickyBlobs), + ...getMiniflareObjectBindings(), ], }, } satisfies Service; @@ -184,12 +168,4 @@ export const IMAGES_PLUGIN: Plugin< return [storageService, objectService, kvNamespaceService, imagesService]; }, - getPersistPath({ imagesPersist }, tmpPath) { - return getPersistPath( - IMAGES_PLUGIN_NAME, - tmpPath, - undefined, - imagesPersist - ); - }, }; diff --git a/packages/miniflare/src/plugins/index.ts b/packages/miniflare/src/plugins/index.ts index f60738d9d2..3dca3daa67 100644 --- a/packages/miniflare/src/plugins/index.ts +++ b/packages/miniflare/src/plugins/index.ts @@ -194,7 +194,6 @@ export { CoreOptionsSchema, CoreSharedOptionsSchema, compileModuleRules, - createFetchMock, getGlobalServices, ModuleRuleTypeSchema, ModuleRuleSchema, diff --git a/packages/miniflare/src/plugins/kv/index.ts b/packages/miniflare/src/plugins/kv/index.ts index c84af9e01f..45ebc094e9 100644 --- a/packages/miniflare/src/plugins/kv/index.ts +++ b/packages/miniflare/src/plugins/kv/index.ts @@ -10,7 +10,6 @@ import { namespaceEntries, namespaceKeys, objectEntryWorker, - PersistenceSchema, ProxyNodeBinding, remoteProxyClientWorker, SERVICE_LOOPBACK, @@ -53,10 +52,6 @@ export const KVOptionsSchema = z.object({ siteInclude: z.string().array().optional(), siteExclude: z.string().array().optional(), }); -export const KVSharedOptionsSchema = z.object({ - kvPersist: PersistenceSchema, -}); - const SERVICE_NAMESPACE_PREFIX = `${KV_PLUGIN_NAME}:ns`; const KV_STORAGE_SERVICE_NAME = `${KV_PLUGIN_NAME}:storage`; export const KV_NAMESPACE_OBJECT_CLASS_NAME = "KVNamespaceObject"; @@ -71,12 +66,8 @@ function isWorkersSitesEnabled( return options.sitePath !== undefined; } -export const KV_PLUGIN: Plugin< - typeof KVOptionsSchema, - typeof KVSharedOptionsSchema -> = { +export const KV_PLUGIN: Plugin = { options: KVOptionsSchema, - sharedOptions: KVSharedOptionsSchema, bindingTypeDescription: "KV namespace", async getBindings(options) { const namespaces = namespaceEntries(options.kvNamespaces); @@ -111,14 +102,7 @@ export const KV_PLUGIN: Plugin< return bindings; }, - async getServices({ - options, - sharedOptions, - tmpPath, - defaultPersistRoot, - unsafeStickyBlobs, - }) { - const persist = sharedOptions.kvPersist; + async getServices({ options, tmpPath, resourcePersistencePath }) { const namespaces = namespaceEntries(options.kvNamespaces); const services = namespaces.map( ([name, { id, remoteProxyConnectionString }]) => ({ @@ -138,8 +122,7 @@ export const KV_PLUGIN: Plugin< const persistPath = getPersistPath( KV_PLUGIN_NAME, tmpPath, - defaultPersistRoot, - persist + resourcePersistencePath ); await fs.mkdir(persistPath, { recursive: true }); const storageService: Service = { @@ -172,7 +155,7 @@ export const KV_PLUGIN: Plugin< name: SharedBindings.MAYBE_SERVICE_LOOPBACK, service: { name: SERVICE_LOOPBACK }, }, - ...getMiniflareObjectBindings(unsafeStickyBlobs), + ...getMiniflareObjectBindings(), ], }, }; @@ -185,10 +168,6 @@ export const KV_PLUGIN: Plugin< return services; }, - - getPersistPath({ kvPersist }, tmpPath) { - return getPersistPath(KV_PLUGIN_NAME, tmpPath, undefined, kvPersist); - }, }; export { KV_PLUGIN_NAME }; diff --git a/packages/miniflare/src/plugins/queues/index.ts b/packages/miniflare/src/plugins/queues/index.ts index ff0afb99d3..959f46d345 100644 --- a/packages/miniflare/src/plugins/queues/index.ts +++ b/packages/miniflare/src/plugins/queues/index.ts @@ -77,7 +77,6 @@ export const QUEUES_PLUGIN: Plugin = { queueProducers: allQueueProducers, queueConsumers: allQueueConsumers, devRegistryEnabled, - unsafeStickyBlobs, }) { const produced = bindingEntries(options.queueProducers).map(([, id]) => id); // Consumed queues get a broker service even without a local producer so @@ -119,7 +118,7 @@ export const QUEUES_PLUGIN: Plugin = { name: SharedBindings.MAYBE_SERVICE_LOOPBACK, service: { name: SERVICE_LOOPBACK }, }, - ...getMiniflareObjectBindings(unsafeStickyBlobs), + ...getMiniflareObjectBindings(), { name: SharedBindings.DURABLE_OBJECT_NAMESPACE_OBJECT, durableObjectNamespace: { diff --git a/packages/miniflare/src/plugins/r2/index.ts b/packages/miniflare/src/plugins/r2/index.ts index 3cc6f8d750..259139a328 100644 --- a/packages/miniflare/src/plugins/r2/index.ts +++ b/packages/miniflare/src/plugins/r2/index.ts @@ -10,7 +10,6 @@ import { namespaceEntries, namespaceKeys, objectEntryWorker, - PersistenceSchema, ProxyNodeBinding, remoteProxyClientWorker, SERVICE_LOOPBACK, @@ -41,10 +40,6 @@ export const R2OptionsSchema = z.object({ ]) .optional(), }); -export const R2SharedOptionsSchema = z.object({ - r2Persist: PersistenceSchema, -}); - export const R2_PLUGIN_NAME = "r2"; const R2_STORAGE_SERVICE_NAME = `${R2_PLUGIN_NAME}:storage`; const R2_BUCKET_SERVICE_PREFIX = `${R2_PLUGIN_NAME}:bucket`; @@ -86,12 +81,8 @@ export function getR2PublicService( }; } -export const R2_PLUGIN: Plugin< - typeof R2OptionsSchema, - typeof R2SharedOptionsSchema -> = { +export const R2_PLUGIN: Plugin = { options: R2OptionsSchema, - sharedOptions: R2SharedOptionsSchema, bindingTypeDescription: "R2 bucket", getBindings(options) { const buckets = namespaceEntries(options.r2Buckets); @@ -112,14 +103,7 @@ export const R2_PLUGIN: Plugin< buckets.map((name) => [name, new ProxyNodeBinding()]) ); }, - async getServices({ - options, - sharedOptions, - tmpPath, - defaultPersistRoot, - unsafeStickyBlobs, - }) { - const persist = sharedOptions.r2Persist; + async getServices({ options, tmpPath, resourcePersistencePath }) { const buckets = namespaceEntries(options.r2Buckets); const services = buckets.map( ([name, { id, remoteProxyConnectionString }]) => ({ @@ -139,8 +123,7 @@ export const R2_PLUGIN: Plugin< const persistPath = getPersistPath( R2_PLUGIN_NAME, tmpPath, - defaultPersistRoot, - persist + resourcePersistencePath ); await fs.mkdir(persistPath, { recursive: true }); const storageService: Service = { @@ -176,7 +159,7 @@ export const R2_PLUGIN: Plugin< name: SharedBindings.MAYBE_SERVICE_LOOPBACK, service: { name: SERVICE_LOOPBACK }, }, - ...getMiniflareObjectBindings(unsafeStickyBlobs), + ...getMiniflareObjectBindings(), ], }, }; @@ -185,7 +168,4 @@ export const R2_PLUGIN: Plugin< return services; }, - getPersistPath({ r2Persist }, tmpPath) { - return getPersistPath(R2_PLUGIN_NAME, tmpPath, undefined, r2Persist); - }, }; diff --git a/packages/miniflare/src/plugins/ratelimit/index.ts b/packages/miniflare/src/plugins/ratelimit/index.ts index b4dd59b73f..c42ee653f2 100644 --- a/packages/miniflare/src/plugins/ratelimit/index.ts +++ b/packages/miniflare/src/plugins/ratelimit/index.ts @@ -99,7 +99,7 @@ export const RATELIMIT_PLUGIN: Plugin = { ]) ); }, - async getServices({ options, unsafeStickyBlobs }) { + async getServices({ options }) { if (!options.ratelimits) { return []; } @@ -145,7 +145,7 @@ export const RATELIMIT_PLUGIN: Plugin = { name: SharedBindings.MAYBE_SERVICE_LOOPBACK, service: { name: SERVICE_LOOPBACK }, }, - ...getMiniflareObjectBindings(unsafeStickyBlobs), + ...getMiniflareObjectBindings(), ], }, }); diff --git a/packages/miniflare/src/plugins/secret-store/index.ts b/packages/miniflare/src/plugins/secret-store/index.ts index 27074a797f..1fbf4b5b90 100644 --- a/packages/miniflare/src/plugins/secret-store/index.ts +++ b/packages/miniflare/src/plugins/secret-store/index.ts @@ -9,7 +9,6 @@ import { getPersistPath, getUserBindingServiceName, objectEntryWorker, - PersistenceSchema, ProxyNodeBinding, SERVICE_LOOPBACK, } from "../shared"; @@ -28,18 +27,12 @@ export const SecretsStoreSecretsOptionsSchema = z.object({ secretsStoreSecrets: SecretsStoreSecretsSchema.optional(), }); -export const SecretsStoreSecretsSharedOptionsSchema = z.object({ - secretsStorePersist: PersistenceSchema, -}); - export const SECRET_STORE_PLUGIN_NAME = "secrets-store"; export const SECRET_STORE_PLUGIN: Plugin< - typeof SecretsStoreSecretsOptionsSchema, - typeof SecretsStoreSecretsSharedOptionsSchema + typeof SecretsStoreSecretsOptionsSchema > = { options: SecretsStoreSecretsOptionsSchema, - sharedOptions: SecretsStoreSecretsSharedOptionsSchema, bindingTypeDescription: "Secrets Store secret", async getBindings(options) { if (!options.secretsStoreSecrets) { @@ -73,13 +66,7 @@ export const SECRET_STORE_PLUGIN: Plugin< ]) ); }, - async getServices({ - options, - sharedOptions, - tmpPath, - defaultPersistRoot, - unsafeStickyBlobs, - }) { + async getServices({ options, tmpPath, resourcePersistencePath }) { const configs = options.secretsStoreSecrets ? Object.values(options.secretsStoreSecrets) : []; @@ -91,8 +78,7 @@ export const SECRET_STORE_PLUGIN: Plugin< const persistPath = getPersistPath( SECRET_STORE_PLUGIN_NAME, tmpPath, - defaultPersistRoot, - sharedOptions.secretsStorePersist + resourcePersistencePath ); await fs.mkdir(persistPath, { recursive: true }); @@ -130,7 +116,7 @@ export const SECRET_STORE_PLUGIN: Plugin< name: SharedBindings.MAYBE_SERVICE_LOOPBACK, service: { name: SERVICE_LOOPBACK }, }, - ...getMiniflareObjectBindings(unsafeStickyBlobs), + ...getMiniflareObjectBindings(), ], }, } satisfies Service; @@ -178,12 +164,4 @@ export const SECRET_STORE_PLUGIN: Plugin< return [...services, storageService, objectService]; }, - getPersistPath({ secretsStorePersist }, tmpPath) { - return getPersistPath( - SECRET_STORE_PLUGIN_NAME, - tmpPath, - undefined, - secretsStorePersist - ); - }, }; diff --git a/packages/miniflare/src/plugins/shared/constants.ts b/packages/miniflare/src/plugins/shared/constants.ts index b26fcc06a4..3cb6fd819d 100644 --- a/packages/miniflare/src/plugins/shared/constants.ts +++ b/packages/miniflare/src/plugins/shared/constants.ts @@ -37,21 +37,12 @@ const WORKER_BINDING_ENABLE_CONTROL_ENDPOINTS: Worker_Binding = { name: SharedBindings.MAYBE_JSON_ENABLE_CONTROL_ENDPOINTS, json: "true", }; -const WORKER_BINDING_ENABLE_STICKY_BLOBS: Worker_Binding = { - name: SharedBindings.MAYBE_JSON_ENABLE_STICKY_BLOBS, - json: "true", -}; let enableControlEndpoints = false; -export function getMiniflareObjectBindings( - unsafeStickyBlobs: boolean -): Worker_Binding[] { +export function getMiniflareObjectBindings(): Worker_Binding[] { const result: Worker_Binding[] = []; if (enableControlEndpoints) { result.push(WORKER_BINDING_ENABLE_CONTROL_ENDPOINTS); } - if (unsafeStickyBlobs) { - result.push(WORKER_BINDING_ENABLE_STICKY_BLOBS); - } return result; } /** @internal */ diff --git a/packages/miniflare/src/plugins/shared/index.ts b/packages/miniflare/src/plugins/shared/index.ts index e65ac9cd1a..4d61caa0a5 100644 --- a/packages/miniflare/src/plugins/shared/index.ts +++ b/packages/miniflare/src/plugins/shared/index.ts @@ -1,8 +1,7 @@ import { createHash } from "node:crypto"; import path from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; -import { z } from "zod"; -import { MiniflareCoreError, PathSchema } from "../../shared"; +import { pathToFileURL } from "node:url"; +import { MiniflareCoreError } from "../../shared"; import type { Extension, Service, @@ -18,20 +17,7 @@ import type { import type { DOContainerOptions } from "../do"; import type { HyperdriveProxyController } from "../hyperdrive/hyperdrive-proxy"; import type { UnsafeUniqueKey } from "./constants"; - -export const DEFAULT_PERSIST_ROOT = ".mf"; - -export const PersistenceSchema = z - // Zod checks union types in order, both `z.url()` and `PathSchema` - // will result in a `string`, but `PathSchema` gets resolved relative to the - // closest `rootPath`. - .union([z.boolean(), z.url(), PathSchema]) - .optional(); -export type Persistence = z.infer; - -// Set of "worker" names that are being used as wrapped bindings and shouldn't -// be added a regular worker services. These workers shouldn't be routable. -export type WrappedBindingNames = Set; +import type { z } from "zod"; // Maps workflow binding names to their workflow options export interface WorkflowOption { @@ -74,16 +60,14 @@ export interface PluginServicesOptions< workerIndex: number; additionalModules: Worker_Module[]; tmpPath: string; - defaultPersistRoot: string | undefined; - defaultProjectTmpPath: string | undefined; + resourcePersistencePath: string | undefined; + resourceTmpPath: string | undefined; workerNames: string[]; loopbackHost: string; loopbackPort: number; publicUrl: string | undefined; - unsafeStickyBlobs: boolean; // ~~Leaky abstractions~~ "Plugin specific options" :) - wrappedBindingNames: WrappedBindingNames; durableObjectClassNames: DurableObjectClassNames; unsafeEphemeralDurableObjects: boolean; queueProducers: QueueProducers; @@ -117,10 +101,6 @@ export interface PluginBase< getServices( options: PluginServicesOptions ): Awaitable; - getPersistPath?( - sharedOptions: OptionalZodTypeOf, - tmpPath: string - ): string; getExtensions?(options: { options: z.infer[]; }): Awaitable; @@ -220,7 +200,7 @@ export function namespaceEntries( } } -export function maybeParseURL(url: Persistence): URL | undefined { +export function maybeParseURL(url: string | undefined): URL | undefined { if (typeof url !== "string" || path.isAbsolute(url)) return; try { return new URL(url); @@ -230,48 +210,18 @@ export function maybeParseURL(url: Persistence): URL | undefined { export function getPersistPath( pluginName: string, tmpPath: string, - defaultPersistRoot: string | undefined, - persist: Persistence + resourcePersistencePath: string | undefined ): string { - // If persistence is disabled, use "memory" storage. Note we're still - // returning a path on the file-system here. Miniflare 2's in-memory storage - // persisted between options reloads. However, we restart the `workerd` - // process on each reload which would destroy any in-memory data. We'd like to - // keep Miniflare 2's behaviour, so persist to a temporary path which we - // destroy on `dispose()`. - const memoryishPath = path.join(tmpPath, pluginName); - - let result: string; - if (persist === false) { - result = memoryishPath; - } else if (persist === undefined) { - // If `persist` is undefined, use either the default path or fallback to the tmpPath - result = - defaultPersistRoot === undefined - ? memoryishPath - : path.join(defaultPersistRoot, pluginName); - } else { - // Try parse `persist` as a URL - const url = maybeParseURL(persist); - if (url !== undefined) { - if (url.protocol === "memory:") { - result = memoryishPath; - } else if (url.protocol === "file:") { - result = fileURLToPath(url); - } else { - throw new MiniflareCoreError( - "ERR_PERSIST_UNSUPPORTED", - `Unsupported "${url.protocol}" persistence protocol for storage: ${url.href}` - ); - } - } else { - // Otherwise, fallback to file storage - result = - persist === true - ? path.join(defaultPersistRoot ?? DEFAULT_PERSIST_ROOT, pluginName) - : persist; - } - } + // If persistence is disabled (no resource persistence path), use "memory" + // storage. Note we're still returning a path on the file-system here. + // Miniflare 2's in-memory storage persisted between options reloads. However, + // we restart the `workerd` process on each reload which would destroy any + // in-memory data. We'd like to keep Miniflare 2's behaviour, so persist to a + // temporary path which we destroy on `dispose()`. + const result = + resourcePersistencePath === undefined + ? path.join(tmpPath, pluginName) + : path.join(resourcePersistencePath, pluginName); // Normalize to forward slashes for workerd's disk service compatibility on // Windows. workerd is a Unix-oriented C++ program and its disk service does diff --git a/packages/miniflare/src/plugins/stream/index.ts b/packages/miniflare/src/plugins/stream/index.ts index 9c1b446f64..1b873c7724 100644 --- a/packages/miniflare/src/plugins/stream/index.ts +++ b/packages/miniflare/src/plugins/stream/index.ts @@ -7,7 +7,6 @@ import { getMiniflareObjectBindings, getPersistPath, getUserBindingServiceName, - PersistenceSchema, ProxyNodeBinding, remoteProxyClientWorker, WORKER_BINDING_SERVICE_LOOPBACK, @@ -26,10 +25,6 @@ export const StreamOptionsSchema = z.object({ stream: StreamSchema.optional(), }); -export const StreamSharedOptionsSchema = z.object({ - streamPersist: PersistenceSchema, -}); - export const STREAM_PLUGIN_NAME = "stream"; const STREAM_STORAGE_SERVICE_NAME = `${STREAM_PLUGIN_NAME}:storage`; const STREAM_OBJECT_SERVICE_NAME = `${STREAM_PLUGIN_NAME}:object`; @@ -37,12 +32,8 @@ export const STREAM_OBJECT_CLASS_NAME = "StreamObject"; export const STREAM_COMPAT_DATE = "2026-03-23"; -export const STREAM_PLUGIN: Plugin< - typeof StreamOptionsSchema, - typeof StreamSharedOptionsSchema -> = { +export const STREAM_PLUGIN: Plugin = { options: StreamOptionsSchema, - sharedOptions: StreamSharedOptionsSchema, bindingTypeDescription: "Stream", async getBindings(options) { if (!options.stream) { @@ -73,13 +64,7 @@ export const STREAM_PLUGIN: Plugin< [options.stream.binding]: new ProxyNodeBinding(), }; }, - async getServices({ - options, - sharedOptions, - tmpPath, - defaultPersistRoot, - unsafeStickyBlobs, - }) { + async getServices({ options, tmpPath, resourcePersistencePath }) { if (!options.stream) { return []; } @@ -105,8 +90,7 @@ export const STREAM_PLUGIN: Plugin< const persistPath = getPersistPath( STREAM_PLUGIN_NAME, tmpPath, - defaultPersistRoot, - sharedOptions.streamPersist + resourcePersistencePath ); await fs.mkdir(persistPath, { recursive: true }); @@ -141,7 +125,7 @@ export const STREAM_PLUGIN: Plugin< name: SharedBindings.MAYBE_SERVICE_BLOBS, service: { name: STREAM_STORAGE_SERVICE_NAME }, }, - ...getMiniflareObjectBindings(unsafeStickyBlobs), + ...getMiniflareObjectBindings(), ], // Allow the DO to send outbound HTTP requests (fetching watermark images) globalOutbound: { name: "internet" }, @@ -182,12 +166,4 @@ export const STREAM_PLUGIN: Plugin< return [storageService, objectService, bindingService]; }, - getPersistPath({ streamPersist }, tmpPath) { - return getPersistPath( - STREAM_PLUGIN_NAME, - tmpPath, - undefined, - streamPersist - ); - }, }; diff --git a/packages/miniflare/src/plugins/workflows/index.ts b/packages/miniflare/src/plugins/workflows/index.ts index b54ccfdcd6..8d5d7cc803 100644 --- a/packages/miniflare/src/plugins/workflows/index.ts +++ b/packages/miniflare/src/plugins/workflows/index.ts @@ -6,7 +6,6 @@ import { getUserServiceName } from "../core"; import { getPersistPath, getUserBindingServiceName, - PersistenceSchema, ProxyNodeBinding, SERVICE_DEV_REGISTRY_PROXY, } from "../shared"; @@ -37,19 +36,11 @@ export const WorkflowsOptionsSchema = z.object({ ) .optional(), }); -export const WorkflowsSharedOptionsSchema = z.object({ - workflowsPersist: PersistenceSchema, -}); - export const WORKFLOWS_PLUGIN_NAME = "workflows"; export const WORKFLOWS_STORAGE_SERVICE_NAME = `${WORKFLOWS_PLUGIN_NAME}:storage`; -export const WORKFLOWS_PLUGIN: Plugin< - typeof WorkflowsOptionsSchema, - typeof WorkflowsSharedOptionsSchema -> = { +export const WORKFLOWS_PLUGIN: Plugin = { options: WorkflowsOptionsSchema, - sharedOptions: WorkflowsSharedOptionsSchema, bindingTypeDescription: "Workflow", async getBindings(options: z.infer) { return Object.entries(options.workflows ?? {}).map( @@ -98,12 +89,11 @@ export const WORKFLOWS_PLUGIN: Plugin< ]; }, - async getServices({ options, sharedOptions, tmpPath, defaultPersistRoot }) { + async getServices({ options, tmpPath, resourcePersistencePath }) { const persistPath = getPersistPath( WORKFLOWS_PLUGIN_NAME, tmpPath, - defaultPersistRoot, - sharedOptions.workflowsPersist + resourcePersistencePath ); await fs.mkdir(persistPath, { recursive: true }); // each workflow should get its own storage service @@ -205,13 +195,4 @@ export const WORKFLOWS_PLUGIN: Plugin< return [...storageServices, ...services]; }, - - getPersistPath({ workflowsPersist }, tmpPath) { - return getPersistPath( - WORKFLOWS_PLUGIN_NAME, - tmpPath, - undefined, - workflowsPersist - ); - }, }; diff --git a/packages/miniflare/src/runtime/index.ts b/packages/miniflare/src/runtime/index.ts index 35a9faa529..0c35443be2 100644 --- a/packages/miniflare/src/runtime/index.ts +++ b/packages/miniflare/src/runtime/index.ts @@ -40,7 +40,6 @@ export interface RuntimeOptions { inspectorAddress?: string; debugPortAddress?: string; verbose?: boolean; - handleRuntimeStdio?: (stdout: Readable, stderr: Readable) => void; handleStructuredLogs?: StructuredLogsHandler; // Extra environment variables to set on the spawned `workerd` subprocess. // Merged on top of `process.env` and Miniflare's own defaults @@ -88,20 +87,27 @@ function waitForExit(process: childProcess.ChildProcess): Promise { }); } -function pipeOutput(stdout: Readable, stderr: Readable) { - // TODO: may want to proxy these and prettify ✨ - // We can't just pipe() to `process.stdout/stderr` here, as Ink (used by - // wrangler), only patches the `console.*` methods: - // https://github.com/vadimdemedes/ink/blob/5d24ed8ada593a6c36ea5416f452158461e33ba5/readme.md#patchconsole - // Writing directly to `process.stdout/stderr` would result in graphical - // glitches. - // eslint-disable-next-line no-console -- Intentional console.log to forward workerd stdout through Ink-patched console - rl.createInterface(stdout).on("line", (data) => console.log(data)); - // eslint-disable-next-line no-console -- Intentional console.error to forward workerd stderr through Ink-patched console - rl.createInterface(stderr).on("line", (data) => console.error(red(data))); - // stdout.pipe(process.stdout); - // stderr.pipe(process.stderr); -} +// When no `handleStructuredLogs` handler is provided, workerd's structured logs +// are forwarded to the console by default. `warn`/`error` logs go to stderr +// (in red), everything else to stdout, matching how the raw workerd streams +// used to be forwarded. We use `console.*` rather than writing to +// `process.stdout/stderr` directly, as Ink (used by Wrangler) only patches the +// `console.*` methods: +// https://github.com/vadimdemedes/ink/blob/5d24ed8ada593a6c36ea5416f452158461e33ba5/readme.md#patchconsole +// Writing directly to `process.stdout/stderr` would result in graphical +// glitches. +const defaultStructuredLogsHandler: StructuredLogsHandler = ({ + level, + message, +}) => { + if (level === "error" || level === "warn") { + // eslint-disable-next-line no-console -- forward workerd output through Ink-patched console + console.error(red(message)); + } else { + // eslint-disable-next-line no-console -- forward workerd output through Ink-patched console + console.log(message); + } +}; function getRuntimeCommand() { return process.env.MINIFLARE_WORKERD_PATH ?? workerdPath; @@ -258,28 +264,17 @@ export class Runtime { const processExitPromise = waitForExit(runtimeProcess); this.#processExitPromise = processExitPromise; - const handleRuntimeStdio = - options.handleRuntimeStdio ?? - (options.handleStructuredLogs - ? // If `handleStructuredLogs` is provided then by default Miniflare should not pipe through the stream's output - () => {} - : pipeOutput); - - handleRuntimeStdio( - runtimeProcess.stdout.pipe(startupLogBuffer.stdoutStream), - runtimeProcess.stderr.pipe(startupLogBuffer.stderrStream) + const stdoutStream = runtimeProcess.stdout.pipe( + startupLogBuffer.stdoutStream + ); + const stderrStream = runtimeProcess.stderr.pipe( + startupLogBuffer.stderrStream ); - if (options.handleStructuredLogs) { - handleStructuredLogsFromStream( - startupLogBuffer.stdoutStream, - options.handleStructuredLogs - ); - handleStructuredLogsFromStream( - startupLogBuffer.stderrStream, - options.handleStructuredLogs - ); - } + const structuredLogsHandler = + options.handleStructuredLogs ?? defaultStructuredLogsHandler; + handleStructuredLogsFromStream(stdoutStream, structuredLogsHandler); + handleStructuredLogsFromStream(stderrStream, structuredLogsHandler); const controlPipe = runtimeProcess.stdio[3]; assert(controlPipe instanceof Readable); diff --git a/packages/miniflare/src/shared/error.ts b/packages/miniflare/src/shared/error.ts index add9fc4fcf..91aeee011e 100644 --- a/packages/miniflare/src/shared/error.ts +++ b/packages/miniflare/src/shared/error.ts @@ -13,15 +13,12 @@ export const USER_ERROR_CODES = new Set([ "ERR_DIFFERENT_STORAGE_BACKEND", // Multiple Durable Object bindings declared for same class with different storage backends "ERR_DIFFERENT_UNIQUE_KEYS", // Multiple Durable Object bindings declared for same class with different unsafe unique keys "ERR_DIFFERENT_PREVENT_EVICTION", // Multiple Durable Object bindings declared for same class with different unsafe prevent eviction values - "ERR_MULTIPLE_OUTBOUNDS", // Both `outboundService` and `fetchMock` specified - "ERR_INVALID_WRAPPED", // Worker not allowed to be used as wrapped binding "ERR_MISSING_INSPECTOR_PROXY_PORT", // An inspector proxy has been requested but no inspector port to use has been specified "ERR_MISSING_EXPLORER_UI", // Local Explorer enabled but assets not found at expected path ] as const); export const SYSTEM_ERROR_CODES = new Set([ "ERR_RUNTIME_FAILURE", // Runtime failed to start - "ERR_CYCLIC", // Generate cyclic workerd config "ERR_PLUGIN_LOADING_FAILED", ] as const); diff --git a/packages/miniflare/src/workers/cache/cache-entry.worker.ts b/packages/miniflare/src/workers/cache/cache-entry.worker.ts index 8112c5ef9a..f5627ed06c 100644 --- a/packages/miniflare/src/workers/cache/cache-entry.worker.ts +++ b/packages/miniflare/src/workers/cache/cache-entry.worker.ts @@ -1,11 +1,9 @@ import { SharedBindings } from "miniflare:shared"; -import { CacheBindings, CacheHeaders } from "./constants"; -import type { CacheObjectCf } from "./constants"; +import { CacheHeaders } from "./constants"; import type { MiniflareDurableObjectCf } from "miniflare:shared"; interface Env { [SharedBindings.DURABLE_OBJECT_NAMESPACE_OBJECT]: DurableObjectNamespace; - [CacheBindings.MAYBE_JSON_CACHE_WARN_USAGE]?: boolean; } export default >{ @@ -16,11 +14,10 @@ export default >{ const objectNamespace = env[SharedBindings.DURABLE_OBJECT_NAMESPACE_OBJECT]; const id = objectNamespace.idFromName(name); const stub = objectNamespace.get(id); - const cf: MiniflareDurableObjectCf & CacheObjectCf = { + const cf: MiniflareDurableObjectCf = { ...request.cf, miniflare: { name, - cacheWarnUsage: env[CacheBindings.MAYBE_JSON_CACHE_WARN_USAGE], }, }; return await stub.fetch(request, { cf: cf as Record }); diff --git a/packages/miniflare/src/workers/cache/cache.worker.ts b/packages/miniflare/src/workers/cache/cache.worker.ts index 95f97978dc..431d3ece29 100644 --- a/packages/miniflare/src/workers/cache/cache.worker.ts +++ b/packages/miniflare/src/workers/cache/cache.worker.ts @@ -6,7 +6,6 @@ import { DELETE, GET, KeyValueStorage, - LogLevel, MiniflareDurableObject, parseRanges, PURGE, @@ -19,7 +18,6 @@ import { RangeNotSatisfiable, StorageFailure, } from "./errors.worker"; -import type { CacheObjectCf } from "./constants"; import type { InclusiveRange, MiniflareDurableObjectCf, @@ -36,7 +34,7 @@ interface CacheMetadata { type CacheRouteHandler = RouteHandler< unknown, - RequestInitCfProperties & MiniflareDurableObjectCf & CacheObjectCf + RequestInitCfProperties & MiniflareDurableObjectCf >; function getCacheKey(req: Request) { @@ -265,17 +263,6 @@ class SizingStream extends TransformStream { } export class CacheObject extends MiniflareDurableObject { - #warnedUsage = false; - async #maybeWarnUsage(request: Request) { - if (!this.#warnedUsage && request.cf?.miniflare?.cacheWarnUsage === true) { - this.#warnedUsage = true; - await this.logWithLevel( - LogLevel.WARN, - "Cache operations will have no impact if you deploy to a workers.dev subdomain!" - ); - } - } - #storage?: KeyValueStorage; get storage() { // `KeyValueStorage` can only be constructed once `this.blob` is initialised @@ -284,7 +271,6 @@ export class CacheObject extends MiniflareDurableObject { @GET() match: CacheRouteHandler = async (req) => { - await this.#maybeWarnUsage(req); const cacheKey = getCacheKey(req); // Never cache Workers Sites requests, so we always return on-disk files @@ -330,7 +316,6 @@ export class CacheObject extends MiniflareDurableObject { @PUT() put: CacheRouteHandler = async (req) => { - await this.#maybeWarnUsage(req); const cacheKey = getCacheKey(req); // Never cache Workers Sites requests, so we always return on-disk files @@ -384,7 +369,6 @@ export class CacheObject extends MiniflareDurableObject { @PURGE() delete: CacheRouteHandler = async (req) => { - await this.#maybeWarnUsage(req); const cacheKey = getCacheKey(req); const deleted = await this.storage.delete(cacheKey); diff --git a/packages/miniflare/src/workers/cache/constants.ts b/packages/miniflare/src/workers/cache/constants.ts index 1c80759888..1a71214374 100644 --- a/packages/miniflare/src/workers/cache/constants.ts +++ b/packages/miniflare/src/workers/cache/constants.ts @@ -2,11 +2,3 @@ export const CacheHeaders = { NAMESPACE: "cf-cache-namespace", STATUS: "cf-cache-status", } as const; - -export const CacheBindings = { - MAYBE_JSON_CACHE_WARN_USAGE: "MINIFLARE_CACHE_WARN_USAGE", -} as const; - -export interface CacheObjectCf { - miniflare?: { cacheWarnUsage?: boolean }; -} diff --git a/packages/miniflare/src/workers/shared/blob.worker.ts b/packages/miniflare/src/workers/shared/blob.worker.ts index 2e9743e63c..c1abb57f88 100644 --- a/packages/miniflare/src/workers/shared/blob.worker.ts +++ b/packages/miniflare/src/workers/shared/blob.worker.ts @@ -182,17 +182,16 @@ export class BlobStore { readonly #fetcher: Fetcher; readonly #baseURL: string; - readonly #stickyBlobs: boolean; - constructor(fetcher: Fetcher, namespace: string, stickyBlobs: boolean) { + constructor(fetcher: Fetcher, namespace: string) { namespace = encodeURIComponent(sanitisePath(namespace)); this.#fetcher = fetcher; // `baseURL`'s `pathname` (`/${namespace}/blobs/`) is relative to the - // `*Persist` (e.g. `kvPersist`) option if defined. For example, if - // `kvPersist` is `/path/to/kv`, the `blobs` directory for a KV namespace - // with ID `TEST_NAMESPACE` would be `/path/to/kv/TEST_NAMESPACE/blobs`. + // plugin's persistence directory under `resourcePersistencePath`. For + // example, if the KV persistence directory is `/path/to/kv`, the `blobs` + // directory for a KV namespace with ID `TEST_NAMESPACE` would be + // `/path/to/kv/TEST_NAMESPACE/blobs`. this.#baseURL = `http://placeholder/${namespace}/blobs/`; - this.#stickyBlobs = stickyBlobs; } private idURL(id: BlobId) { @@ -239,8 +238,6 @@ export class BlobStore { } async delete(id: BlobId): Promise { - // If sticky blobs are enabled, don't delete any blobs - if (this.#stickyBlobs) return; // Get path for this ID and delete, ignoring if outside root or not found const idURL = this.idURL(id); if (idURL === null) return; diff --git a/packages/miniflare/src/workers/shared/constants.ts b/packages/miniflare/src/workers/shared/constants.ts index f30006a231..ef7e61a433 100644 --- a/packages/miniflare/src/workers/shared/constants.ts +++ b/packages/miniflare/src/workers/shared/constants.ts @@ -8,7 +8,6 @@ export const SharedBindings = { MAYBE_SERVICE_BLOBS: "MINIFLARE_BLOBS", MAYBE_SERVICE_LOOPBACK: "MINIFLARE_LOOPBACK", MAYBE_JSON_ENABLE_CONTROL_ENDPOINTS: "MINIFLARE_ENABLE_CONTROL_ENDPOINTS", - MAYBE_JSON_ENABLE_STICKY_BLOBS: "MINIFLARE_STICKY_BLOBS", } as const; export enum LogLevel { diff --git a/packages/miniflare/src/workers/shared/object.worker.ts b/packages/miniflare/src/workers/shared/object.worker.ts index c7c637a79e..54273e243f 100644 --- a/packages/miniflare/src/workers/shared/object.worker.ts +++ b/packages/miniflare/src/workers/shared/object.worker.ts @@ -24,11 +24,6 @@ export interface MiniflareDurableObjectEnv { // for testing. Note these endpoints allow anyone with access to the Miniflare // dev server to run arbitrary SQL queries and read arbitrary blobs. [SharedBindings.MAYBE_JSON_ENABLE_CONTROL_ENDPOINTS]?: boolean; - // If set to `true`, Miniflare won't delete blobs when deleting/overriding - // existing keys. This is a requirement for "stacked storage": when popping - // from the storage stack, we need to guarantee the blobs created in and - // before that storage stack frame still exist. - [SharedBindings.MAYBE_JSON_ENABLE_STICKY_BLOBS]?: boolean; } export interface MiniflareDurableObjectCfControlOp { @@ -78,13 +73,11 @@ export abstract class MiniflareDurableObject< get blob(): BlobStore { if (this.#blob !== undefined) return this.#blob; const maybeBlobsService = this.env[SharedBindings.MAYBE_SERVICE_BLOBS]; - const stickyBlobs = - !!this.env[SharedBindings.MAYBE_JSON_ENABLE_STICKY_BLOBS]; assert( maybeBlobsService !== undefined, `Expected ${SharedBindings.MAYBE_SERVICE_BLOBS} service binding` ); - this.#blob = new BlobStore(maybeBlobsService, this.name, stickyBlobs); + this.#blob = new BlobStore(maybeBlobsService, this.name); return this.#blob; } diff --git a/packages/miniflare/src/workers/stream/object.worker.ts b/packages/miniflare/src/workers/stream/object.worker.ts index 7e9b264767..4a230187b8 100644 --- a/packages/miniflare/src/workers/stream/object.worker.ts +++ b/packages/miniflare/src/workers/stream/object.worker.ts @@ -21,7 +21,6 @@ const BLOB_NAMESPACE = "stream-data"; interface Env { MINIFLARE_BLOBS?: Fetcher; - MINIFLARE_STICKY_BLOBS?: boolean; [SharedBindings.MAYBE_JSON_ENABLE_CONTROL_ENDPOINTS]?: boolean; } @@ -42,12 +41,7 @@ export class StreamObject extends DurableObject { db.exec(SQL_SCHEMA); this.#db = db; this.#stmts = sqlStmts(db, () => this.#now()); - const stickyBlobs = !!env.MINIFLARE_STICKY_BLOBS; - this.#blob = new BlobStore( - env.MINIFLARE_BLOBS as Fetcher, - BLOB_NAMESPACE, - stickyBlobs - ); + this.#blob = new BlobStore(env.MINIFLARE_BLOBS as Fetcher, BLOB_NAMESPACE); } async createVideo( diff --git a/packages/miniflare/test/dev-registry.spec.ts b/packages/miniflare/test/dev-registry.spec.ts index c0976f51e7..5f7714e07f 100644 --- a/packages/miniflare/test/dev-registry.spec.ts +++ b/packages/miniflare/test/dev-registry.spec.ts @@ -1193,7 +1193,7 @@ describe.sequential("DevRegistry", () => { serviceBindings: { remote: "remote-worker", }, - handleRuntimeStdio: () => {}, + handleStructuredLogs: () => {}, compatibilityFlags: ["experimental"], modules: true, script: ` @@ -1231,7 +1231,7 @@ describe.sequential("DevRegistry", () => { }, compatibilityFlags: ["experimental"], modules: true, - handleRuntimeStdio: () => {}, + handleStructuredLogs: () => {}, script: ` export default { async fetch(request, env) { @@ -1306,7 +1306,7 @@ describe.sequential("DevRegistry", () => { props: { tailKey: "from-tail-binding" }, }, ], - handleRuntimeStdio: () => {}, + handleStructuredLogs: () => {}, compatibilityFlags: ["experimental"], modules: true, script: ` diff --git a/packages/miniflare/test/fixtures/echo-plugin/index.ts b/packages/miniflare/test/fixtures/echo-plugin/index.ts new file mode 100644 index 0000000000..4f4db1e8ca --- /dev/null +++ b/packages/miniflare/test/fixtures/echo-plugin/index.ts @@ -0,0 +1,77 @@ +import { ProxyNodeBinding } from "miniflare"; +import { z } from "miniflare:zod"; +import type { Plugin, Worker_Binding } from "miniflare"; + +// Module implementing the wrapped binding. It exposes an `asyncIdentity` method +// that echoes back its arguments, allowing tests to exercise the proxy client's +// serialisation of `ReadableStream`/`Blob`/`File` arguments across the +// Node.js <-> workerd boundary. +// +// The `.pipeThrough(new TransformStream())` is required: without it we'd see +// `TypeError: Inter-TransformStream ReadableStream.pipeTo() is not implemented` +// when echoing a `ReadableStream` back. `IdentityTransformStream` doesn't work +// here. +const ECHO_MODULE_NAME = "cloudflare-internal:echo-plugin:module"; +const ECHO_MODULE = /* javascript */ ` +class Identity { + async asyncIdentity(...args) { + const i = args.findIndex((arg) => arg instanceof ReadableStream); + if (i !== -1) args[i] = args[i].pipeThrough(new TransformStream()); + return args; + } +} +export default function () { + return new Identity(); +} +`; + +export const EchoBindingOptionSchema = z.array( + z.object({ + name: z.string(), + type: z.string(), + plugin: z.object({ + package: z.string(), + name: z.string(), + }), + options: z.record(z.string(), z.unknown()), + }) +); + +export const plugins = { + "echo-plugin": { + options: EchoBindingOptionSchema, + getBindings(options) { + return options.map((binding) => ({ + name: binding.name, + wrapped: { + moduleName: ECHO_MODULE_NAME, + innerBindings: [], + }, + })); + }, + getNodeBindings(options) { + return Object.fromEntries( + options.map((binding) => [binding.name, new ProxyNodeBinding()]) + ); + }, + getServices() { + return []; + }, + getExtensions({ options }) { + if (!options.some((bindings) => bindings.length > 0)) { + return []; + } + return [ + { + modules: [ + { + name: ECHO_MODULE_NAME, + esModule: ECHO_MODULE, + internal: true, + }, + ], + }, + ]; + }, + } satisfies Plugin, +}; diff --git a/packages/miniflare/test/fixtures/unsafe-plugin/index.ts b/packages/miniflare/test/fixtures/unsafe-plugin/index.ts index d405d9b18e..39f9cdf0c7 100644 --- a/packages/miniflare/test/fixtures/unsafe-plugin/index.ts +++ b/packages/miniflare/test/fixtures/unsafe-plugin/index.ts @@ -98,7 +98,7 @@ export const plugins = { Object.keys(options).map((name) => [name, new ProxyNodeBinding()]) ); }, - getServices({ options, unsafeStickyBlobs }) { + getServices({ options }) { if (options.length === 0) { return []; } @@ -155,7 +155,7 @@ export const plugins = { name: SharedBindings.MAYBE_SERVICE_LOOPBACK, service: { name: SERVICE_LOOPBACK }, }, - ...getMiniflareObjectBindings(unsafeStickyBlobs), + ...getMiniflareObjectBindings(), ], }, }, diff --git a/packages/miniflare/test/index.spec.ts b/packages/miniflare/test/index.spec.ts index 582fe1a86e..d947e3bd16 100644 --- a/packages/miniflare/test/index.spec.ts +++ b/packages/miniflare/test/index.spec.ts @@ -10,19 +10,15 @@ import net from "node:net"; import os from "node:os"; import path from "node:path"; import { json, text } from "node:stream/consumers"; -import url from "node:url"; import util from "node:util"; import { _forceColour } from "@cloudflare/workers-utils"; import { _transformsForContentEncodingAndContentType, - createFetchMock, DeferredPromise, fetch, kCurrentWorker, Miniflare, MiniflareCoreError, - parseWithRootPath, - PLUGINS, Response, viewToBuffer, } from "miniflare"; @@ -51,7 +47,6 @@ import type { MiniflareOptions, ReplaceWorkersTypes, Worker_Module, - WorkerOptions, } from "miniflare"; import type { AddressInfo } from "node:net"; import type { Writable } from "node:stream"; @@ -1011,7 +1006,7 @@ test("Miniflare: service binding to named entrypoint that implements a method re test("Miniflare: tail consumer called", async ({ expect }) => { const mf = new Miniflare({ - handleRuntimeStdio: () => {}, + handleStructuredLogs: () => {}, workers: [ { name: "a", @@ -1313,51 +1308,6 @@ test("Miniflare: handles redirect responses", async ({ expect }) => { expect(await res.text()).toBe("end:https://custom.mf/external-redirected"); }); -test("Miniflare: fetch mocking", async ({ expect }) => { - const fetchMock = createFetchMock(); - fetchMock.disableNetConnect(); - const origin = fetchMock.get("https://example.com"); - origin.intercept({ method: "GET", path: "/" }).reply(200, "Mocked response!"); - - const mfOptions: MiniflareOptions = { - modules: true, - script: `export default { - async fetch() { - return fetch("https://example.com/"); - } - }`, - fetchMock, - }; - const resultOptions = {} as MiniflareOptions; - - // Verify that options with `fetchMock` can be parsed first before passing to Miniflare - // Regression test for https://github.com/cloudflare/workers-sdk/issues/5486 - for (const plugin of Object.values(PLUGINS)) { - Object.assign( - resultOptions, - parseWithRootPath("", plugin.options, mfOptions) - ); - } - - const mf = new Miniflare(resultOptions); - useDispose(mf); - const res = await mf.dispatchFetch("http://localhost"); - expect(await res.text()).toBe("Mocked response!"); - - // Check `outboundService`and `fetchMock` mutually exclusive - await expect( - mf.setOptions({ - script: "", - fetchMock, - outboundService: "", - }) - ).rejects.toThrow( - new MiniflareCoreError( - "ERR_MULTIPLE_OUTBOUNDS", - "Only one of `outboundService` or `fetchMock` may be specified per worker" - ) - ); -}); test("Miniflare: custom upstream as origin (with colons)", async ({ expect, }) => { @@ -2272,197 +2222,6 @@ test("Miniflare: getBindings() returns all bindings", async ({ ) ); }); -test("Miniflare: getBindings() returns wrapped bindings", async ({ - expect, -}) => { - const mf = new Miniflare({ - workers: [ - { - wrappedBindings: { - Greeter: { - scriptName: "greeter-implementation", - }, - }, - modules: true, - script: "", - }, - { - modules: true, - name: "greeter-implementation", - script: ` - class Greeter { - sayHello(name) { - return "Hello " + name; - } - } - - export default function (env) { - return new Greeter(); - } - `, - }, - ], - }); - useDispose(mf); - - interface Env { - Greeter: { - sayHello: (str: string) => string; - }; - } - const { Greeter } = await mf.getBindings(); - - const helloWorld = Greeter.sayHello("World"); - - expect(helloWorld).toBe("Hello World"); -}); -test("Miniflare: getBindings() handles wrapped bindings returning objects containing functions", async ({ - expect, -}) => { - const mf = new Miniflare({ - workers: [ - { - wrappedBindings: { - Greeter: { - scriptName: "greeter-obj-implementation", - }, - }, - modules: true, - script: "", - }, - { - modules: true, - name: "greeter-obj-implementation", - script: ` - export default function (env) { - const objWithFunction = { - greeting: "Hello", - sayHello(name) { - return this.greeting + ' ' + name; - } - }; - return objWithFunction; - } - `, - }, - ], - }); - useDispose(mf); - - interface Env { - Greeter: { - greeting: string; - sayHello: (str: string) => string; - }; - } - const { Greeter } = await mf.getBindings(); - - const helloWorld = Greeter.sayHello("World"); - - expect(helloWorld).toBe("Hello World"); - expect(Greeter.greeting).toBe("Hello"); -}); -test("Miniflare: getBindings() handles wrapped bindings returning objects containing nested functions", async ({ - expect, -}) => { - const mf = new Miniflare({ - workers: [ - { - wrappedBindings: { - Greeter: { - scriptName: "greeter-obj-implementation", - }, - }, - modules: true, - script: "", - }, - { - modules: true, - name: "greeter-obj-implementation", - script: ` - export default function (env) { - const objWithFunction = { - obj: { - obj1: { - obj2: { - sayHello: (name) => "Hello " + name + " from a nested function" - } - } - } - }; - return objWithFunction; - } - `, - }, - ], - }); - useDispose(mf); - - interface Env { - Greeter: { - obj: { - obj1: { - obj2: { - sayHello: (str: string) => string; - }; - }; - }; - }; - } - const { Greeter } = await mf.getBindings(); - - const helloWorld = Greeter.obj.obj1.obj2.sayHello("World"); - - expect(helloWorld).toBe("Hello World from a nested function"); -}); -test("Miniflare: getBindings() handles wrapped bindings returning functions returning functions", async ({ - expect, -}) => { - const mf = new Miniflare({ - workers: [ - { - wrappedBindings: { - GreetFactory: { - scriptName: "greet-factory-obj-implementation", - }, - }, - modules: true, - script: "", - }, - { - modules: true, - name: "greet-factory-obj-implementation", - script: ` - export default function (env) { - const factory = { - getGreetFunction(name) { - return (name) => { - return this.greeting + ' ' + name; - } - }, - greeting: "Salutations", - }; - return factory; - } - `, - }, - ], - }); - useDispose(mf); - - interface Env { - GreetFactory: { - greeting: string; - getGreetFunction: () => (str: string) => string; - }; - } - const { GreetFactory } = await mf.getBindings(); - - const greetFunction = GreetFactory.getGreetFunction(); - - expect(greetFunction("Esteemed World")).toBe("Salutations Esteemed World"); - expect(GreetFactory.greeting).toBe("Salutations"); -}); test("Miniflare: getWorker() allows dispatching events directly", async ({ expect, }) => { @@ -3021,459 +2780,6 @@ test("Miniflare: supports unsafe eval bindings", async ({ expect }) => { expect(await response.text()).toBe("the computed value is 3"); }); -test("Miniflare: supports wrapped bindings", async ({ expect }) => { - const store = new Map(); - const mf = new Miniflare({ - workers: [ - { - wrappedBindings: { - MINI_KV: { - scriptName: "mini-kv", - bindings: { NAMESPACE: "ns" }, - }, - }, - modules: true, - script: `export default { - async fetch(request, env, ctx) { - await env.MINI_KV.set("key", "value"); - const value = await env.MINI_KV.get("key"); - await env.MINI_KV.delete("key"); - const emptyValue = await env.MINI_KV.get("key"); - await env.MINI_KV.set("key", "another value"); - return Response.json({ value, emptyValue }); - } - }`, - }, - { - name: "mini-kv", - serviceBindings: { - async STORE(request) { - const { pathname } = new URL(request.url); - const key = pathname.substring(1); - if (request.method === "GET") { - const value = store.get(key); - const status = value === undefined ? 404 : 200; - return new Response(value ?? null, { status }); - } else if (request.method === "PUT") { - const value = await request.text(); - store.set(key, value); - return new Response(null, { status: 204 }); - } else if (request.method === "DELETE") { - store.delete(key); - return new Response(null, { status: 204 }); - } else { - return new Response(null, { status: 405 }); - } - }, - }, - modules: true, - script: ` - class MiniKV { - constructor(env) { - this.STORE = env.STORE; - this.baseURL = "http://x/" + (env.NAMESPACE ?? "") + ":"; - } - async get(key) { - const res = await this.STORE.fetch(this.baseURL + key); - return res.status === 404 ? null : await res.text(); - } - async set(key, body) { - await this.STORE.fetch(this.baseURL + key, { method: "PUT", body }); - } - async delete(key) { - await this.STORE.fetch(this.baseURL + key, { method: "DELETE" }); - } - } - - export default function (env) { - return new MiniKV(env); - } - `, - }, - ], - }); - useDispose(mf); - - const res = await mf.dispatchFetch("http://localhost/"); - expect(await res.json()).toEqual({ value: "value", emptyValue: null }); - expect(store).toEqual(new Map([["ns:key", "another value"]])); -}); -test("Miniflare: check overrides default bindings with bindings from wrapped binding designator", async ({ - expect, -}) => { - const mf = new Miniflare({ - workers: [ - { - wrappedBindings: { - WRAPPED: { - scriptName: "binding", - entrypoint: "wrapped", - bindings: { B: "overridden b" }, - }, - }, - modules: true, - script: `export default { - fetch(request, env, ctx) { - return env.WRAPPED(); - } - }`, - }, - { - name: "binding", - modules: true, - bindings: { A: "default a", B: "default b" }, - script: `export function wrapped(env) { - return () => Response.json(env); - }`, - }, - ], - }); - useDispose(mf); - - const res = await mf.dispatchFetch("http://localhost/"); - expect(await res.json()).toEqual({ A: "default a", B: "overridden b" }); -}); -test("Miniflare: checks uses compatibility and outbound configuration of binder", async ({ - expect, -}) => { - const workers: WorkerOptions[] = [ - { - compatibilityDate: "2022-03-21", // Default-on date for `global_navigator` - compatibilityFlags: ["nodejs_compat"], - wrappedBindings: { WRAPPED: "binding" }, - modules: true, - script: `export default { - fetch(request, env, ctx) { - return env.WRAPPED(); - } - }`, - outboundService(request) { - return new Response(`outbound:${request.url}`); - }, - }, - { - name: "binding", - modules: [ - { - type: "ESModule", - path: "index.mjs", - contents: `export default function () { - return async () => { - const typeofNavigator = typeof navigator; - let importedNode = false; - try { - await import("node:util"); - importedNode = true; - } catch {} - const outboundRes = await fetch("http://placeholder/"); - const outboundText = await outboundRes.text(); - return Response.json({ typeofNavigator, importedNode, outboundText }); - } - }`, - }, - ], - }, - ]; - const mf = new Miniflare({ workers }); - useDispose(mf); - - let res = await mf.dispatchFetch("http://localhost/"); - expect(await res.json()).toEqual({ - typeofNavigator: "object", - importedNode: true, - outboundText: "outbound:http://placeholder/", - }); - - const fetchMock = createFetchMock(); - fetchMock.disableNetConnect(); - fetchMock - .get("http://placeholder") - .intercept({ path: "/" }) - .reply(200, "mocked"); - workers[0].compatibilityDate = "2022-03-20"; - workers[0].compatibilityFlags = []; - workers[0].outboundService = undefined; - workers[0].fetchMock = fetchMock; - await mf.setOptions({ workers }); - res = await mf.dispatchFetch("http://localhost/"); - expect(await res.json()).toEqual({ - typeofNavigator: "undefined", - importedNode: false, - outboundText: "mocked", - }); -}); -test("Miniflare: cannot call getWorker() on wrapped binding worker", async ({ - expect, -}) => { - const mf = new Miniflare({ - workers: [ - { - wrappedBindings: { WRAPPED: "binding" }, - modules: true, - script: `export default { - fetch(request, env, ctx) { - return env.WRAPPED; - } - }`, - }, - { - name: "binding", - modules: true, - script: `export default function () { - return "🎁"; - }`, - }, - ], - }); - useDispose(mf); - - await expect(mf.getWorker("binding")).rejects.toThrow( - new TypeError( - '"binding" is being used as a wrapped binding, and cannot be accessed as a worker' - ) - ); -}); -test("Miniflare: prohibits invalid wrapped bindings", async ({ expect }) => { - const mf = new Miniflare({ modules: true, script: "" }); - useDispose(mf); - - // Check prohibits using entrypoint worker - await expect( - mf.setOptions({ - name: "a", - modules: true, - script: "", - wrappedBindings: { - WRAPPED: { scriptName: "a", entrypoint: "wrapped" }, - }, - }) - ).rejects.toThrow( - new MiniflareCoreError( - "ERR_INVALID_WRAPPED", - 'Cannot use "a" for wrapped binding because it\'s the entrypoint.\n' + - 'Ensure "a" isn\'t the first entry in the `workers` array.' - ) - ); - - // Check prohibits using service worker - await expect( - mf.setOptions({ - workers: [ - { modules: true, script: "", wrappedBindings: { WRAPPED: "binding" } }, - { name: "binding", script: "" }, - ], - }) - ).rejects.toThrow( - new MiniflareCoreError( - "ERR_INVALID_WRAPPED", - 'Cannot use "binding" for wrapped binding because it\'s a service worker.\n' + - 'Ensure "binding" sets `modules` to `true` or an array of modules' - ) - ); - - // Check prohibits multiple modules - await expect( - mf.setOptions({ - workers: [ - { modules: true, script: "", wrappedBindings: { WRAPPED: "binding" } }, - { - name: "binding", - modules: [ - { type: "ESModule", path: "index.mjs", contents: "" }, - { type: "ESModule", path: "dep.mjs", contents: "" }, - ], - }, - ], - }) - ).rejects.toThrow( - new MiniflareCoreError( - "ERR_INVALID_WRAPPED", - 'Cannot use "binding" for wrapped binding because it isn\'t a single module.\n' + - 'Ensure "binding" doesn\'t include unbundled `import`s.' - ) - ); - - // Check prohibits non-ES-modules - await expect( - mf.setOptions({ - workers: [ - { modules: true, script: "", wrappedBindings: { WRAPPED: "binding" } }, - { - name: "binding", - modules: [{ type: "CommonJS", path: "index.cjs", contents: "" }], - }, - ], - }) - ).rejects.toThrow( - new MiniflareCoreError( - "ERR_INVALID_WRAPPED", - 'Cannot use "binding" for wrapped binding because it isn\'t a single ES module' - ) - ); - - // Check prohibits Durable Object bindings - await expect( - mf.setOptions({ - workers: [ - { - modules: true, - script: "", - wrappedBindings: { WRAPPED: "binding" }, - durableObjects: { - OBJECT: { scriptName: "binding", className: "TestObject" }, - }, - }, - { - name: "binding", - modules: [{ type: "ESModule", path: "index.mjs", contents: "" }], - }, - ], - }) - ).rejects.toThrow( - new MiniflareCoreError( - "ERR_INVALID_WRAPPED", - 'Cannot use "binding" for wrapped binding because it is bound to with Durable Object bindings.\n' + - 'Ensure other workers don\'t define Durable Object bindings to "binding".' - ) - ); - - // Check prohibits service bindings - await expect( - mf.setOptions({ - workers: [ - { - modules: true, - script: "", - wrappedBindings: { - WRAPPED: { scriptName: "binding", entrypoint: "wrapped" }, - }, - serviceBindings: { SERVICE: "binding" }, - }, - { - name: "binding", - modules: [{ type: "ESModule", path: "index.mjs", contents: "" }], - }, - ], - }) - ).rejects.toThrow( - new MiniflareCoreError( - "ERR_INVALID_WRAPPED", - 'Cannot use "binding" for wrapped binding because it is bound to with service bindings.\n' + - 'Ensure other workers don\'t define service bindings to "binding".' - ) - ); - - // Check prohibits compatibility date and flags - await expect( - mf.setOptions({ - workers: [ - { modules: true, script: "", wrappedBindings: { WRAPPED: "binding" } }, - { - name: "binding", - compatibilityDate: "2023-11-01", - modules: [{ type: "ESModule", path: "index.mjs", contents: "" }], - }, - ], - }) - ).rejects.toThrow( - new MiniflareCoreError( - "ERR_INVALID_WRAPPED", - 'Cannot use "binding" for wrapped binding because it defines a compatibility date.\n' + - "Wrapped bindings use the compatibility date of the worker with the binding." - ) - ); - await expect( - mf.setOptions({ - workers: [ - { modules: true, script: "", wrappedBindings: { WRAPPED: "binding" } }, - { - name: "binding", - compatibilityFlags: ["nodejs_compat"], - modules: [{ type: "ESModule", path: "index.mjs", contents: "" }], - }, - ], - }) - ).rejects.toThrow( - new MiniflareCoreError( - "ERR_INVALID_WRAPPED", - 'Cannot use "binding" for wrapped binding because it defines compatibility flags.\n' + - "Wrapped bindings use the compatibility flags of the worker with the binding." - ) - ); - - // Check prohibits outbound service - await expect( - mf.setOptions({ - workers: [ - { modules: true, script: "", wrappedBindings: { WRAPPED: "binding" } }, - { - name: "binding", - outboundService() { - assert.fail(); - }, - modules: [{ type: "ESModule", path: "index.mjs", contents: "" }], - }, - ], - }) - ).rejects.toThrow( - new MiniflareCoreError( - "ERR_INVALID_WRAPPED", - 'Cannot use "binding" for wrapped binding because it defines an outbound service.\n' + - "Wrapped bindings use the outbound service of the worker with the binding." - ) - ); - - // Check prohibits cyclic wrapped bindings - await expect( - mf.setOptions({ - workers: [ - { modules: true, script: "", wrappedBindings: { WRAPPED: "binding" } }, - { - name: "binding", - wrappedBindings: { WRAPPED: "binding" }, // Simple cycle - modules: [{ type: "ESModule", path: "index.mjs", contents: "" }], - }, - ], - }) - ).rejects.toThrow( - new MiniflareCoreError( - "ERR_CYCLIC", - "Generated workerd config contains cycles. Ensure wrapped bindings don't have bindings to themselves." - ) - ); - await expect( - mf.setOptions({ - workers: [ - { - modules: true, - script: "", - wrappedBindings: { WRAPPED1: "binding-1" }, - }, - { - name: "binding-1", - wrappedBindings: { WRAPPED2: "binding-2" }, - modules: [{ type: "ESModule", path: "index.mjs", contents: "" }], - }, - { - name: "binding-2", - wrappedBindings: { WRAPPED3: "binding-3" }, - modules: [{ type: "ESModule", path: "index.mjs", contents: "" }], - }, - { - name: "binding-3", - wrappedBindings: { WRAPPED1: "binding-1" }, // Multi-step cycle - modules: [{ type: "ESModule", path: "index.mjs", contents: "" }], - }, - ], - }) - ).rejects.toThrow( - new MiniflareCoreError( - "ERR_CYCLIC", - "Generated workerd config contains cycles. Ensure wrapped bindings don't have bindings to themselves." - ) - ); -}); - test("Miniflare: getCf() returns a standard cf object", async ({ expect }) => { const mf = new Miniflare({ script: "", modules: true }); useDispose(mf); @@ -3891,7 +3197,7 @@ test("Miniflare: respects rootPath for path-valued options", async ({ await fs.writeFile(path.join(tmp, "3.txt"), "three text"); const mf = new Miniflare({ rootPath: tmp, - kvPersist: "kv", + resourcePersistencePath: tmp, workers: [ { name: "a", @@ -3956,10 +3262,10 @@ test("Miniflare: respects rootPath for path-valued options", async ({ }); expect(existsSync(path.join(tmp, "kv", "namespace"))).toBe(true); - // Check persistence URLs not resolved relative to root path + // Check persisted KV data survives an options reload await mf.setOptions({ rootPath: tmp, - kvPersist: url.pathToFileURL(path.join(tmp, "kv")).href, + resourcePersistencePath: tmp, kvNamespaces: { NAMESPACE: "namespace" }, modules: true, script: `export default { diff --git a/packages/miniflare/test/logs.spec.ts b/packages/miniflare/test/logs.spec.ts index 72bb347012..3a449fd5c4 100644 --- a/packages/miniflare/test/logs.spec.ts +++ b/packages/miniflare/test/logs.spec.ts @@ -1,72 +1,26 @@ -import { Miniflare, MiniflareCoreError } from "miniflare"; -import { assert, test } from "vitest"; +import { Miniflare } from "miniflare"; +import { onTestFinished, test, vi } from "vitest"; import { useDispose } from "./test-shared"; import type { WorkerdStructuredLog } from "miniflare"; -test("logs are treated as standard stdout/stderr chunks by default", async ({ +test("logs are written to the console by default when no `handleStructuredLogs` is provided", async ({ expect, }) => { - const collected = { - stdout: "", - stderr: "", - }; - const mf = new Miniflare({ - modules: true, - handleRuntimeStdio(stdout, stderr) { - stdout.forEach((data) => { - collected.stdout += `${data}`; - }); - stderr.forEach((error) => { - collected.stderr += `${error}`; - }); - }, - script: ` - export default { - async fetch(req, env) { - console.log('__LOG__'); - console.warn('__WARN__'); - console.error('__ERROR__'); - console.info('__INFO__'); - console.debug('__DEBUG__'); - return new Response('Hello world!'); - } - }`, + const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + onTestFinished(() => { + logSpy.mockRestore(); + errorSpy.mockRestore(); }); - useDispose(mf); - - const response = await mf.dispatchFetch("http://localhost"); - await response.text(); - expect(collected.stdout).toBe("__LOG__\n__INFO__\n__DEBUG__\n"); - expect(collected.stderr).toBe("__WARN__\n__ERROR__\n"); -}); - -test("logs are structured and all sent to stdout when `structuredWorkerdLogs` is `true`", async ({ - expect, -}) => { - const collected = { - stdout: "", - stderr: "", - }; const mf = new Miniflare({ modules: true, - structuredWorkerdLogs: true, - handleRuntimeStdio(stdout, stderr) { - stdout.forEach((data) => { - collected.stdout += `${data}`; - }); - stderr.forEach((error) => { - collected.stderr += `${error}`; - }); - }, script: ` export default { async fetch(req, env) { console.log('__LOG__'); console.warn('__WARN__'); console.error('__ERROR__'); - console.info('__INFO__'); - console.debug('__DEBUG__'); return new Response('Hello world!'); } }`, @@ -76,26 +30,16 @@ test("logs are structured and all sent to stdout when `structuredWorkerdLogs` is const response = await mf.dispatchFetch("http://localhost"); await response.text(); - expect(collected.stdout).toMatch( - /{"timestamp":\d+,"level":"log","message":"__LOG__"}/ - ); - expect(collected.stdout).toMatch( - /{"timestamp":\d+,"level":"warn","message":"__WARN__"}/ - ); - expect(collected.stdout).toMatch( - /{"timestamp":\d+,"level":"error","message":"__ERROR__"}/ - ); - expect(collected.stdout).toMatch( - /{"timestamp":\d+,"level":"info","message":"__INFO__"}/ - ); - expect(collected.stdout).toMatch( - /{"timestamp":\d+,"level":"debug","message":"__DEBUG__"}/ - ); + const stdout = logSpy.mock.calls.map((args) => args.join(" ")).join("\n"); + const stderr = errorSpy.mock.calls.map((args) => args.join(" ")).join("\n"); - expect(collected.stderr).toBe(""); + // `log` goes to stdout; `warn`/`error` go to stderr + expect(stdout).toContain("__LOG__"); + expect(stderr).toContain("__WARN__"); + expect(stderr).toContain("__ERROR__"); }); -test("logs are structured and handled via `handleStructuredLogs` when such option is provided (no `structuredWorkerdLogs: true` needed)", async ({ +test("logs are structured and handled via `handleStructuredLogs` when such option is provided", async ({ expect, }) => { const collectedLogs: (Pick & { @@ -155,77 +99,6 @@ test("logs are structured and handled via `handleStructuredLogs` when such optio ]); }); -test("even when `handleStructuredLogs` is provided, `handleRuntimeStdio` can still be used to read the raw stream values", async ({ - expect, -}) => { - let numOfCollectedStructuredLogs = 0; - const collectedRaw = { - stdout: "", - stderr: "", - }; - const mf = new Miniflare({ - modules: true, - handleRuntimeStdio(stdout, stderr) { - stdout.forEach((data) => { - collectedRaw.stdout += `${data}`; - }); - stderr.forEach((error) => { - collectedRaw.stderr += `${error}`; - }); - }, - handleStructuredLogs() { - numOfCollectedStructuredLogs++; - }, - script: ` - export default { - async fetch(req, env) { - console.log('__LOG__'); - console.error('__ERROR__'); - return new Response('Hello world!'); - } - }`, - }); - useDispose(mf); - - const response = await mf.dispatchFetch("http://localhost"); - await response.text(); - - expect(numOfCollectedStructuredLogs).toBe(2); - - expect(collectedRaw.stdout).toMatch( - /{"timestamp":\d+,"level":"log","message":"__LOG__"}/ - ); - expect(collectedRaw.stdout).toMatch( - /{"timestamp":\d+,"level":"error","message":"__ERROR__"}/ - ); - expect(collectedRaw.stderr).toBe(""); -}); - -test("setting `handleStructuredLogs` when `structuredWorkerdLogs` is `false` triggers an error", async ({ - expect, -}) => { - const mf = new Miniflare({ modules: true, script: "" }); - useDispose(mf); - - let error: MiniflareCoreError | undefined = undefined; - try { - new Miniflare({ - modules: true, - script: "", - structuredWorkerdLogs: false, - handleStructuredLogs() {}, - }); - } catch (e) { - error = e as MiniflareCoreError; - } - - assert(error instanceof MiniflareCoreError); - expect(error.code).toBe("ERR_VALIDATION"); - expect(error.message).toContain( - "A `handleStructuredLogs` has been provided but `structuredWorkerdLogs` is set to `false`" - ); -}); - test("when using `handleStructuredLogs` some known unhelpful logs are filtered out (e.g. CODE_MOVED warnings)", async ({ expect, }) => { diff --git a/packages/miniflare/test/plugins/cache/index.spec.ts b/packages/miniflare/test/plugins/cache/index.spec.ts index 61189d36e7..c510b9ebf3 100644 --- a/packages/miniflare/test/plugins/cache/index.spec.ts +++ b/packages/miniflare/test/plugins/cache/index.spec.ts @@ -1,13 +1,8 @@ import assert from "node:assert"; import crypto from "node:crypto"; import fs from "node:fs/promises"; -import { - CACHE_PLUGIN_NAME, - LogLevel, - Miniflare, - Request, - Response, -} from "miniflare"; +import path from "node:path"; +import { CACHE_PLUGIN_NAME, Miniflare, Request, Response } from "miniflare"; import { beforeEach, type ExpectStatic, onTestFinished, test } from "vitest"; import { MiniflareDurableObjectControlStub, @@ -419,39 +414,13 @@ test("operations respect cf.cacheKey", async ({ expect }) => { const deleted2 = await cache.delete(key2); expect(deleted2).toBe(true); }); -test("operations log warning on workers.dev subdomain", async ({ expect }) => { - // Set option, then reset after test - await ctx.setOptions({ cacheWarnUsage: true }); - onTestFinished(() => ctx.setOptions({})); - ctx.caches = await ctx.mf.getCaches(); - const defaultObject = await getControlStub(ctx.mf); - - const cache = ctx.caches.default; - const key = "http://localhost/cache-workers-dev-warning"; - - ctx.log.logs = []; - const resToCache = new Response("body", { - headers: { "Cache-Control": "max-age=3600" }, - }); - await cache.put(key, resToCache.clone()); - await defaultObject.waitForFakeTasks(); - expect(ctx.log.logsAtLevel(LogLevel.WARN)).toEqual([ - "Cache operations will have no impact if you deploy to a workers.dev subdomain!", - ]); - - // Check only warns once - ctx.log.logs = []; - await cache.put(key, resToCache); - await defaultObject.waitForFakeTasks(); - expect(ctx.log.logsAtLevel(LogLevel.WARN)).toEqual([]); -}); test("operations persist cached data", async ({ expect }) => { // Create new temporary file-system persistence directory const tmp = await useTmp(); const opts: MiniflareOptions = { modules: true, script: "", - cachePersist: tmp, + resourcePersistencePath: tmp, }; let mf = new Miniflare(opts); useDispose(mf); @@ -465,8 +434,8 @@ test("operations persist cached data", async ({ expect }) => { }); await cache.put(key, resToCache); - // Check directory created for namespace - const names = await fs.readdir(tmp); + // Check directory created for namespace under the plugin subdirectory + const names = await fs.readdir(path.join(tmp, CACHE_PLUGIN_NAME)); expect(names.includes("miniflare-CacheObject")).toBe(true); // Check "restarting" keeps persisted data @@ -486,7 +455,7 @@ test("operations persist cached data", async ({ expect }) => { }); test("operations are no-ops when caching disabled", async ({ expect }) => { // Set option, then reset after test - await ctx.setOptions({ cache: false }); + await ctx.setOptions({ cacheAPI: false }); onTestFinished(() => ctx.setOptions({})); ctx.caches = await ctx.mf.getCaches(); diff --git a/packages/miniflare/test/plugins/core/inspector-proxy/index.spec.ts b/packages/miniflare/test/plugins/core/inspector-proxy/index.spec.ts index 1a0fba7a40..66181034f6 100644 --- a/packages/miniflare/test/plugins/core/inspector-proxy/index.spec.ts +++ b/packages/miniflare/test/plugins/core/inspector-proxy/index.spec.ts @@ -706,12 +706,10 @@ test("InspectorProxy: can proxy messages > 1MB", async ({ expect }) => { const mf = new Miniflare({ inspectorPort: 0, - // Avoid the default handling of stdio since that will console log the very large string in the test output. - handleRuntimeStdio(stdout, stderr) { - // We need to add these handlers otherwise the streams will not be consumed and the process will hang. - stdout.on("data", () => {}); - stderr.on("data", () => {}); - }, + // Avoid the default handling of stdio since that will console log the very + // large string in the test output. A no-op structured log handler consumes + // the runtime output without forwarding it to the console. + handleStructuredLogs() {}, workers: [ { script: ` diff --git a/packages/miniflare/test/plugins/core/proxy/client.spec.ts b/packages/miniflare/test/plugins/core/proxy/client.spec.ts index 01627a9275..b4f80928a2 100644 --- a/packages/miniflare/test/plugins/core/proxy/client.spec.ts +++ b/packages/miniflare/test/plugins/core/proxy/client.spec.ts @@ -1,6 +1,7 @@ import assert from "node:assert"; import { Blob } from "node:buffer"; import http from "node:http"; +import path from "node:path"; import { text } from "node:stream/consumers"; import { ReadableStream, WritableStream } from "node:stream/web"; import util from "node:util"; @@ -13,7 +14,7 @@ import { WebSocketPair, } from "miniflare"; import { describe, onTestFinished, test } from "vitest"; -import { useDispose } from "../../../test-shared"; +import { EXPORTED_FIXTURES, useDispose } from "../../../test-shared"; import type { Fetcher } from "@cloudflare/workers-types/experimental"; import type { MessageEvent, ReplaceWorkersTypes } from "miniflare"; @@ -81,38 +82,29 @@ describe("ProxyClient", () => { test("supports serialising multiple ReadableStreams, Blobs and Files", async ({ expect, }) => { - // For testing proxy client serialisation, add an API that just returns its - // arguments. Note without the `.pipeThrough(new TransformStream())` below, - // we'll see `TypeError: Inter-TransformStream ReadableStream.pipeTo() is - // not implemented.`. `IdentityTransformStream` doesn't work here. + // For testing proxy client serialisation, use a wrapped binding inside an + // unsafe binding. This is just an echo module that just returns its arguments + // (see the `echo-plugin` fixture). + const echoPlugin = path.resolve(EXPORTED_FIXTURES, "echo-plugin/index.js"); const mf = new Miniflare({ - workers: [ - { - name: "entry", - modules: true, - script: "", - wrappedBindings: { IDENTITY: "identity" }, - }, + name: "entry", + modules: true, + script: "", + unsafeBindings: [ { - name: "identity", - modules: true, - script: ` - class Identity { - async asyncIdentity(...args) { - const i = args.findIndex((arg) => arg instanceof ReadableStream); - if (i !== -1) args[i] = args[i].pipeThrough(new TransformStream()); - return args; - } - } - export default function() { return new Identity(); } - `, + name: "IDENTITY", + type: "wrapped", + plugin: { package: echoPlugin, name: "echo-plugin" }, + options: {}, }, ], }); useDispose(mf); const client = await mf._getProxyClient(); - const IDENTITY = client.env["MINIFLARE_PROXY:core:entry:IDENTITY"] as { + const IDENTITY = client.env[ + "MINIFLARE_PROXY:echo-plugin:entry:IDENTITY" + ] as { asyncIdentity(...args: Args): Promise; }; @@ -151,6 +143,7 @@ describe("ProxyClient", () => { expect(allResult[2].lastModified).toBe(1000); expect(await allResult[2].text()).toBe("text file"); }); + test("poisons dependent proxies after setOptions()/dispose()", async ({ expect, }) => { diff --git a/packages/miniflare/test/plugins/d1/suite.ts b/packages/miniflare/test/plugins/d1/suite.ts index 8b3cc171c8..60c20a69e7 100644 --- a/packages/miniflare/test/plugins/d1/suite.ts +++ b/packages/miniflare/test/plugins/d1/suite.ts @@ -1,7 +1,8 @@ import assert from "node:assert"; import fs from "node:fs/promises"; +import path from "node:path"; import { type D1Database } from "@cloudflare/workers-types/experimental"; -import { Miniflare } from "miniflare"; +import { D1_PLUGIN_NAME, Miniflare } from "miniflare"; import { beforeEach, type ExpectStatic, onTestFinished, test } from "vitest"; import { useDispose, useTmp, utf8Encode } from "../../test-shared"; import { binding, ctx, getDatabase, opts } from "./test"; @@ -507,7 +508,10 @@ test("operations persist D1 data", async ({ expect }) => { // Create new temporary file-system persistence directory const tmp = await useTmp(); - const persistOpts: MiniflareOptions = { ...opts, d1Persist: tmp }; + const persistOpts: MiniflareOptions = { + ...opts, + resourcePersistencePath: tmp, + }; const mf = new Miniflare(persistOpts); useDispose(mf); let db = await getDatabase(mf); @@ -524,8 +528,8 @@ test("operations persist D1 data", async ({ expect }) => { .first(); expect(result).toEqual({ name: "purple" }); - // Check directory created for database - const names = await fs.readdir(tmp); + // Check directory created for database under the plugin subdirectory + const names = await fs.readdir(path.join(tmp, D1_PLUGIN_NAME)); expect(names.includes("miniflare-D1DatabaseObject")).toBe(true); // Check "restarting" keeps persisted data @@ -599,7 +603,7 @@ test("dumpSql exports and imports complete database structure and content correc const tmp1 = await useTmp(); const originalMF = new Miniflare({ ...opts, - d1Persist: tmp1, + resourcePersistencePath: tmp1, d1Databases: { test: "test" }, }); useDispose(originalMF); @@ -620,7 +624,7 @@ test("dumpSql exports and imports complete database structure and content correc const tmp2 = await useTmp(); const mirrorMF = new Miniflare({ ...opts, - d1Persist: tmp2, + resourcePersistencePath: tmp2, d1Databases: { test: "test" }, }); useDispose(mirrorMF); diff --git a/packages/miniflare/test/plugins/do/index.spec.ts b/packages/miniflare/test/plugins/do/index.spec.ts index 049b06e7d9..e396c6ae58 100644 --- a/packages/miniflare/test/plugins/do/index.spec.ts +++ b/packages/miniflare/test/plugins/do/index.spec.ts @@ -5,6 +5,7 @@ import { setTimeout } from "node:timers/promises"; import { removeDir } from "@cloudflare/workers-utils"; import { DeferredPromise, + DURABLE_OBJECTS_PLUGIN_NAME, kUnsafeEphemeralUniqueKey, Miniflare, } from "miniflare"; @@ -71,20 +72,17 @@ test("persists Durable Object data in-memory between options reloads", async ({ res = await mf.dispatchFetch("http://localhost"); expect(await res.text()).toBe("Options #2: 2"); - opts.durableObjectsPersist = false; opts.script = COUNTER_SCRIPT("Options #3: "); await mf.setOptions(opts); res = await mf.dispatchFetch("http://localhost"); expect(await res.text()).toBe("Options #3: 3"); - opts.durableObjectsPersist = "memory:"; opts.script = COUNTER_SCRIPT("Options #4: "); await mf.setOptions(opts); res = await mf.dispatchFetch("http://localhost"); expect(await res.text()).toBe("Options #4: 4"); // Check a `new Miniflare()` instance has its own in-memory storage - delete opts.durableObjectsPersist; opts.script = COUNTER_SCRIPT("Options #5: "); await mf.dispose(); const mf2 = new Miniflare(opts); @@ -112,7 +110,7 @@ test("persists Durable Object data on file-system", async ({ expect }) => { modules: true, script: COUNTER_SCRIPT(), durableObjects: { COUNTER: "Counter" }, - durableObjectsPersist: tmp, + resourcePersistencePath: tmp, }; const mf = new Miniflare(opts); useDispose(mf); @@ -120,8 +118,9 @@ test("persists Durable Object data on file-system", async ({ expect }) => { let res = await mf.dispatchFetch("http://localhost"); expect(await res.text()).toBe("1"); - // Check directory created for "worker"'s Durable Object - const names = await fs.readdir(tmp); + // Check directory created for "worker"'s Durable Object under the plugin subdirectory + const doTmp = path.join(tmp, DURABLE_OBJECTS_PLUGIN_NAME); + const names = await fs.readdir(doTmp); expect(names).toEqual(["worker-Counter"]); // Check reloading keeps persisted data @@ -133,7 +132,7 @@ test("persists Durable Object data on file-system", async ({ expect }) => { // reload here as `workerd` keeps a copy of the SQLite database in-memory, // we also need to `dispose()` to avoid `EBUSY` error on Windows) await mf.dispose(); - await removeDir(path.join(tmp, names[0])); + await removeDir(path.join(doTmp, names[0])); const mf2 = new Miniflare(opts); useDispose(mf2); @@ -152,7 +151,7 @@ test("persists Durable Object data on file-system", async ({ expect }) => { test("lists Durable Object ids with persisted storage", async ({ expect }) => { const tmp = await useTmp(); const mf = new Miniflare({ - defaultPersistRoot: tmp, + resourcePersistencePath: tmp, name: "worker", modules: true, script: COUNTER_SCRIPT(), @@ -182,7 +181,7 @@ test("lists Durable Object ids with persisted storage", async ({ expect }) => { test("multiple Workers access same Durable Object data", async ({ expect }) => { const tmp = await useTmp(); const mf = new Miniflare({ - durableObjectsPersist: tmp, + resourcePersistencePath: tmp, workers: [ { name: "entry", @@ -234,8 +233,8 @@ test("multiple Workers access same Durable Object data", async ({ expect }) => { }); expect(await res.text()).toBe("via A: b: 1"); - // Check directory created for Durable Objects - const names = await fs.readdir(tmp); + // Check directory created for Durable Objects under the plugin subdirectory + const names = await fs.readdir(path.join(tmp, DURABLE_OBJECTS_PLUGIN_NAME)); expect(names.sort()).toEqual(["a-Counter", "b-Counter"]); // Check accessing via a different service accesses same persisted data diff --git a/packages/miniflare/test/plugins/email/index.spec.ts b/packages/miniflare/test/plugins/email/index.spec.ts index 6f11cc48cf..0f3dda9093 100644 --- a/packages/miniflare/test/plugins/email/index.spec.ts +++ b/packages/miniflare/test/plugins/email/index.spec.ts @@ -63,7 +63,7 @@ test("Unbound send_email binding works", async ({ expect }) => { email: { send_email: [{ name: "SEND_EMAIL" }], }, - defaultProjectTmpPath: projectTmpPath, + resourceTmpPath: projectTmpPath, compatibilityDate: "2025-03-17", }); @@ -163,7 +163,7 @@ test("Single allowed destination send_email binding works", async ({ { name: "SEND_EMAIL", destination_address: "someone-else@example.com" }, ], }, - defaultProjectTmpPath: projectTmpPath, + resourceTmpPath: projectTmpPath, compatibilityDate: "2025-03-17", }); @@ -1079,7 +1079,7 @@ test("MessageBuilder with text only", async ({ expect }) => { email: { send_email: [{ name: "SEND_EMAIL" }], }, - defaultProjectTmpPath: projectTmpPath, + resourceTmpPath: projectTmpPath, compatibilityDate: "2025-03-17", }); @@ -1194,7 +1194,7 @@ test("MessageBuilder with attachments", async ({ expect }) => { email: { send_email: [{ name: "SEND_EMAIL" }], }, - defaultProjectTmpPath: projectTmpPath, + resourceTmpPath: projectTmpPath, compatibilityDate: "2025-03-17", }); @@ -1260,7 +1260,7 @@ test("MessageBuilder log output format snapshot", async ({ expect }) => { email: { send_email: [{ name: "SEND_EMAIL" }], }, - defaultProjectTmpPath: projectTmpPath, + resourceTmpPath: projectTmpPath, compatibilityDate: "2025-03-17", }); @@ -2083,7 +2083,7 @@ test("disposing does not remove a concurrent email session", async ({ email: { send_email: [{ name: "SEND_EMAIL" }], }, - defaultProjectTmpPath: projectTmpPath, + resourceTmpPath: projectTmpPath, compatibilityDate: "2025-03-17", }); @@ -2123,7 +2123,7 @@ describe("EMAIL_PLUGIN.getServices", () => { }, sharedOptions: {}, tmpPath: tmp, - defaultProjectTmpPath: projectTmpPath, + resourceTmpPath: projectTmpPath, workerNames: ["default"], workerIndex: 0, } as unknown as Parameters[0]); @@ -2206,7 +2206,7 @@ describe("EMAIL_PLUGIN.getServices", () => { expect(emailDiskServices[1].path).toBe(projectDisk.disk.path); }); - test("creates only system disk service when defaultProjectTmpPath is undefined", async ({ + test("creates only system disk service when resourceTmpPath is undefined", async ({ expect, }) => { const tmp = await useTmp(); @@ -2217,7 +2217,7 @@ describe("EMAIL_PLUGIN.getServices", () => { }, sharedOptions: {}, tmpPath: tmp, - defaultProjectTmpPath: undefined, + resourceTmpPath: undefined, workerNames: ["default"], workerIndex: 0, } as unknown as Parameters[0]); @@ -2307,7 +2307,7 @@ describe("getEmailPathsToClean", () => { }); }); -test("MessageBuilder writes files to system temp when defaultProjectTmpPath is unset", async ({ +test("MessageBuilder writes files to system temp when resourceTmpPath is unset", async ({ expect, }) => { const log = new TestLog(); diff --git a/packages/miniflare/test/plugins/hello-world/index.spec.ts b/packages/miniflare/test/plugins/hello-world/index.spec.ts index 5e6995beb0..c9f0d90503 100644 --- a/packages/miniflare/test/plugins/hello-world/index.spec.ts +++ b/packages/miniflare/test/plugins/hello-world/index.spec.ts @@ -10,7 +10,6 @@ test("hello-world", async ({ expect }) => { enable_timer: true, }, }, - helloWorldPersist: false, modules: true, script: ` export default { diff --git a/packages/miniflare/test/plugins/images/index.spec.ts b/packages/miniflare/test/plugins/images/index.spec.ts index 2a21050285..ecd3784143 100644 --- a/packages/miniflare/test/plugins/images/index.spec.ts +++ b/packages/miniflare/test/plugins/images/index.spec.ts @@ -54,7 +54,6 @@ function createMiniflare(): Miniflare { return new Miniflare({ compatibilityDate: "2025-04-01", images: { binding: "IMAGES" }, - imagesPersist: false, modules: true, script: WORKER_SCRIPT, } satisfies MiniflareOptions); diff --git a/packages/miniflare/test/plugins/kv/index.spec.ts b/packages/miniflare/test/plugins/kv/index.spec.ts index de91b1741a..c595402abb 100644 --- a/packages/miniflare/test/plugins/kv/index.spec.ts +++ b/packages/miniflare/test/plugins/kv/index.spec.ts @@ -1,7 +1,7 @@ import assert from "node:assert"; import { Blob } from "node:buffer"; import fs from "node:fs/promises"; -import consumers from "node:stream/consumers"; +import path from "node:path"; import { KV_PLUGIN_NAME, MAX_BULK_GET_KEYS, Miniflare } from "miniflare"; import { beforeEach, type ExpectStatic, test } from "vitest"; import { @@ -788,7 +788,7 @@ test("persists on file-system", async ({ expect }) => { modules: true, script: "", kvNamespaces: { NAMESPACE: "namespace" }, - kvPersist: tmp, + resourcePersistencePath: tmp, }; let mf = new Miniflare(opts); useDispose(mf); @@ -797,8 +797,8 @@ test("persists on file-system", async ({ expect }) => { await kv.put("key", "value"); expect(await kv.get("key")).toBe("value"); - // Check directory created for namespace - const names = await fs.readdir(tmp); + // Check directory created for namespace under the plugin subdirectory + const names = await fs.readdir(path.join(tmp, KV_PLUGIN_NAME)); expect(names.includes("miniflare-KVNamespaceObject")).toBe(true); // Check "restarting" keeps persisted data @@ -808,43 +808,3 @@ test("persists on file-system", async ({ expect }) => { kv = await mf.getKVNamespace("NAMESPACE"); expect(await kv.get("key")).toBe("value"); }); - -test("sticky blobs never deleted", async ({ expect }) => { - // Checking regular behaviour that old blobs deleted in `put: overrides - // existing keys` test. Only testing sticky blobs for KV, as the blob store - // should only be constructed in the shared `MiniflareDurableObject` ABC. - - // Create instance with sticky blobs enabled (can't use `ctx.mf`) - const mf = new Miniflare({ - script: "", - modules: true, - kvNamespaces: ["NAMESPACE"], - unsafeStickyBlobs: true, - }); - useDispose(mf); - - // Create control stub for newly created instance's namespace - const objectNamespace = await mf._getInternalDurableObjectNamespace( - KV_PLUGIN_NAME, - "kv:ns", - "KVNamespaceObject" - ); - const objectId = objectNamespace.idFromName("NAMESPACE"); - const objectStub = objectNamespace.get(objectId); - const object = new MiniflareDurableObjectControlStub(objectStub); - await object.enableFakeTimers(secondsToMillis(TIME_NOW)); - const stmts = sqlStmts(object); - - // Store something in the namespace and get the blob ID - const ns = await mf.getKVNamespace("NAMESPACE"); - await ns.put("key", "value 1"); - const blobId = await stmts.getBlobIdByKey("key"); - assert(blobId !== undefined); - - // Override key and check we can still access the old blob - await ns.put("key", "value 2"); - await object.waitForFakeTasks(); - const blob = await object.getBlob(blobId); - assert(blob !== null); - expect(await consumers.text(blob)).toBe("value 1"); -}); diff --git a/packages/miniflare/test/plugins/local-explorer/aggregation.spec.ts b/packages/miniflare/test/plugins/local-explorer/aggregation.spec.ts index 1034f276a7..32c677a819 100644 --- a/packages/miniflare/test/plugins/local-explorer/aggregation.spec.ts +++ b/packages/miniflare/test/plugins/local-explorer/aggregation.spec.ts @@ -633,7 +633,7 @@ describe("Same ID across multiple instances with same persistence directories", script: `export default { fetch() { return new Response("Worker A"); } }`, unsafeLocalExplorer: true, unsafeDevRegistryPath: registryPath, - defaultPersistRoot: persistencePath, + resourcePersistencePath: persistencePath, kvNamespaces: { MY_KV: "shared-kv-id", }, @@ -652,7 +652,7 @@ describe("Same ID across multiple instances with same persistence directories", script: `export default { fetch() { return new Response("Worker B"); } }`, unsafeLocalExplorer: true, unsafeDevRegistryPath: registryPath, - defaultPersistRoot: persistencePath, + resourcePersistencePath: persistencePath, kvNamespaces: { MY_KV: "shared-kv-id", }, diff --git a/packages/miniflare/test/plugins/local-explorer/do.spec.ts b/packages/miniflare/test/plugins/local-explorer/do.spec.ts index d28897ac18..940dea834f 100644 --- a/packages/miniflare/test/plugins/local-explorer/do.spec.ts +++ b/packages/miniflare/test/plugins/local-explorer/do.spec.ts @@ -188,7 +188,7 @@ describe("Durable Objects API", () => { } `, unsafeLocalExplorer: true, - defaultPersistRoot: persistPath, + resourcePersistencePath: persistPath, durableObjects: { TEST_DO: { className: "TestDO", useSQLite: true }, }, diff --git a/packages/miniflare/test/plugins/r2/index.spec.ts b/packages/miniflare/test/plugins/r2/index.spec.ts index c48ff14dcf..7ca639a658 100644 --- a/packages/miniflare/test/plugins/r2/index.spec.ts +++ b/packages/miniflare/test/plugins/r2/index.spec.ts @@ -3,6 +3,7 @@ import assert from "node:assert"; import crypto from "node:crypto"; import fs from "node:fs/promises"; +import path from "node:path"; import { text } from "node:stream/consumers"; import { Headers, Miniflare, R2_PLUGIN_NAME } from "miniflare"; import { beforeEach, type ExpectStatic, onTestFinished, test } from "vitest"; @@ -998,7 +999,7 @@ test("operations persist stored data", async ({ expect }) => { modules: true, script: "", r2Buckets: { BUCKET: "bucket" }, - r2Persist: tmp, + resourcePersistencePath: tmp, }; const mf = new Miniflare(persistOpts); useDispose(mf); @@ -1011,8 +1012,8 @@ test("operations persist stored data", async ({ expect }) => { let object = await r2.head("key"); expect(object?.size).toBe(5); - // Check directory created for namespace - const names = await fs.readdir(tmp); + // Check directory created for namespace under the plugin subdirectory + const names = await fs.readdir(path.join(tmp, R2_PLUGIN_NAME)); expect(names.includes("miniflare-R2BucketObject")).toBe(true); // Check "restarting" keeps persisted data diff --git a/packages/miniflare/test/plugins/secret-store/index.spec.ts b/packages/miniflare/test/plugins/secret-store/index.spec.ts index 25de329f69..50dda2f052 100644 --- a/packages/miniflare/test/plugins/secret-store/index.spec.ts +++ b/packages/miniflare/test/plugins/secret-store/index.spec.ts @@ -11,7 +11,6 @@ test("single secret-store", async ({ expect }) => { secret_name: "secret_name", }, }, - secretsStorePersist: false, modules: true, script: ` export default { diff --git a/packages/miniflare/test/plugins/stream/index.spec.ts b/packages/miniflare/test/plugins/stream/index.spec.ts index 202c2a94db..e47bc902fd 100644 --- a/packages/miniflare/test/plugins/stream/index.spec.ts +++ b/packages/miniflare/test/plugins/stream/index.spec.ts @@ -1,4 +1,3 @@ -import { pathToFileURL } from "node:url"; import { Miniflare, STREAM_COMPAT_DATE, @@ -140,7 +139,6 @@ function createMiniflare(options: Partial = {}): Miniflare { return new Miniflare({ compatibilityDate: STREAM_COMPAT_DATE, stream: { binding: "STREAM" }, - streamPersist: false, modules: true, script: WORKER_SCRIPT, ...options, @@ -706,7 +704,6 @@ describe("Stream reloads", () => { const opts = { compatibilityDate: STREAM_COMPAT_DATE, stream: { binding: "STREAM" }, - streamPersist: false, modules: true, script: WORKER_SCRIPT, } satisfies MiniflareOptions; @@ -732,9 +729,7 @@ describe("Stream reloads", () => { expect(videos[0].id).toBe(video.id); }); - test("keeps persisted data when persistence path format changes on reload", async ({ - expect, - }) => { + test("keeps persisted data across setOptions reloads", async ({ expect }) => { const tmp = await useTmp(); const { http: videoUrl } = await useServer( staticBytesListener(TEST_VIDEO_BYTES) @@ -742,7 +737,7 @@ describe("Stream reloads", () => { const opts = { compatibilityDate: STREAM_COMPAT_DATE, stream: { binding: "STREAM" }, - streamPersist: tmp, + resourcePersistencePath: tmp, modules: true, script: WORKER_SCRIPT, } satisfies MiniflareOptions; @@ -755,7 +750,6 @@ describe("Stream reloads", () => { await mf.setOptions({ ...opts, - streamPersist: pathToFileURL(tmp).href, script: `${WORKER_SCRIPT}\n// reload persisted stream worker`, }); diff --git a/packages/miniflare/test/plugins/workflows/index.spec.ts b/packages/miniflare/test/plugins/workflows/index.spec.ts index 56061c1fb7..9ab62d25b6 100644 --- a/packages/miniflare/test/plugins/workflows/index.spec.ts +++ b/packages/miniflare/test/plugins/workflows/index.spec.ts @@ -1,6 +1,7 @@ import * as fs from "node:fs/promises"; +import path from "node:path"; import { scheduler } from "node:timers/promises"; -import { Miniflare } from "miniflare"; +import { Miniflare, WORKFLOWS_PLUGIN_NAME } from "miniflare"; import { describe, test } from "vitest"; import { useDispose, useTmp } from "../../test-shared"; import type { MiniflareOptions } from "miniflare"; @@ -42,7 +43,7 @@ test("starts Workflows with user-provided experimental compatibility flag", asyn ], }, }, - workflowsPersist: tmp, + resourcePersistencePath: tmp, }); useDispose(mf); @@ -67,7 +68,7 @@ test("persists Workflow data on file-system between runs", async ({ name: "MY_WORKFLOW", }, }, - workflowsPersist: tmp, + resourcePersistencePath: tmp, }; const mf = new Miniflare(opts); useDispose(mf); @@ -97,8 +98,8 @@ test("persists Workflow data on file-system between runs", async ({ true ); - // check if files were committed - const names = await fs.readdir(tmp); + // check if files were committed under the plugin subdirectory + const names = await fs.readdir(path.join(tmp, WORKFLOWS_PLUGIN_NAME)); expect(names).toEqual(["miniflare-workflows-MY_WORKFLOW"]); // restart miniflare @@ -191,7 +192,7 @@ function lifecycleMiniflareOpts(tmp: string): MiniflareOptions { name: "LIFECYCLE_WORKFLOW", }, }, - workflowsPersist: tmp, + resourcePersistencePath: tmp, }; } diff --git a/packages/vite-plugin-cloudflare/src/miniflare-options.ts b/packages/vite-plugin-cloudflare/src/miniflare-options.ts index 5b9eef7644..44b98d44c6 100644 --- a/packages/vite-plugin-cloudflare/src/miniflare-options.ts +++ b/packages/vite-plugin-cloudflare/src/miniflare-options.ts @@ -251,6 +251,7 @@ export async function getDevMiniflareOptions( ]; const containerTagToOptionsMap: ContainerTagToOptionsMap = new Map(); + let containerEngine: string | undefined; const workersFromConfig = resolvedPluginConfig.type === "workers" @@ -293,8 +294,7 @@ export async function getDevMiniflareOptions( worker.config.dev.enable_containers ) { const dockerPath = getDockerPath(); - worker.config.dev.container_engine = - resolveDockerHost(dockerPath); + containerEngine = resolveDockerHost(dockerPath); containerBuildId = generateContainerBuildId(); const options = getContainerOptions({ @@ -474,14 +474,12 @@ export async function getDevMiniflareOptions( unsafeLocalExplorer: getLocalExplorerEnabledFromEnv(), telemetry: { enabled: false }, handleStructuredLogs: getStructuredLogsLogger(logger), - defaultPersistRoot: getPersistenceRoot( + resourcePersistencePath: getPersistenceRoot( resolvedViteConfig.root, resolvedPluginConfig.persistState ), - defaultProjectTmpPath: path.resolve( - resolvedViteConfig.root, - ".wrangler/tmp" - ), + resourceTmpPath: path.resolve(resolvedViteConfig.root, ".wrangler/tmp"), + containerEngine, workers: [...assetWorkers, ...externalWorkers, ...userWorkers], async unsafeModuleFallbackService(request) { const parsed = await parseModuleFallbackRequest(request); @@ -648,6 +646,7 @@ export async function getPreviewMiniflareOptions( ); const { resolvedPluginConfig, resolvedViteConfig } = ctx; const containerTagToOptionsMap: ContainerTagToOptionsMap = new Map(); + let containerEngine: string | undefined; const workers: Array = ( await Promise.all( @@ -688,7 +687,7 @@ export async function getPreviewMiniflareOptions( workerConfig.dev.enable_containers ) { const dockerPath = getDockerPath(); - workerConfig.dev.container_engine = resolveDockerHost(dockerPath); + containerEngine = resolveDockerHost(dockerPath); containerBuildId = generateContainerBuildId(); const options = getContainerOptions({ @@ -758,14 +757,12 @@ export async function getPreviewMiniflareOptions( unsafeLocalExplorer: getLocalExplorerEnabledFromEnv(), telemetry: { enabled: false }, handleStructuredLogs: getStructuredLogsLogger(logger), - defaultPersistRoot: getPersistenceRoot( + resourcePersistencePath: getPersistenceRoot( resolvedViteConfig.root, resolvedPluginConfig.persistState ), - defaultProjectTmpPath: path.resolve( - resolvedViteConfig.root, - ".wrangler/tmp" - ), + resourceTmpPath: path.resolve(resolvedViteConfig.root, ".wrangler/tmp"), + containerEngine, workers, }, containerTagToOptionsMap, diff --git a/packages/vitest-pool-workers/src/pool/index.ts b/packages/vitest-pool-workers/src/pool/index.ts index 26edf2485d..7d34883a57 100644 --- a/packages/vitest-pool-workers/src/pool/index.ts +++ b/packages/vitest-pool-workers/src/pool/index.ts @@ -626,7 +626,6 @@ const SHARED_MINIFLARE_OPTIONS: SharedOptions = { log: mfLog, verbose: true, handleStructuredLogs, - unsafeStickyBlobs: true, } satisfies Partial; const DEFAULT_INSPECTOR_PORT = 9229; diff --git a/packages/wrangler/src/api/integrations/platform/index.ts b/packages/wrangler/src/api/integrations/platform/index.ts index cb35d7e655..522193935c 100644 --- a/packages/wrangler/src/api/integrations/platform/index.ts +++ b/packages/wrangler/src/api/integrations/platform/index.ts @@ -1,8 +1,6 @@ import path from "node:path"; -import { resolveDockerHost } from "@cloudflare/containers-shared"; import { extractBindingsOfType } from "@cloudflare/deploy-helpers"; import { - getDockerPath, getRegistryPath, getTodaysCompatDate, } from "@cloudflare/workers-utils"; @@ -321,11 +319,11 @@ async function getMiniflareOptionsFromConfig(args: { ? buildAssetOptions({ assets: processedAssetOptions }) : {}; - const defaultPersistRoot = getMiniflarePersistRoot(options.persist); + const resourcePersistencePath = getMiniflarePersistRoot(options.persist); const projectRoot = config.userConfigPath ? path.dirname(config.userConfigPath) : process.cwd(); - const defaultProjectTmpPath = getDefaultProjectTmpPath(projectRoot); + const resourceTmpPath = getDefaultProjectTmpPath(projectRoot); const miniflareOptions: MiniflareOptions = { workers: [ @@ -339,8 +337,8 @@ async function getMiniflareOptionsFromConfig(args: { }, ...externalWorkers, ], - defaultPersistRoot, - defaultProjectTmpPath, + resourcePersistencePath, + resourceTmpPath, }; return { @@ -502,15 +500,10 @@ export function unstable_getMiniflareWorkerOptions( ? buildAssetOptions({ assets: processedAssetOptions }) : {}; - const useContainers = - config.dev?.enable_containers && config.containers?.length; const workerOptions: SourcelessWorkerOptions = { compatibilityDate: config.compatibility_date, compatibilityFlags: config.compatibility_flags, modulesRules, - containerEngine: useContainers - ? (config.dev.container_engine ?? resolveDockerHost(getDockerPath())) - : undefined, zone: getZoneFromConfig(config), ...bindingOptions, diff --git a/packages/wrangler/src/api/startDevWorker/LocalRuntimeController.ts b/packages/wrangler/src/api/startDevWorker/LocalRuntimeController.ts index a38e11118b..4e1c4bd18e 100644 --- a/packages/wrangler/src/api/startDevWorker/LocalRuntimeController.ts +++ b/packages/wrangler/src/api/startDevWorker/LocalRuntimeController.ts @@ -199,8 +199,6 @@ export async function convertToConfigBundle( queueConsumers, outboundService: event.config.dev.outboundService, localProtocol: event.config.dev?.server?.secure ? "https" : "http", - httpsCertPath: event.config.dev?.server?.httpsCertPath, - httpsKeyPath: event.config.dev?.server?.httpsKeyPath, localUpstream: event.config.dev?.origin?.hostname, upstreamProtocol: event.config.dev?.origin?.secure ? "https" : "http", testScheduled: !!event.config.dev.testScheduled, diff --git a/packages/wrangler/src/api/startDevWorker/ProxyController.ts b/packages/wrangler/src/api/startDevWorker/ProxyController.ts index 1bfcc3a449..a5fee81f14 100644 --- a/packages/wrangler/src/api/startDevWorker/ProxyController.ts +++ b/packages/wrangler/src/api/startDevWorker/ProxyController.ts @@ -113,7 +113,7 @@ export class ProxyController extends Controller { }, // no need to use file-system, so don't - cache: false, + cacheAPI: false, unsafeEphemeralDurableObjects: true, }, ], @@ -172,7 +172,7 @@ export class ProxyController extends Controller { }, ], // no need to use file-system, so don't - cache: false, + cacheAPI: false, unsafeEphemeralDurableObjects: true, }); } diff --git a/packages/wrangler/src/d1/execute.ts b/packages/wrangler/src/d1/execute.ts index 327824bf79..290a0a64fb 100644 --- a/packages/wrangler/src/d1/execute.ts +++ b/packages/wrangler/src/d1/execute.ts @@ -317,7 +317,8 @@ async function executeLocally({ // TODO(#11870): Really we should prefer localDB.name here, but that would break users with existing local databases. const id = localDB.previewDatabaseUuid ?? localDB.uuid ?? localDB.binding; const persistencePath = getLocalPersistencePath(persistTo, config); - const d1Persist = path.join(persistencePath, "v3", "d1"); + const resourcePersistencePath = path.join(persistencePath, "v3"); + const d1Persist = path.join(resourcePersistencePath, "d1"); logger.log( `🌀 Executing on local database ${name} (${id}) from ${readableRelative( @@ -331,7 +332,7 @@ async function executeLocally({ const mf = new Miniflare({ modules: true, script: "", - d1Persist, + resourcePersistencePath, d1Databases: { DATABASE: id }, }); const db = await mf.getD1Database("DATABASE"); diff --git a/packages/wrangler/src/d1/export.ts b/packages/wrangler/src/d1/export.ts index b6e6ae7178..43617f0c34 100644 --- a/packages/wrangler/src/d1/export.ts +++ b/packages/wrangler/src/d1/export.ts @@ -152,7 +152,8 @@ async function exportLocal( // TODO: should we allow customising persistence path? // Should it be --persist-to for consistency (even though this isn't persisting anything)? const persistencePath = getLocalPersistencePath(undefined, config); - const d1Persist = path.join(persistencePath, "v3", "d1"); + const resourcePersistencePath = path.join(persistencePath, "v3"); + const d1Persist = path.join(resourcePersistencePath, "d1"); logger.log( `🌀 Exporting local database ${name} (${id}) from ${readableRelative( @@ -166,7 +167,7 @@ async function exportLocal( const mf = new Miniflare({ modules: true, script: "export default {}", - d1Persist, + resourcePersistencePath, d1Databases: { DATABASE: id }, }); const db = await mf.getD1Database("DATABASE"); diff --git a/packages/wrangler/src/dev/miniflare/index.ts b/packages/wrangler/src/dev/miniflare/index.ts index 50bf3e46cd..d2c8964f6b 100644 --- a/packages/wrangler/src/dev/miniflare/index.ts +++ b/packages/wrangler/src/dev/miniflare/index.ts @@ -91,8 +91,6 @@ export interface ConfigBundle { routes: string[] | undefined; queueConsumers: Config["queues"]["consumers"]; localProtocol: "http" | "https"; - httpsKeyPath: string | undefined; - httpsCertPath: string | undefined; localUpstream: string | undefined; upstreamProtocol: "http" | "https"; inspect: boolean; @@ -465,7 +463,6 @@ type WorkerOptionsBindings = Pick< | "serviceBindings" | "ratelimits" | "workflows" - | "wrappedBindings" | "secretsStoreSecrets" | "images" | "email" @@ -695,8 +692,6 @@ export function buildMiniflareBindingOptions( const externalWorkers: WorkerOptions[] = []; - const wrappedBindings: WorkerOptions["wrappedBindings"] = {}; - for (const ai of aiBindings) { warnOrError("ai", ai.remote); } @@ -1084,7 +1079,6 @@ export function buildMiniflareBindingOptions( }) ), serviceBindings, - wrappedBindings: wrappedBindings, tails, streamingTails, }; @@ -1158,8 +1152,10 @@ export async function buildMiniflareOptions( bindingOptions.browserRendering.headful = true; } const sitesOptions = buildSitesOptions(config); - const defaultPersistRoot = getDefaultPersistRoot(config.localPersistencePath); - const defaultProjectTmpPath = getDefaultProjectTmpPath(config.projectRoot); + const resourcePersistencePath = getDefaultPersistRoot( + config.localPersistencePath + ); + const resourceTmpPath = getDefaultProjectTmpPath(config.projectRoot); const assetOptions = buildAssetOptions(config); const options: MiniflareOptions = { @@ -1186,8 +1182,9 @@ export async function buildMiniflareOptions( log, verbose: logger.loggerLevel === "debug", handleStructuredLogs: config.structuredLogsHandler ?? handleStructuredLogs, - defaultPersistRoot, - defaultProjectTmpPath, + resourcePersistencePath, + resourceTmpPath, + containerEngine: config.containerEngine, workers: [ { name: getName(config), @@ -1200,7 +1197,6 @@ export async function buildMiniflareOptions( ...assetOptions, routes: config.routes, outboundService: config.outboundService, - containerEngine: config.containerEngine, zone: config.zone, }, ...externalWorkers, diff --git a/packages/wrangler/src/hello-world/index.ts b/packages/wrangler/src/hello-world/index.ts index 3a6f893441..9145ad0c1e 100644 --- a/packages/wrangler/src/hello-world/index.ts +++ b/packages/wrangler/src/hello-world/index.ts @@ -23,11 +23,11 @@ export async function usingLocalHelloWorldBinding( ) => Promise ): Promise { const persist = getLocalPersistencePath(persistTo, config); - const defaultPersistRoot = getDefaultPersistRoot(persist); + const resourcePersistencePath = getDefaultPersistRoot(persist); const mf = new Miniflare({ script: 'addEventListener("fetch", (e) => e.respondWith(new Response(null, { status: 404 })))', - defaultPersistRoot, + resourcePersistencePath, helloWorld: { BINDING: { enable_timer: false, diff --git a/packages/wrangler/src/kv/helpers.ts b/packages/wrangler/src/kv/helpers.ts index cd01bff4b3..f07ba89437 100644 --- a/packages/wrangler/src/kv/helpers.ts +++ b/packages/wrangler/src/kv/helpers.ts @@ -584,11 +584,11 @@ export async function usingLocalNamespace( // We need to cast to Config for the getLocalPersistencePath function since // it expects a full Config object, even though it only uses compliance_region const persist = getLocalPersistencePath(persistTo, config); - const defaultPersistRoot = getDefaultPersistRoot(persist); + const resourcePersistencePath = getDefaultPersistRoot(persist); const mf = new Miniflare({ script: 'addEventListener("fetch", (e) => e.respondWith(new Response(null, { status: 404 })))', - defaultPersistRoot, + resourcePersistencePath, kvNamespaces: { NAMESPACE: namespaceId }, }); const namespace = await mf.getKVNamespace("NAMESPACE"); diff --git a/packages/wrangler/src/r2/helpers/object.ts b/packages/wrangler/src/r2/helpers/object.ts index 9563b60ed9..3a169ed0fd 100644 --- a/packages/wrangler/src/r2/helpers/object.ts +++ b/packages/wrangler/src/r2/helpers/object.ts @@ -157,7 +157,7 @@ export async function usingLocalBucket( ) => Promise ): Promise { const persist = getLocalPersistencePath(persistTo, config); - const defaultPersistRoot = getDefaultPersistRoot(persist); + const resourcePersistencePath = getDefaultPersistRoot(persist); const mf = new Miniflare({ modules: true, // TODO(soon): import `reduceError()` from `miniflare:shared` @@ -189,7 +189,7 @@ export async function usingLocalBucket( } } }`, - defaultPersistRoot, + resourcePersistencePath, r2Buckets: { BUCKET: bucketName }, }); const bucket = await mf.getR2Bucket("BUCKET"); diff --git a/packages/wrangler/src/secrets-store/commands.ts b/packages/wrangler/src/secrets-store/commands.ts index 040e4cf502..aeabc4796f 100644 --- a/packages/wrangler/src/secrets-store/commands.ts +++ b/packages/wrangler/src/secrets-store/commands.ts @@ -34,11 +34,11 @@ export async function usingLocalSecretsStoreSecretAPI( ) => Promise ): Promise { const persist = getLocalPersistencePath(persistTo, config); - const defaultPersistRoot = getDefaultPersistRoot(persist); + const resourcePersistencePath = getDefaultPersistRoot(persist); const mf = new Miniflare({ script: 'addEventListener("fetch", (e) => e.respondWith(new Response(null, { status: 404 })))', - defaultPersistRoot, + resourcePersistencePath, secretsStoreSecrets: { SECRET: { store_id: storeId,