mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
0c7ff57710
On Cloudflare Workers the ASSETS binding serves any uploaded file whose path matches the request BEFORE the Worker runs. Cloudflare builds enable `build.manifest` (and `ssrManifest` for Pages Router) so the worker entry can compute lazy chunks, which writes Vite's manifests to `dist/client/.vite/`. Because nothing excluded that directory from the asset bundle, `/.vite/manifest.json` and `/.vite/ssr-manifest.json` were publicly fetchable, leaking the full source-file -> chunk mapping (including the paths of routes that are never linked from the UI). The Node production server already blocks `/.vite/` explicitly (server/static-file-cache.ts); this adds the Cloudflare-side equivalent by writing a `.assetsignore` into `dist/client` during the cloudflare-build closeBundle hook (right after the existing `_headers` generation, after the manifests have been read). wrangler matches `.assetsignore` with gitignore semantics, so the bare `.vite` entry excludes the directory and its contents from upload, and the file is never served. The merge logic preserves any user-authored `.assetsignore` and only appends missing patterns, so it is idempotent across rebuilds. The writer lives in a small typed helper (build/assets-ignore.ts) with focused unit tests; a real Cloudflare build test asserts the wiring emits `.assetsignore` with `.vite` while the manifest still exists on disk.
107 lines
3.4 KiB
TypeScript
107 lines
3.4 KiB
TypeScript
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it } from "vite-plus/test";
|
|
import {
|
|
DEFAULT_VINEXT_ASSET_IGNORE_PATTERNS,
|
|
ensureAssetsIgnore,
|
|
} from "../packages/vinext/src/build/assets-ignore.js";
|
|
|
|
// Regression: the Cloudflare ASSETS binding serves any uploaded file matching
|
|
// the request path BEFORE the Worker runs, so Vite's build/SSR manifests under
|
|
// `dist/client/.vite/` would otherwise be publicly fetchable at
|
|
// `/.vite/manifest.json`. The Node prod server blocks `/.vite/` explicitly;
|
|
// this `.assetsignore` is the Cloudflare-side equivalent.
|
|
describe("ensureAssetsIgnore", () => {
|
|
const tmpDirs: string[] = [];
|
|
|
|
function makeDir(): string {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "vinext-assetsignore-"));
|
|
tmpDirs.push(dir);
|
|
return dir;
|
|
}
|
|
|
|
function readIgnore(dir: string): string {
|
|
return fs.readFileSync(path.join(dir, ".assetsignore"), "utf-8");
|
|
}
|
|
|
|
afterEach(() => {
|
|
for (const dir of tmpDirs.splice(0)) {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("excludes `.vite` by default", () => {
|
|
expect(DEFAULT_VINEXT_ASSET_IGNORE_PATTERNS).toContain(".vite");
|
|
});
|
|
|
|
it("creates a `.assetsignore` containing the default patterns", () => {
|
|
const dir = makeDir();
|
|
const changed = ensureAssetsIgnore(dir);
|
|
expect(changed).toBe(true);
|
|
|
|
const lines = new Set(
|
|
readIgnore(dir)
|
|
.split("\n")
|
|
.map((l) => l.trim()),
|
|
);
|
|
for (const pattern of DEFAULT_VINEXT_ASSET_IGNORE_PATTERNS) {
|
|
expect(lines.has(pattern)).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("creates the assets dir if it does not exist", () => {
|
|
const dir = path.join(makeDir(), "nested", "client");
|
|
ensureAssetsIgnore(dir);
|
|
expect(fs.existsSync(path.join(dir, ".assetsignore"))).toBe(true);
|
|
});
|
|
|
|
it("is idempotent — a second call makes no change and does not duplicate entries", () => {
|
|
const dir = makeDir();
|
|
expect(ensureAssetsIgnore(dir)).toBe(true);
|
|
const first = readIgnore(dir);
|
|
|
|
expect(ensureAssetsIgnore(dir)).toBe(false);
|
|
expect(readIgnore(dir)).toBe(first);
|
|
|
|
const occurrences = first.split("\n").filter((l) => l.trim() === ".vite").length;
|
|
expect(occurrences).toBe(1);
|
|
});
|
|
|
|
it("preserves user-authored content and only appends missing patterns", () => {
|
|
const dir = makeDir();
|
|
fs.writeFileSync(path.join(dir, ".assetsignore"), "_worker.js\nsecret-stuff/\n");
|
|
|
|
const changed = ensureAssetsIgnore(dir);
|
|
expect(changed).toBe(true);
|
|
|
|
const content = readIgnore(dir);
|
|
// User entries are kept verbatim...
|
|
expect(content).toContain("_worker.js");
|
|
expect(content).toContain("secret-stuff/");
|
|
// ...and the security pattern is appended.
|
|
expect(content.split("\n").map((l) => l.trim())).toContain(".vite");
|
|
});
|
|
|
|
it("does not re-append a pattern the user already listed", () => {
|
|
const dir = makeDir();
|
|
fs.writeFileSync(path.join(dir, ".assetsignore"), ".vite\n");
|
|
|
|
expect(ensureAssetsIgnore(dir)).toBe(false);
|
|
const occurrences = readIgnore(dir)
|
|
.split("\n")
|
|
.filter((l) => l.trim() === ".vite").length;
|
|
expect(occurrences).toBe(1);
|
|
});
|
|
|
|
it("honors a custom pattern list", () => {
|
|
const dir = makeDir();
|
|
ensureAssetsIgnore(dir, [".vite", "*.map"]);
|
|
const lines = readIgnore(dir)
|
|
.split("\n")
|
|
.map((l) => l.trim());
|
|
expect(lines).toContain(".vite");
|
|
expect(lines).toContain("*.map");
|
|
});
|
|
});
|