2026-06-07 23:45:51 +00:00
|
|
|
import { spawn, spawnSync } from 'node:child_process';
|
2026-06-08 04:18:18 +00:00
|
|
|
import { createServer, createConnection } from 'node:net';
|
2026-06-07 23:45:51 +00:00
|
|
|
import { dirname, resolve } from 'node:path';
|
|
|
|
|
import { fileURLToPath } from 'node:url';
|
2026-06-08 07:24:38 +00:00
|
|
|
import { resolveNatsServer } from '../../scripts/lib/nats-server-tool.mjs';
|
2026-06-07 23:45:51 +00:00
|
|
|
|
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
|
|
const repoRoot = resolve(__dirname, '..', '..');
|
|
|
|
|
const demoDir = __dirname;
|
2026-06-08 04:18:18 +00:00
|
|
|
const matrixCli = resolve(repoRoot, 'packages', 'cli', 'dist', 'index.js');
|
2026-06-07 23:45:51 +00:00
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-08 04:18:18 +00:00
|
|
|
async function waitForTcp(port, child) {
|
2026-06-07 23:45:51 +00:00
|
|
|
const deadline = Date.now() + 10_000;
|
|
|
|
|
let lastError;
|
|
|
|
|
while (Date.now() < deadline) {
|
2026-06-08 04:18:18 +00:00
|
|
|
if (child.exitCode !== null) throw new Error(`nats-server exited early with ${child.exitCode}`);
|
2026-06-07 23:45:51 +00:00
|
|
|
try {
|
2026-06-08 04:18:18 +00:00
|
|
|
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;
|
2026-06-07 23:45:51 +00:00
|
|
|
} catch (error) {
|
|
|
|
|
lastError = error;
|
|
|
|
|
}
|
|
|
|
|
await new Promise((resolveDelay) => setTimeout(resolveDelay, 100));
|
|
|
|
|
}
|
2026-06-08 04:18:18 +00:00
|
|
|
throw lastError ?? new Error('Timed out waiting for nats-server');
|
2026-06-07 23:45:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
2026-06-08 04:18:18 +00:00
|
|
|
let natsServer;
|
2026-06-07 23:45:51 +00:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
run('pnpm', ['run', 'build']);
|
2026-06-08 00:21:26 +00:00
|
|
|
run('pnpm', ['--filter', 'matrix-sdk-example-standalone-greeter', 'run', 'build']);
|
2026-06-07 23:45:51 +00:00
|
|
|
|
2026-06-08 07:24:38 +00:00
|
|
|
const nats = resolveNatsServer(repoRoot);
|
|
|
|
|
if (!nats) {
|
|
|
|
|
throw new Error([
|
|
|
|
|
'nats-server is required for pnpm demo:standalone.',
|
|
|
|
|
'Run pnpm setup:nats, install NATS Server on PATH, or set MATRIX_NATS_SERVER=/path/to/nats-server.',
|
|
|
|
|
].join(' '));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
natsServer = spawn(nats.path, ['-p', String(port), '-a', '127.0.0.1'], {
|
2026-06-07 23:45:51 +00:00
|
|
|
cwd: repoRoot,
|
|
|
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
|
|
|
env: process.env,
|
|
|
|
|
});
|
2026-06-08 04:18:18 +00:00
|
|
|
natsServer.stderr.on('data', (chunk) => process.stderr.write(chunk));
|
|
|
|
|
await waitForTcp(port, natsServer);
|
2026-06-07 23:45:51 +00:00
|
|
|
|
|
|
|
|
const invoked = run(process.execPath, [
|
2026-06-08 04:18:18 +00:00
|
|
|
matrixCli,
|
|
|
|
|
'package',
|
|
|
|
|
'run',
|
|
|
|
|
demoDir,
|
|
|
|
|
'--nats-url',
|
|
|
|
|
`nats://127.0.0.1:${port}`,
|
|
|
|
|
'--root',
|
|
|
|
|
'demo',
|
|
|
|
|
'--check-op',
|
2026-06-07 23:45:51 +00:00
|
|
|
'echo',
|
2026-06-08 04:18:18 +00:00
|
|
|
'--check-payload',
|
2026-06-07 23:45:51 +00:00
|
|
|
'{"message":"hello-from-standalone-demo"}',
|
2026-06-08 04:18:18 +00:00
|
|
|
'--json',
|
2026-06-07 23:45:51 +00:00
|
|
|
], { cwd: repoRoot, stdio: 'pipe' });
|
|
|
|
|
const body = JSON.parse(invoked.stdout.trim());
|
2026-06-08 04:18:18 +00:00
|
|
|
if (body?.ok !== true || body?.check?.result?.message !== 'hello-from-standalone-demo') {
|
|
|
|
|
throw new Error(`Unexpected package run result: ${invoked.stdout}`);
|
2026-06-07 23:45:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(JSON.stringify({
|
|
|
|
|
ok: true,
|
|
|
|
|
demo: 'standalone-greeter',
|
2026-06-08 04:18:18 +00:00
|
|
|
mode: body.mode,
|
|
|
|
|
mount: body.mount,
|
|
|
|
|
root: body.root,
|
|
|
|
|
natsUrl: body.natsUrl,
|
2026-06-08 07:24:38 +00:00
|
|
|
natsServer: {
|
|
|
|
|
source: nats.source,
|
|
|
|
|
path: nats.path,
|
|
|
|
|
},
|
2026-06-08 04:18:18 +00:00
|
|
|
authMode: body.authMode,
|
|
|
|
|
check: body.check,
|
2026-06-07 23:45:51 +00:00
|
|
|
}, null, 2));
|
|
|
|
|
} finally {
|
2026-06-08 04:18:18 +00:00
|
|
|
if (natsServer) await stop(natsServer);
|
2026-06-07 23:45:51 +00:00
|
|
|
}
|