152 lines
4.1 KiB
JavaScript
152 lines
4.1 KiB
JavaScript
import { createServer } from 'node:http';
|
|
import { readFile, stat } from 'node:fs/promises';
|
|
import { dirname, extname, join, resolve, sep } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const distDirectory = join(repositoryRoot, 'dist');
|
|
const nestedPath = '/deep/nested/app/';
|
|
const mimeTypes = {
|
|
'.css': 'text/css',
|
|
'.html': 'text/html',
|
|
'.ico': 'image/x-icon',
|
|
'.js': 'text/javascript',
|
|
'.json': 'application/json',
|
|
'.png': 'image/png',
|
|
'.svg': 'image/svg+xml',
|
|
};
|
|
|
|
const server = createServer(async (request, response) => {
|
|
try {
|
|
const requestUrl = new URL(request.url ?? '/', 'http://localhost');
|
|
|
|
if (requestUrl.pathname === '/toolbox.catalog.json') {
|
|
response.setHeader('content-type', 'application/json');
|
|
response.end(
|
|
JSON.stringify({
|
|
schemaVersion: 1,
|
|
id: 'nested-smoke-test',
|
|
name: 'Nested smoke test',
|
|
home: './',
|
|
theme: {
|
|
mode: 'system',
|
|
brand: 'Nested smoke test',
|
|
},
|
|
apps: [
|
|
{
|
|
manifest: `${nestedPath}toolbox-app.json`,
|
|
enabled: true,
|
|
},
|
|
],
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!requestUrl.pathname.startsWith(nestedPath)) {
|
|
response.writeHead(404).end();
|
|
return;
|
|
}
|
|
|
|
const requestedPath = decodeURIComponent(
|
|
requestUrl.pathname.slice(nestedPath.length)
|
|
);
|
|
const filePath = resolve(
|
|
distDirectory,
|
|
requestedPath === '' ? 'index.html' : requestedPath
|
|
);
|
|
|
|
if (
|
|
filePath !== distDirectory &&
|
|
!filePath.startsWith(`${distDirectory}${sep}`)
|
|
) {
|
|
response.writeHead(403).end();
|
|
return;
|
|
}
|
|
|
|
if (!(await stat(filePath)).isFile()) {
|
|
response.writeHead(404).end();
|
|
return;
|
|
}
|
|
|
|
response.setHeader(
|
|
'content-type',
|
|
mimeTypes[extname(filePath)] ?? 'application/octet-stream'
|
|
);
|
|
response.end(await readFile(filePath));
|
|
} catch {
|
|
response.writeHead(404).end();
|
|
}
|
|
});
|
|
|
|
await new Promise((resolveListening) => {
|
|
server.listen(0, '127.0.0.1', resolveListening);
|
|
});
|
|
|
|
try {
|
|
const address = server.address();
|
|
if (!address || typeof address === 'string') {
|
|
throw new Error('Static smoke server did not expose a TCP port.');
|
|
}
|
|
|
|
const appUrl = `http://127.0.0.1:${address.port}${nestedPath}`;
|
|
const standaloneHtml = await fetchText(appUrl);
|
|
const toolboxHtml = await fetchText(
|
|
`${appUrl}?toolbox=${encodeURIComponent('/toolbox.catalog.json')}`
|
|
);
|
|
|
|
if (toolboxHtml !== standaloneHtml) {
|
|
throw new Error('Toolbox mode did not serve the same static app artifact.');
|
|
}
|
|
|
|
const assetReferences = Array.from(
|
|
standaloneHtml.matchAll(/(?:href|src)="([^"]+)"/g),
|
|
(match) => match[1]
|
|
).filter(
|
|
(reference) =>
|
|
!reference.startsWith('#') &&
|
|
!reference.startsWith('data:') &&
|
|
!reference.startsWith('http:') &&
|
|
!reference.startsWith('https:')
|
|
);
|
|
|
|
for (const reference of assetReferences) {
|
|
const resolved = new URL(reference, appUrl);
|
|
if (!resolved.pathname.startsWith(nestedPath)) {
|
|
throw new Error(
|
|
`Built asset escaped the nested app path: ${reference} -> ${resolved.pathname}`
|
|
);
|
|
}
|
|
await fetchOk(resolved);
|
|
}
|
|
|
|
const manifestUrl = new URL('toolbox-app.json', appUrl);
|
|
const manifest = JSON.parse(await fetchText(manifestUrl));
|
|
await fetchOk(new URL(manifest.entry, appUrl));
|
|
await fetchOk(new URL(manifest.icon, manifestUrl));
|
|
await fetchOk(new URL('vendor/saxon/SaxonJS2.js', appUrl));
|
|
await fetchOk(`http://127.0.0.1:${address.port}/toolbox.catalog.json`);
|
|
|
|
console.log(
|
|
`Static smoke test passed at ${nestedPath} in standalone and toolbox modes.`
|
|
);
|
|
} finally {
|
|
await new Promise((resolveClosed, reject) => {
|
|
server.close((error) => (error ? reject(error) : resolveClosed()));
|
|
});
|
|
}
|
|
|
|
async function fetchOk(url) {
|
|
const response = await fetch(url);
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`Expected ${url} to be available, received HTTP ${response.status}.`
|
|
);
|
|
}
|
|
return response;
|
|
}
|
|
|
|
async function fetchText(url) {
|
|
return (await fetchOk(url)).text();
|
|
}
|