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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/v1-container-command-guidance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/vite-plugin": patch
---

Fix Container setup guidance in Vite development and preview

Container registry instructions now show the Wrangler command and the configuration location, including Wrangler files and inline Vite configuration.
2 changes: 1 addition & 1 deletion packages/vite-plugin-cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"ws": "8.22.0"
},
"devDependencies": {
"@cloudflare/containers-shared": "0.22.1",
"@cloudflare/containers-shared": "0.23.0",
"@cloudflare/mock-npm-registry": "workspace:*",
"@cloudflare/remote-bindings": "workspace:*",
"@cloudflare/workers-shared": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,37 @@ describe("Container image planning", () => {
expect(new Set(runtimeImageTags)).toEqual(imageTags);
});
});

test.for([
{
configPath: "project/wrangler.jsonc",
viteConfigFile: "project/vite.config.mts",
configFileName: "wrangler.jsonc",
},
{
configPath: undefined,
viteConfigFile: "project/vite.config.mts",
configFileName: "vite.config.mts",
},
{
configPath: undefined,
viteConfigFile: false as const,
configFileName: "Vite plugin configuration",
},
])(
"passes $configFileName guidance to the Container helper",
async ({ configPath, viteConfigFile, configFileName }, { expect }) => {
await prepareContainerImagesForVite({
dockerPath: "test-docker",
containerOptionsByWorker: new Map([[{ configPath }, []]]),
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
viteConfigFile,
});
expect(prepareContainerImagesForDev).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({
configFileName,
registryConfigureCommand: "wrangler containers registries configure",
})
);
}
);
15 changes: 13 additions & 2 deletions packages/vite-plugin-cloudflare/src/containers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import path from "node:path";
import {
configureOpenAPIForContainerPull,
isCloudflareRegistryImage,
Expand All @@ -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.
Expand Down Expand Up @@ -73,13 +77,14 @@ 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, and configuration source.
* @returns No value.
*/
export async function prepareContainerImagesForVite(args: {
dockerPath: string;
containerOptionsByWorker: ContainerOptionsByWorker;
logger: ViteLogger;
viteConfigFile?: string | false;
}): Promise<void> {
for (const [workerConfig, plannedOptions] of args.containerOptionsByWorker) {
let containerOptions = plannedOptions;
Expand Down Expand Up @@ -119,6 +124,12 @@ export async function prepareContainerImagesForVite(args: {
onContainerImagePreparationEnd: () => {},
logger: args.logger,
complianceConfig: workerConfig,
configFileName: workerConfig.configPath
? path.basename(workerConfig.configPath)
: args.viteConfigFile
? path.basename(args.viteConfigFile)
: "Vite plugin configuration",
registryConfigureCommand: "wrangler containers registries configure",
});
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/vite-plugin-cloudflare/src/plugins/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ export const devPlugin = createPlugin("dev", (ctx) => {
dockerPath: getDockerPath(),
containerOptionsByWorker,
logger: viteDevServer.config.logger,
viteConfigFile: viteDevServer.config.configFile,
});

containerCleanup.track(
Expand Down
1 change: 1 addition & 0 deletions packages/vite-plugin-cloudflare/src/plugins/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const previewPlugin = createPlugin("preview", (ctx) => {
dockerPath,
containerOptionsByWorker,
logger: vitePreviewServer.config.logger,
viteConfigFile: vitePreviewServer.config.configFile,
});

containerCleanup.track(
Expand Down
17 changes: 15 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading