1import * as fs from "node:fs/promises";
2import * as path from "node:path";
3import * as vite from "vite";
4import { expect, onTestFinished, test, vi } from "vitest";
5import cloudflareVitePlugin from "../plugin.ts";
6import * as Assets from "../../core/bindings/assets/Assets.ts";
7
8test.each([
9 ["selective Worker routes", ["/api/*"]],
10 [
11 "explicitly excluded internal routes",
12 ["/api/*", "!/cdn-cgi/alchemy/module-runner/*"],
13 ],
14 ["assets first", false],
15 ["Worker first", true],
16] as const)(
17 "starts the module runner with SPA fallback and %s",
18 async (_, runWorkerFirst) => {
19 onTestFinished(() => {
20 vi.unstubAllEnvs();
21 });
22 // These tests use only the local runtime; no Cloudflare API calls are needed.
23 vi.stubEnv("CLOUDFLARE_API_TOKEN", "local-test-unused");
24 vi.stubEnv("CLOUDFLARE_ACCOUNT_ID", "00000000000000000000000000000000");
25 const tmpRoot = path.resolve(import.meta.dirname, "../.cache/test-roots");
26 await fs.mkdir(tmpRoot, { recursive: true });
27 const root = await fs.mkdtemp(path.join(tmpRoot, "dev-assets-"));
28 onTestFinished(() => fs.rm(root, { recursive: true, force: true }));
29 const entry = path.join(root, "worker.js");
30 await fs.writeFile(
31 path.join(root, "index.html"),
32 "<html><body>SPA shell</body></html>",
33 );
34 await fs.writeFile(
35 entry,
36 `
37 export default {
38 fetch(request, env) {
39 if (new URL(request.url).pathname.startsWith("/api/")) {
40 return new Response("api");
41 }
42 return env.ASSETS.fetch(request);
43 },
44 };
45 `,
46 );
47 const server = await vite.createServer({
48 root,
49 configFile: false,
50 logLevel: "silent",
51 server: { host: "127.0.0.1", port: 0 },
52 plugins: [
53 cloudflareVitePlugin({
54 main: entry,
55 worker: {
56 name: "vite-dev-assets-test",
57 bindings: [Assets.local("ASSETS")],
58 assets: {
59 runWorkerFirst:
60 typeof runWorkerFirst === "boolean"
61 ? runWorkerFirst
62 : [...runWorkerFirst],
63 notFoundHandling: "single-page-application",
64 },
65 },
66 }),
67 ],
68 });
69 onTestFinished(() => server.close());
70 await server.listen();
71 const url = server.resolvedUrls!.local[0];
72 const page = await fetch(new URL("/dashboard", url), {
73 signal: AbortSignal.timeout(5_000),
74 });
75 expect(page.status).toBe(200);
76 expect(await page.text()).toContain("SPA shell");
77 if (runWorkerFirst !== false) {
78 const api = await fetch(new URL("/api/hello", url), {
79 signal: AbortSignal.timeout(5_000),
80 });
81 expect(api.status).toBe(200);
82 expect(await api.text()).toBe("api");
83 }
84 },
85);