import { spawn, spawnSync } from 'node:child_process'; import { createServer, createConnection } from 'node:net'; 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', 'index.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 waitForTcp(port, child) { const deadline = Date.now() + 10_000; let lastError; while (Date.now() < deadline) { if (child.exitCode !== null) throw new Error(`nats-server exited early with ${child.exitCode}`); try { await new Promise((resolveConnect, rejectConnect) => { const socket = createConnection({ host: '127.0.0.1', port }); socket.once('connect', () => { socket.end(); resolveConnect(); }); socket.once('error', rejectConnect); }); return; } catch (error) { lastError = error; } await new Promise((resolveDelay) => setTimeout(resolveDelay, 100)); } throw lastError ?? new Error('Timed out waiting for nats-server'); } 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 natsServer; try { run('pnpm', ['run', 'build']); run('pnpm', ['--filter', 'matrix-sdk-example-standalone-greeter', 'run', 'build']); natsServer = spawn('nats-server', ['-p', String(port), '-a', '127.0.0.1'], { cwd: repoRoot, stdio: ['ignore', 'pipe', 'pipe'], env: process.env, }); natsServer.stderr.on('data', (chunk) => process.stderr.write(chunk)); await waitForTcp(port, natsServer); const invoked = run(process.execPath, [ matrixCli, 'package', 'run', demoDir, '--nats-url', `nats://127.0.0.1:${port}`, '--root', 'demo', '--check-op', 'echo', '--check-payload', '{"message":"hello-from-standalone-demo"}', '--json', ], { cwd: repoRoot, stdio: 'pipe' }); const body = JSON.parse(invoked.stdout.trim()); if (body?.ok !== true || body?.check?.result?.message !== 'hello-from-standalone-demo') { throw new Error(`Unexpected package run result: ${invoked.stdout}`); } console.log(JSON.stringify({ ok: true, demo: 'standalone-greeter', mode: body.mode, mount: body.mount, root: body.root, natsUrl: body.natsUrl, authMode: body.authMode, check: body.check, }, null, 2)); } finally { if (natsServer) await stop(natsServer); }