fix: harden prerender publication boundary

This commit is contained in:
2026-09-02 02:44:23 +03:00
parent 3dd4a818d0
commit c16c4c57da
22 changed files with 1275 additions and 231 deletions

View File

@@ -0,0 +1,92 @@
import { describe, expect, it } from 'vitest';
import { resolveViteAssetClosure } from '../../scripts/build-contract.mjs';
import type { ViteManifest } from '../../scripts/prerender.mjs';
const recursiveManifest = (): ViteManifest => ({
'index.html': {
file: 'assets/entry-a1.js',
src: 'index.html',
isEntry: true,
css: ['assets/entry-a1.css'],
assets: ['assets/entry-logo-a1.webp'],
imports: ['src/shared.ts'],
dynamicImports: ['src/lazy.ts'],
},
'src/shared.ts': {
file: 'assets/shared-b2.js',
src: 'src/shared.ts',
css: ['assets/shared-b2.css'],
imports: ['src/deep.ts'],
},
'src/deep.ts': {
file: 'assets/deep-c3.js',
src: 'src/deep.ts',
},
'src/lazy.ts': {
file: 'assets/lazy-d4.js',
src: 'src/lazy.ts',
css: ['assets/lazy-d4.css'],
},
});
describe('trusted Vite asset closure', () => {
it('resolves the exact recursive executable, preload, style, and Vite asset sets', () => {
expect(resolveViteAssetClosure(recursiveManifest())).toEqual({
entryModule: 'assets/entry-a1.js',
executableModules: [
'assets/deep-c3.js',
'assets/entry-a1.js',
'assets/lazy-d4.js',
'assets/shared-b2.js',
],
modulePreloads: [
'assets/deep-c3.js',
'assets/entry-a1.js',
'assets/shared-b2.js',
],
styles: [
'assets/entry-a1.css',
'assets/lazy-d4.css',
'assets/shared-b2.css',
],
viteAssets: ['assets/entry-logo-a1.webp'],
});
});
it.each([
{
name: 'a missing exact index.html entry',
mutate: (manifest: ViteManifest) => {
manifest['src/entry-client.ts'] = manifest['index.html']!;
delete manifest['index.html'];
},
error: /exact index\.html/i,
},
{
name: 'an unreachable manifest record',
mutate: (manifest: ViteManifest) => {
manifest['src/orphan.ts'] = { file: 'assets/orphan.js', src: 'src/orphan.ts' };
},
error: /unreachable manifest/i,
},
{
name: 'an unsafe emitted path',
mutate: (manifest: ViteManifest) => {
manifest['src/shared.ts']!.file = '../outside.js';
},
error: /unsafe.*path/i,
},
{
name: 'a missing recursive import',
mutate: (manifest: ViteManifest) => {
manifest['src/shared.ts']!.imports = ['src/missing.ts'];
},
error: /missing manifest import/i,
},
])('rejects $name', ({ mutate, error }) => {
const manifest = recursiveManifest();
mutate(manifest);
expect(() => resolveViteAssetClosure(manifest)).toThrow(error);
});
});