114 lines
3.3 KiB
JavaScript

import { spawn, spawnSync } from 'node:child_process';
import { createServer } from 'node:http';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(__dirname, '..', '..');
const demoDir = __dirname;
const matrixCli = resolve(repoRoot, 'packages', 'cli', 'dist', 'bin', 'matrix.js');
function run(command, args, options = {}) {
const result = spawnSync(command, args, {
cwd: options.cwd ?? repoRoot,
stdio: options.stdio ?? 'inherit',
env: process.env,
encoding: 'utf8',
});
if (result.error) throw result.error;
if (result.status !== 0) {
throw new Error(`${command} ${args.join(' ')} failed with exit ${result.status}`);
}
return result;
}
async function freePort() {
return await new Promise((resolvePort, reject) => {
const server = createServer();
server.on('error', reject);
server.listen(0, '127.0.0.1', () => {
const address = server.address();
if (!address || typeof address === 'string') {
server.close(() => reject(new Error('Could not allocate demo port')));
return;
}
const { port } = address;
server.close(() => resolvePort(port));
});
});
}
async function waitForHealth(port, child) {
const deadline = Date.now() + 10_000;
let lastError;
while (Date.now() < deadline) {
if (child.exitCode !== null) throw new Error(`matrix run exited early with ${child.exitCode}`);
try {
const response = await fetch(`http://127.0.0.1:${port}/healthz`);
if (response.ok) return;
lastError = new Error(`healthz returned ${response.status}`);
} catch (error) {
lastError = error;
}
await new Promise((resolveDelay) => setTimeout(resolveDelay, 100));
}
throw lastError ?? new Error('Timed out waiting for matrix run healthz');
}
function stop(child) {
return new Promise((resolveStop) => {
if (child.exitCode !== null) {
resolveStop();
return;
}
child.once('exit', () => resolveStop());
child.kill('SIGTERM');
setTimeout(() => {
if (child.exitCode === null) child.kill('SIGKILL');
}, 3000).unref();
});
}
const port = await freePort();
let runner;
try {
run('pnpm', ['run', 'build']);
run('pnpm', ['--filter', 'matrix-sdk-example-standalone-greeter', 'run', 'build']);
runner = spawn(process.execPath, [
matrixCli, 'run', demoDir,
'--dir', demoDir,
'--port', String(port),
], {
cwd: repoRoot,
stdio: ['ignore', 'pipe', 'pipe'],
env: process.env,
});
runner.stderr.on('data', (chunk) => process.stderr.write(chunk));
await waitForHealth(port, runner);
const invoked = run(process.execPath, [
matrixCli, 'invoke',
'demo.greeter',
'echo',
'{"message":"hello-from-standalone-demo"}',
'--dir', demoDir,
'--port', String(port),
], { cwd: repoRoot, stdio: 'pipe' });
const body = JSON.parse(invoked.stdout.trim());
if (body?.ok !== true || body?.result?.message !== 'hello-from-standalone-demo') {
throw new Error(`Unexpected invoke result: ${invoked.stdout}`);
}
console.log(JSON.stringify({
ok: true,
demo: 'standalone-greeter',
mount: 'demo.greeter',
port,
invoke: body,
}, null, 2));
} finally {
if (runner) await stop(runner);
}