diff --git a/.changeset/v1-container-command-guidance.md b/.changeset/v1-container-command-guidance.md new file mode 100644 index 0000000000..49ed490df2 --- /dev/null +++ b/.changeset/v1-container-command-guidance.md @@ -0,0 +1,7 @@ +--- +"@cloudflare/vite-plugin": patch +--- + +Fix Container setup guidance in Vite development and preview + +Container registry instructions now show the appropriate CLI command and configuration location, including Wrangler files, inline Vite configuration, and the experimental cf delegate. diff --git a/packages/remote-bindings/package.json b/packages/remote-bindings/package.json index 33324b278f..96701fe89c 100644 --- a/packages/remote-bindings/package.json +++ b/packages/remote-bindings/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@cloudflare/cli-shared-helpers": "0.2.6", - "@cloudflare/deploy-helpers": "0.21.0", + "@cloudflare/deploy-helpers": "0.22.0", "@cloudflare/workers-auth": "0.14.0", "@cloudflare/workers-utils": "0.47.2", "chalk": "catalog:default", diff --git a/packages/vite-plugin-cloudflare/package.json b/packages/vite-plugin-cloudflare/package.json index f4d42cc22d..70054fa4ca 100644 --- a/packages/vite-plugin-cloudflare/package.json +++ b/packages/vite-plugin-cloudflare/package.json @@ -59,7 +59,7 @@ "devDependencies": { "@cloudflare/build-output-utils": "0.9.3", "@cloudflare/config": "0.27.0", - "@cloudflare/containers-shared": "0.22.1", + "@cloudflare/containers-shared": "0.23.0", "@cloudflare/mock-npm-registry": "workspace:*", "@cloudflare/remote-bindings": "workspace:*", "@cloudflare/runtime-types": "0.1.13", diff --git a/packages/vite-plugin-cloudflare/src/__tests__/containers-server.spec.ts b/packages/vite-plugin-cloudflare/src/__tests__/containers-server.spec.ts index 9fc75f213a..dc1353a567 100644 --- a/packages/vite-plugin-cloudflare/src/__tests__/containers-server.spec.ts +++ b/packages/vite-plugin-cloudflare/src/__tests__/containers-server.spec.ts @@ -402,6 +402,66 @@ export default { plugins: [cloudflare({ inspectorPort: false, persistState: fals ); } + test("uses inline configuration guidance without inventing a config file", async ({ + expect, + onTestFinished, + }) => { + fs.writeFileSync("package.json", JSON.stringify({ type: "module" })); + fs.writeFileSync("Dockerfile", "FROM alpine:3.19\n"); + fs.writeFileSync( + "index.js", + `import { DurableObject } from "cloudflare:workers"; +export class Probe extends DurableObject {} +export default { fetch() { return new Response("ready"); } };` + ); + function config() { + return { + root: process.cwd(), + configFile: false as const, + logLevel: "silent" as const, + server: { port: 0 }, + preview: { port: 0 }, + plugins: [ + cloudflare({ + inspectorPort: false, + persistState: false, + remoteBindings: false, + config: { + name: "inline-container-test", + main: path.resolve("index.js"), + compatibility_date: "2026-10-19", + containers: [ + { + class_name: "Probe", + scheduling_policy: "durable_object", + images: { app: { dockerfile: path.resolve("Dockerfile") } }, + }, + ], + durable_objects: { + bindings: [{ name: "PROBE", class_name: "Probe" }], + }, + migrations: [{ tag: "v1", new_sqlite_classes: ["Probe"] }], + }, + }), + ], + }; + } + if (mode === "preview") { + const builder = await createBuilder(config()); + await builder.buildApp(); + } + const server = await (mode === "dev" + ? createServer(config()) + : preview(config())); + onTestFinished(() => server.close()); + expect(prepareContainerImagesForDev).toHaveBeenCalledExactlyOnceWith( + expect.objectContaining({ + configFileName: + mode === "dev" ? "Vite plugin configuration" : "wrangler.json", + }) + ); + }); + test.for(["image-free", "configured", "disabled", "absent"] as const)( "prepares only enabled Containers: %s", async (scenario, { expect, onTestFinished }) => { @@ -487,6 +547,14 @@ export default { plugins: [cloudflare({ inspectorPort: false, persistState: fals expect(prepareContainerImagesForDev).not.toHaveBeenCalled(); } else { expect(prepareContainerImagesForDev).toHaveBeenCalledTimes(1); + expect(prepareContainerImagesForDev).toHaveBeenCalledWith( + expect.objectContaining({ + configFileName: + mode === "dev" ? "wrangler.jsonc" : "wrangler.json", + registryConfigureCommand: + "wrangler containers registries configure", + }) + ); const options = vi.mocked(prepareContainerImagesForDev).mock .calls[0]?.[0].containerOptions; expect(options).toHaveLength(scenario === "configured" ? 1 : 0); diff --git a/packages/vite-plugin-cloudflare/src/__tests__/containers.spec.ts b/packages/vite-plugin-cloudflare/src/__tests__/containers.spec.ts index 2a3a4bc8da..828b4ec5e4 100644 --- a/packages/vite-plugin-cloudflare/src/__tests__/containers.spec.ts +++ b/packages/vite-plugin-cloudflare/src/__tests__/containers.spec.ts @@ -174,6 +174,7 @@ test("prepares registry images with their owning Worker configuration", async ({ dockerPath: "docker", containerOptionsByWorker, logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, + cliDisplayName: "Wrangler", }); expect(preparations).toEqual([ @@ -201,6 +202,7 @@ test("prepares the sidecar for a configured Worker without images or registry cr dockerPath: "test-docker", containerOptionsByWorker: new Map([[{}, []]]), logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, + cliDisplayName: "Wrangler", }); expect(prepareContainerImagesForDev).toHaveBeenCalledExactlyOnceWith( expect.objectContaining({ @@ -334,3 +336,71 @@ describe("Container image planning", () => { expect(new Set(runtimeImageTags)).toEqual(imageTags); }); }); + +test.for([ + { + cliDisplayName: "Wrangler", + configPath: "project/wrangler.jsonc", + viteConfigFile: "project/vite.config.mts", + experimentalNewConfig: false, + configFileName: "wrangler.jsonc", + registryConfigureCommand: "wrangler containers registries configure", + }, + { + cliDisplayName: "Wrangler", + configPath: undefined, + viteConfigFile: "project/vite.config.mts", + experimentalNewConfig: false, + configFileName: "vite.config.mts", + registryConfigureCommand: "wrangler containers registries configure", + }, + { + cliDisplayName: "Wrangler", + configPath: undefined, + viteConfigFile: false as const, + experimentalNewConfig: false, + configFileName: "Vite plugin configuration", + registryConfigureCommand: "wrangler containers registries configure", + }, + { + cliDisplayName: "Wrangler", + configPath: undefined, + viteConfigFile: "project/vite.config.ts", + experimentalNewConfig: true, + configFileName: "cloudflare.config.ts", + registryConfigureCommand: "wrangler containers registries configure", + }, + { + cliDisplayName: "cf", + configPath: undefined, + viteConfigFile: false as const, + experimentalNewConfig: true, + configFileName: "cloudflare.config.ts", + registryConfigureCommand: "cf containers registries create", + }, +])( + "passes $configFileName and $cliDisplayName guidance to the Container helper", + async ( + { + cliDisplayName, + configPath, + viteConfigFile, + experimentalNewConfig, + configFileName, + registryConfigureCommand, + }, + { expect } + ) => { + await prepareContainerImagesForVite({ + dockerPath: "test-docker", + containerOptionsByWorker: new Map([[{ configPath }, []]]), + logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, + cliDisplayName, + viteConfigFile, + experimentalNewConfig, + }); + expect(prepareContainerImagesForDev).toHaveBeenCalledExactlyOnceWith( + expect.objectContaining({ configFileName, registryConfigureCommand }) + ); + } +); diff --git a/packages/vite-plugin-cloudflare/src/containers.ts b/packages/vite-plugin-cloudflare/src/containers.ts index 01b92c25a5..dc656bf741 100644 --- a/packages/vite-plugin-cloudflare/src/containers.ts +++ b/packages/vite-plugin-cloudflare/src/containers.ts @@ -1,3 +1,4 @@ +import path from "node:path"; import { configureOpenAPIForContainerPull, isCloudflareRegistryImage, @@ -15,7 +16,10 @@ import type { } from "@cloudflare/containers-shared"; import type { ComplianceConfig } from "@cloudflare/workers-utils"; -type ContainerWorkerConfig = ComplianceConfig & { account_id?: string }; +type ContainerWorkerConfig = ComplianceConfig & { + account_id?: string; + configPath?: string; +}; /** * Configures the Containers API client used to retrieve image pull credentials. @@ -73,13 +77,16 @@ export function normalizeContainerImageUris( * registry account and compliance settings, including Workers whose images * are selected at start time. * - * @param args - Planned images, Docker executable, and Vite logger. + * @param args - Planned images, Docker executable, Vite logger, CLI identity, and configuration source. * @returns No value. */ export async function prepareContainerImagesForVite(args: { dockerPath: string; containerOptionsByWorker: ContainerOptionsByWorker; logger: ViteLogger; + cliDisplayName: string; + viteConfigFile?: string | false; + experimentalNewConfig?: boolean; }): Promise { for (const [workerConfig, plannedOptions] of args.containerOptionsByWorker) { let containerOptions = plannedOptions; @@ -119,6 +126,18 @@ export async function prepareContainerImagesForVite(args: { onContainerImagePreparationEnd: () => {}, logger: args.logger, complianceConfig: workerConfig, + configFileName: workerConfig.configPath + ? path.basename(workerConfig.configPath) + : args.experimentalNewConfig + ? "cloudflare.config.ts" + : args.viteConfigFile + ? path.basename(args.viteConfigFile) + : "Vite plugin configuration", + // TODO: Let cf provide its own command when the delegate supports that contract. + registryConfigureCommand: + args.cliDisplayName === "cf" + ? "cf containers registries create" + : "wrangler containers registries configure", }); } } diff --git a/packages/vite-plugin-cloudflare/src/plugins/dev.ts b/packages/vite-plugin-cloudflare/src/plugins/dev.ts index 5a8e7ed24d..332d58246c 100644 --- a/packages/vite-plugin-cloudflare/src/plugins/dev.ts +++ b/packages/vite-plugin-cloudflare/src/plugins/dev.ts @@ -221,6 +221,10 @@ export const devPlugin = createPlugin("dev", (ctx) => { dockerPath: getDockerPath(), containerOptionsByWorker, logger: viteDevServer.config.logger, + cliDisplayName: ctx.resolvedPluginConfig.cliDisplayName, + viteConfigFile: viteDevServer.config.configFile, + experimentalNewConfig: + ctx.resolvedPluginConfig.experimental.newConfig !== undefined, }); containerCleanup.track( diff --git a/packages/vite-plugin-cloudflare/src/plugins/preview.ts b/packages/vite-plugin-cloudflare/src/plugins/preview.ts index 3065c45979..642ef90d50 100644 --- a/packages/vite-plugin-cloudflare/src/plugins/preview.ts +++ b/packages/vite-plugin-cloudflare/src/plugins/preview.ts @@ -66,6 +66,10 @@ export const previewPlugin = createPlugin("preview", (ctx) => { dockerPath, containerOptionsByWorker, logger: vitePreviewServer.config.logger, + cliDisplayName: ctx.resolvedPluginConfig.cliDisplayName, + viteConfigFile: vitePreviewServer.config.configFile, + experimentalNewConfig: + ctx.resolvedPluginConfig.experimental.newConfig !== undefined, }); containerCleanup.track( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3698711c40..a91fdbf0c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -258,8 +258,8 @@ importers: specifier: 0.2.6 version: 0.2.6(vitest@4.1.0) '@cloudflare/deploy-helpers': - specifier: 0.21.0 - version: 0.21.0(@types/node@22.15.17)(vitest@4.1.0) + specifier: 0.22.0 + version: 0.22.0(@types/node@22.15.17)(vitest@4.1.0) '@cloudflare/workers-auth': specifier: 0.14.0 version: 0.14.0(msw@2.14.6(@types/node@22.15.17)(typescript@5.8.3))(vitest@4.1.0) @@ -332,8 +332,8 @@ importers: specifier: 0.27.0 version: 0.27.0 '@cloudflare/containers-shared': - specifier: 0.22.1 - version: 0.22.1(vitest@4.1.0) + specifier: 0.23.0 + version: 0.23.0(vitest@4.1.0) '@cloudflare/mock-npm-registry': specifier: workspace:* version: link:../mock-npm-registry @@ -2018,12 +2018,12 @@ packages: '@cloudflare/config@0.27.0': resolution: {integrity: sha512-wNnL0z4lj5Gm2fze5qXTOvRPbUOi4jlMmwPk7ArCUakI5oVxgjfl3Rhnfd/Mmzo3g86x/ofH75yPIhsa5msqdQ==} - '@cloudflare/containers-shared@0.22.1': - resolution: {integrity: sha512-zwvrXDNcLFfxcSRwjDkTH0eR5Moxk50oLsgBlgGblIHZXWblMk6N4xMx3xWD45/GcO8rWJ99aoZGIT/qWTnZZA==} + '@cloudflare/containers-shared@0.23.0': + resolution: {integrity: sha512-Gkoizqq4G0NzOjPhM8VHEz4xV43doXzoeMc5R7c4m4b611MavjBgCUakynnuoH+d3QfsE2f3iN0hPaVaTuDnnQ==} engines: {node: '>=22.0.0'} - '@cloudflare/deploy-helpers@0.21.0': - resolution: {integrity: sha512-QRBBYnB4kJedvMOdkJgrvSo8KT1wdey+qjQ6G6YouL7lgbYxAX602tpYbn7ayTaCOLVo/f4DKzw4sI05PWIURQ==} + '@cloudflare/deploy-helpers@0.22.0': + resolution: {integrity: sha512-MEcKDbv8diNjZV9S4OIfho6nRKSHcqq677WMiXJQsN9I8HrCV9DYD9LVlPawOTLXUmxlmL85xWBrSW6i9MkWJw==} '@cloudflare/kv-asset-handler@0.1.3': resolution: {integrity: sha512-FNcunDuTmEfQTLRLtA6zz+buIXUHj1soPvSWzzQFBC+n2lsy+CGf/NIrR3SEPCmsVNQj70/Jx2lViCpq+09YpQ==} @@ -6335,10 +6335,6 @@ packages: resolution: {integrity: sha512-Cjrs+vHHQ7krLO4HYELYpYBJBuMhcnNZD/Z5z/UcQSnFqWSQY3DhtTCFalXgI1/yO1iK+YrRKK8EYkumrEopxg==} engines: {node: '>=22.0.0'} - miniflare@5.20261012.1-beta: - resolution: {integrity: sha512-8Canhy6g9+vAhqMc0o80YphqYIwpiA3aAp8aX+uDXU7J/qbUeoZzcII9c2V1P4uT/eHD4SB93qet1o1bRY3BuQ==} - engines: {node: '>=22.0.0'} - miniflare@5.20261019.0-beta: resolution: {integrity: sha512-NmE6Yz7MSdjrfr3lMaMAVO55rIm3+6YXllOkJOMy6pPQITDuDeo0PYRedp7pju2olNWmrAlf1i1LIWYfGQvvuw==} engines: {node: '>=22.0.0'} @@ -8798,7 +8794,7 @@ snapshots: dependencies: zod: 4.6.5 - '@cloudflare/containers-shared@0.22.1(vitest@4.1.0)': + '@cloudflare/containers-shared@0.23.0(vitest@4.1.0)': dependencies: '@cloudflare/build-output-utils': 0.9.3 '@cloudflare/cli-shared-helpers': 0.2.6(vitest@4.1.0) @@ -8807,18 +8803,18 @@ snapshots: transitivePeerDependencies: - vitest - '@cloudflare/deploy-helpers@0.21.0(@types/node@22.15.17)(vitest@4.1.0)': + '@cloudflare/deploy-helpers@0.22.0(@types/node@22.15.17)(vitest@4.1.0)': dependencies: '@cloudflare/cli-shared-helpers': 0.2.6(vitest@4.1.0) '@cloudflare/config': 0.27.0 - '@cloudflare/containers-shared': 0.22.1(vitest@4.1.0) + '@cloudflare/containers-shared': 0.23.0(vitest@4.1.0) '@cloudflare/workers-utils': 0.47.2(vitest@4.1.0) blake3-wasm: 2.1.5 chalk: 5.3.0 command-exists: 1.2.9 devtools-protocol: 0.0.1182435 dotenv: 16.3.1 - miniflare: 5.20261012.1-beta(@types/node@22.15.17) + miniflare: 5.20261019.0-beta(@types/node@22.15.17) p-queue: 9.0.0 pretty-bytes: 6.1.1 undici: 7.29.1 @@ -12688,19 +12684,6 @@ snapshots: - bufferutil - utf-8-validate - miniflare@5.20261012.1-beta(@types/node@22.15.17): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - sharp: 0.35.5(@types/node@22.15.17) - undici: 7.29.1 - workerd: 1.20261012.1 - ws: 8.21.0 - youch: 4.1.0-beta.10(patch_hash=3e73fc48581841f22ea1d2cf5a3560bde280fdba04940253073fb121a70a9269) - transitivePeerDependencies: - - '@types/node' - - bufferutil - - utf-8-validate - miniflare@5.20261019.0-beta(@types/node@22.15.17): dependencies: '@cspotcode/source-map-support': 0.8.1