Files
cloudflare__vinext/tests/cdn-adapter-build.test.ts
James Anderson 9265f300cc feat(cloudflare): isolate cached response entrypoint (#3152)
* feat(cloudflare): isolate cached response entrypoint

* test(cloudflare): reuse generic stage facade coverage

* test(cloudflare): preserve composite CDN output config

* fix(cloudflare): track final shared response provenance

* fix(cloudflare): validate routed response artifacts

* test(build): follow response-stage sidecar closure

* test(build): validate response-stage manifest closure

* style(test): format stage closure assertion

* test(cache): assert uncached gateway policy for browser fetch

* fix(cloudflare): partition framework cache variants

* feat(cloudflare): expose entrypoint cache status

* fix(cloudflare): protect shared response provenance

* test(cloudflare): align local cache status mirrors

* test(cloudflare): align entrypoint gateway policy
2026-09-07 21:48:28 +01:00

212 lines
8.2 KiB
TypeScript

import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";
import { createBuilder } from "vite";
import { afterAll, beforeAll, describe, expect, it } from "vite-plus/test";
import { cdnAdapter } from "../packages/cloudflare/src/cache/cdn-adapter.js";
import vinext from "../packages/vinext/src/index.js";
const CLOUDFLARE_NODE_MODULES = path.resolve(
import.meta.dirname,
"fixtures/cf-app-basic/node_modules",
);
const CLOUDFLARE_PLUGIN_PATH = path.join(
CLOUDFLARE_NODE_MODULES,
"@cloudflare/vite-plugin/dist/index.mjs",
);
describe("Cloudflare CDN adapter build output", () => {
let root: string;
beforeAll(async () => {
root = await fs.mkdtemp(path.join(os.tmpdir(), "vinext-cdn-adapter-build-"));
await fs.symlink(CLOUDFLARE_NODE_MODULES, path.join(root, "node_modules"), "dir");
await fs.mkdir(path.join(root, "app"), { recursive: true });
await fs.mkdir(path.join(root, "pages"), { recursive: true });
await fs.writeFile(
path.join(root, "package.json"),
JSON.stringify({ name: "cdn-adapter-build", type: "module" }),
);
await fs.writeFile(
path.join(root, "app/layout.tsx"),
"export default function Layout({ children }) { return <html><body>{children}</body></html>; }\n",
);
await fs.writeFile(
path.join(root, "app/page.tsx"),
"export default function Page() { return <main>home</main>; }\n",
);
await fs.writeFile(
path.join(root, "pages/legacy.tsx"),
"export default function LegacyPage() { return <main>legacy</main>; }\n",
);
await fs.writeFile(
path.join(root, "wrangler.jsonc"),
JSON.stringify({
name: "cdn-adapter-build",
compatibility_date: "2026-09-02",
compatibility_flags: ["nodejs_compat"],
main: "vinext/server/fetch-handler",
assets: { not_found_handling: "none", binding: "ASSETS" },
}),
);
const { cloudflare } = (await import(pathToFileURL(CLOUDFLARE_PLUGIN_PATH).href)) as {
cloudflare: (options: {
viteEnvironment: { name: string; childEnvironments: string[] };
}) => import("vite").Plugin;
};
const builder = await createBuilder({
root,
configFile: false,
logLevel: "silent",
plugins: [
vinext({ appDir: root, cache: { cdn: cdnAdapter() } }),
cloudflare({ viteEnvironment: { name: "rsc", childEnvironments: ["ssr"] } }),
],
});
await builder.buildApp();
}, 120_000);
afterAll(async () => {
await fs.rm(root, { recursive: true, force: true });
});
it("makes the emitted Wrangler config directly deployable without changing source config", async () => {
const source = JSON.parse(await fs.readFile(path.join(root, "wrangler.jsonc"), "utf8"));
const generated = JSON.parse(
await fs.readFile(path.join(root, "dist/server/wrangler.json"), "utf8"),
);
expect(source.version_metadata).toBeUndefined();
expect(generated.version_metadata).toEqual({ binding: "CF_VERSION_METADATA" });
});
it("emits the pregenerated-paths sidecar only in the response-stage closure", async () => {
const sidecarName = "__vinext_pregenerated_concrete_paths.js";
const serverDir = path.join(root, "dist/server");
type ManifestChunk = { file: string; imports?: string[]; src?: string };
const manifest = JSON.parse(
await fs.readFile(path.join(serverDir, ".vite/manifest.json"), "utf8"),
) as Record<string, ManifestChunk>;
const readStaticClosure = async (entry: ManifestChunk): Promise<string[]> => {
const pending = [entry];
const seen = new Set<string>();
const sources: string[] = [];
while (pending.length > 0) {
const chunk = pending.pop()!;
if (seen.has(chunk.file)) continue;
seen.add(chunk.file);
sources.push(await fs.readFile(path.join(serverDir, chunk.file), "utf8"));
for (const imported of chunk.imports ?? []) {
const importedChunk = manifest[imported];
if (!importedChunk) throw new Error(`missing manifest chunk ${imported}`);
pending.push(importedChunk);
}
}
return sources;
};
const workerEntry = Object.values(manifest).find((chunk) => chunk.file === "index.js");
const responseStageEntry = Object.values(manifest).find((chunk) =>
chunk.src?.endsWith("virtual:vinext-response-stage"),
);
expect(workerEntry).toBeDefined();
expect(responseStageEntry).toBeDefined();
const sidecarImport = /import\s*["']\.\/__vinext_pregenerated_concrete_paths\.js["']/;
expect(
(await readStaticClosure(workerEntry!)).some((source) => sidecarImport.test(source)),
).toBe(false);
expect(
(await readStaticClosure(responseStageEntry!)).some((source) => sidecarImport.test(source)),
).toBe(true);
expect(await fs.readFile(path.join(root, "dist/server", sidecarName), "utf8")).toBe(
"delete globalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS;\n",
);
});
it("is the effective config selected by Wrangler's deploy redirect", async () => {
const wranglerPath = createRequire(path.join(root, "package.json")).resolve("wrangler");
const wrangler = (await import(pathToFileURL(wranglerPath).href)) as {
unstable_readConfig(
args: Record<string, never>,
options: {
hideWarnings: true;
preserveOriginalMain: true;
useRedirectIfAvailable: true;
},
): { configPath?: string; version_metadata?: { binding: string } };
};
const previousCwd = process.cwd();
try {
process.chdir(root);
const config = wrangler.unstable_readConfig(
{},
{
hideWarnings: true,
preserveOriginalMain: true,
useRedirectIfAvailable: true,
},
);
expect(await fs.realpath(path.resolve(root, config.configPath ?? ""))).toBe(
await fs.realpath(path.join(root, "dist/server/wrangler.json")),
);
expect(config.version_metadata).toEqual({ binding: "CF_VERSION_METADATA" });
} finally {
process.chdir(previousCwd);
}
});
it("adds the binding to a Pages Router primary output", async () => {
const pagesRoot = await fs.mkdtemp(path.join(os.tmpdir(), "vinext-cdn-adapter-pages-"));
try {
await fs.symlink(CLOUDFLARE_NODE_MODULES, path.join(pagesRoot, "node_modules"), "dir");
await fs.mkdir(path.join(pagesRoot, "pages"), { recursive: true });
await fs.writeFile(
path.join(pagesRoot, "package.json"),
JSON.stringify({ name: "cdn-adapter-pages", type: "module" }),
);
await fs.writeFile(
path.join(pagesRoot, "pages/index.tsx"),
"export default function Page() { return <main>home</main>; }\n",
);
await fs.writeFile(
path.join(pagesRoot, "wrangler.jsonc"),
JSON.stringify({
name: "cdn-adapter-pages",
compatibility_date: "2026-09-02",
compatibility_flags: ["nodejs_compat"],
main: "vinext/server/fetch-handler",
assets: { not_found_handling: "none", binding: "ASSETS" },
}),
);
const { cloudflare } = (await import(pathToFileURL(CLOUDFLARE_PLUGIN_PATH).href)) as {
cloudflare: () => import("vite").Plugin;
};
const builder = await createBuilder({
root: pagesRoot,
configFile: false,
logLevel: "silent",
plugins: [vinext({ appDir: pagesRoot, cache: { cdn: cdnAdapter() } }), cloudflare()],
});
await builder.buildApp();
const redirect = JSON.parse(
await fs.readFile(path.join(pagesRoot, ".wrangler/deploy/config.json"), "utf8"),
) as { configPath: string };
const generatedPath = path.resolve(pagesRoot, ".wrangler/deploy", redirect.configPath);
const generated = JSON.parse(await fs.readFile(generatedPath, "utf8"));
expect(generated.version_metadata).toEqual({ binding: "CF_VERSION_METADATA" });
await expect(
fs.readFile(path.join(pagesRoot, "dist/server/__vinext_pregenerated_concrete_paths.js")),
).rejects.toMatchObject({ code: "ENOENT" });
} finally {
await fs.rm(pagesRoot, { recursive: true, force: true });
}
}, 120_000);
});