Skip to content
Merged
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
25 changes: 20 additions & 5 deletions scripts/product-proof-asset-manifest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -592,16 +592,31 @@ function directHtmlAssets(htmlBytes, artifacts) {

function routeAssetPaths(entries, moduleKey, htmlAssets) {
const manifestKeys = new Set();
const visit = (key, depth = 0) => {
const staticallyVisited = new Set();
const dynamicallyVisited = new Set();
const visit = (key, depth = 0, includeDynamicImports = false) => {
if (depth > PRODUCT_PROOF_VITE_MANIFEST_MAXIMUM_JSON_DEPTH) {
fail('asset_manifest_set_invalid');
}
if (manifestKeys.has(key)) return;
manifestKeys.add(key);
for (const imported of entries.get(key).imports ?? []) visit(imported, depth + 1);
if (includeDynamicImports) {
if (dynamicallyVisited.has(key)) return;
dynamicallyVisited.add(key);
} else {
if (staticallyVisited.has(key) || dynamicallyVisited.has(key)) return;
staticallyVisited.add(key);
}
for (const imported of entries.get(key).imports ?? []) {
visit(imported, depth + 1, includeDynamicImports && imported !== indexEntryKey);
}
if (includeDynamicImports) {
for (const imported of entries.get(key).dynamicImports ?? []) {
visit(imported, depth + 1, imported !== indexEntryKey);
}
}
};
visit(indexEntryKey);
visit(moduleKey);
visit(indexEntryKey, 0, false);
visit(moduleKey, 0, true);
const outputPaths = new Map(htmlAssets);
for (const key of manifestKeys) {
const entry = entries.get(key);
Expand Down
50 changes: 49 additions & 1 deletion scripts/product-proof-asset-manifest.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,26 @@ function syntheticCodeSplitFixture(options = {}) {
...(index === 0 ? { css: ['assets/route.css'] } : {}),
};
}
if (options.includeRouteDynamics === true) {
manifest['src/pages/ChannelsDashboard.tsx'].dynamicImports = [
'src/components/channels/WebsiteVoiceRuntimeStart.tsx',
];
manifest['src/components/channels/WebsiteVoiceRuntimeStart.tsx'] = {
dynamicImports: ['index.html', 'src/components/channels/WebsiteVoiceDevicePanel.tsx'],
file: 'assets/WebsiteVoiceRuntimeStart.js',
imports: ['_runtime.js'],
isDynamicEntry: true,
name: 'WebsiteVoiceRuntimeStart',
src: 'src/components/channels/WebsiteVoiceRuntimeStart.tsx',
};
manifest['src/components/channels/WebsiteVoiceDevicePanel.tsx'] = {
file: 'assets/WebsiteVoiceDevicePanel.js',
imports: ['_runtime.js'],
isDynamicEntry: true,
name: 'WebsiteVoiceDevicePanel',
src: 'src/components/channels/WebsiteVoiceDevicePanel.tsx',
};
}
const extraAuthChunks = options.extraAuthChunks ?? 0;
for (let index = 0; index < extraAuthChunks; index += 1) {
const key = `_auth-extra-${String(index).padStart(3, '0')}.js`;
Expand Down Expand Up @@ -136,6 +156,16 @@ function syntheticCodeSplitFixture(options = {}) {
const name = path.posix.basename(moduleKey, '.tsx');
contents.set(`assets/${name}.js`, Buffer.from(`export default function ${name}(){}`));
}
if (options.includeRouteDynamics === true) {
contents.set(
'assets/WebsiteVoiceRuntimeStart.js',
Buffer.from('export function WebsiteVoiceRuntimeStart(){}'),
);
contents.set(
'assets/WebsiteVoiceDevicePanel.js',
Buffer.from('export function WebsiteVoiceDevicePanel(){}'),
);
}
for (let index = 0; index < extraAuthChunks; index += 1) {
const file = `assets/auth-extra-${String(index).padStart(3, '0')}.js`;
contents.set(file, Buffer.from(`export const extra${index} = true;`));
Expand Down Expand Up @@ -210,7 +240,7 @@ function nestedJson(depth, containerAt) {
}

test('real synthetic Vite code-split fixture emits exact route-specific artifact-bound assets', (t) => {
const { dependencies, parameters } = harness(t);
const { dependencies, parameters } = harness(t, { includeRouteDynamics: true });
const manifest = createProductProofAssetManifest(parameters, dependencies);
assert.deepEqual(
Object.keys(manifest.routes).sort(),
Expand Down Expand Up @@ -241,6 +271,24 @@ test('real synthetic Vite code-split fixture emits exact route-specific artifact
assert.ok(!authPaths.includes('/assets/pdf-vendor.js'));
assert.ok(!authPaths.includes('/assets/Settings.js'));
assert.ok(manifest.routes.settings.some((entry) => entry.path === '/assets/Settings.js'));
assert.ok(
manifest.routes.channels.some((entry) => entry.path === '/assets/WebsiteVoiceRuntimeStart.js'),
);
assert.ok(
manifest.routes.channels.some((entry) => entry.path === '/assets/WebsiteVoiceDevicePanel.js'),
);
assert.ok(
!manifest.routes.settings.some(
(entry) =>
entry.path === '/assets/WebsiteVoiceRuntimeStart.js' ||
entry.path === '/assets/WebsiteVoiceDevicePanel.js',
),
);
assert.ok(
!Object.values(manifest.routes).some((assets) =>
assets.some((entry) => entry.path === '/assets/Dashboard.js'),
),
);
assert.deepEqual(
manifest.routes['auth-native-email-validation'],
manifest.routes['auth-final-state'],
Expand Down
44 changes: 44 additions & 0 deletions scripts/product-proof-real-build-compatibility.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url';
import { buildArtifactManifest } from './cloudflare-pages-preview-release.mjs';
import {
PRODUCT_PROOF_ASSET_ROUTE_LABELS,
PRODUCT_PROOF_ASSET_ROUTE_MODULES,
createProductProofAssetManifest,
} from './product-proof-asset-manifest.mjs';
import { readImmutableReleaseMarker } from './product-proof-deployment-binding.mjs';
Expand Down Expand Up @@ -66,6 +67,25 @@ function manifestClosure(manifest, entryKeys, { includeDynamicImports = true } =
return closure;
}

function routeManifestClosure(manifest, moduleKey) {
const closure = manifestClosure(manifest, ['index.html'], {
includeDynamicImports: false,
});
const visited = new Set();
const visit = (key) => {
if (key === 'index.html' || visited.has(key)) return;
const entry = manifest[key];
assert.ok(entry, key);
visited.add(key);
closure.add(key);
for (const imported of [...(entry.imports ?? []), ...(entry.dynamicImports ?? [])]) {
visit(imported);
}
};
visit(moduleKey);
return closure;
}

function routeOptions(route) {
const authenticated = route.contextLabel !== 'signed-out';
const conversation = route.path.includes(':conversation');
Expand Down Expand Up @@ -212,6 +232,30 @@ test(
Object.keys(assetManifest.routes).sort(),
[...PRODUCT_PROOF_ASSET_ROUTE_LABELS].sort(),
);
for (const [label, moduleKey] of Object.entries(PRODUCT_PROOF_ASSET_ROUTE_MODULES)) {
const expectedEntryKeys = routeManifestClosure(viteManifest, moduleKey);
const routePaths = new Set(assetManifest.routes[label].map((entry) => entry.path));
for (const entryKey of expectedEntryKeys) {
const entry = viteManifest[entryKey];
for (const outputPath of [entry.file, ...(entry.css ?? []), ...(entry.assets ?? [])]) {
assert.equal(routePaths.has(`/${outputPath}`), true, `${label}:${outputPath}`);
}
}
}
const channelsDynamicEntries =
viteManifest[PRODUCT_PROOF_ASSET_ROUTE_MODULES.channels].dynamicImports ?? [];
assert.deepEqual(channelsDynamicEntries, [
'src/components/channels/WebsiteVoiceRuntimeStart.tsx',
]);
const channelsDynamicPath = `/${viteManifest[channelsDynamicEntries[0]].file}`;
assert.equal(
assetManifest.routes.channels.some((entry) => entry.path === channelsDynamicPath),
true,
);
assert.equal(
assetManifest.routes.settings.some((entry) => entry.path === channelsDynamicPath),
false,
);

const factory = createProductProofNetworkPolicyFactory({ assetManifest });
for (const route of PRODUCT_PROOF_NETWORK_ROUTES) {
Expand Down