Files
XZBT/tools/dev-server.mjs
Labyricorn e02d49a739 feat(phase0): complete GC1 feasibility and GC2 shared format contracts
- Record user evidence for directory fallback to complete GC1 (10/10 checks passed)

- Expand Format Specification 0.1 to Revision 0.2 with normative shared contracts

- Author JSON Schema Draft-07 at schema/xzbt-0.1.schema.json

- Implement zero-dependency semantic validator at tools/validate-exhibit.mjs

- Create 12-case conformance fixture suite and automated test runner (12/12 passing)

- Update implementation status, verification gates, and gap closure decisions

- Add devlog entry covering stall recovery and Phase 0 current state
2026-09-05 11:38:48 -07:00

90 lines
2.8 KiB
JavaScript

/**
* Dependency-free local development server for XZBT.
*
* It deliberately binds only to loopback. It is not part of the distributed
* XZBT artifact and cannot establish direct-file Phase 0 feasibility results.
*/
import { createReadStream, statSync } from 'node:fs';
import { createServer } from 'node:http';
import { extname, normalize, resolve, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
const root = resolve(fileURLToPath(new URL('..', import.meta.url)));
const host = '127.0.0.1';
const loopbackAddresses = ['127.0.0.1', '::1'];
const port = Number.parseInt(process.env.XZBT_DEV_PORT ?? '5173', 10);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error('XZBT_DEV_PORT must be an integer from 1 through 65535.');
}
const mimeTypes = new Map([
['.css', 'text/css; charset=utf-8'],
['.html', 'text/html; charset=utf-8'],
['.js', 'text/javascript; charset=utf-8'],
['.json', 'application/json; charset=utf-8'],
['.mjs', 'text/javascript; charset=utf-8'],
['.xzbt', 'application/json; charset=utf-8'],
]);
function requestedPath(requestUrl) {
const pathname = new URL(requestUrl, `http://${host}:${port}`).pathname;
const decodedPath = decodeURIComponent(pathname);
if (decodedPath.includes('\0')) return null;
if (decodedPath.split('/').some((segment) => segment.startsWith('.'))) return null;
const relativePath = decodedPath === '/'
? 'prototypes/phase0/XZBT-phase0-probe.html'
: decodedPath.replace(/^\/+/, '');
const candidate = resolve(root, normalize(relativePath));
return candidate === root || candidate.startsWith(`${root}${sep}`) ? candidate : null;
}
function handleRequest(request, response) {
if (!['GET', 'HEAD'].includes(request.method ?? '')) {
response.writeHead(405, { Allow: 'GET, HEAD' }).end();
return;
}
let path;
try {
path = requestedPath(request.url ?? '/');
} catch {
response.writeHead(400).end('Malformed request path.');
return;
}
if (!path) {
response.writeHead(403).end('Path is outside the XZBT workspace.');
return;
}
let stats;
try {
stats = statSync(path);
} catch {
response.writeHead(404).end('Not found.');
return;
}
if (!stats.isFile()) {
response.writeHead(404).end('Not found.');
return;
}
response.writeHead(200, {
'Content-Length': stats.size,
'Content-Type': mimeTypes.get(extname(path).toLowerCase()) ?? 'application/octet-stream',
'Cache-Control': 'no-store',
'X-Content-Type-Options': 'nosniff',
});
if (request.method === 'HEAD') response.end();
else createReadStream(path).pipe(response);
}
for (const address of loopbackAddresses) {
const server = createServer(handleRequest);
server.listen(port, address, () => {
console.log(`XZBT development server: http://${address}:${port}/`);
});
}
console.log(`Serving workspace root: ${root}`);