203 lines
6.2 KiB
JavaScript
203 lines
6.2 KiB
JavaScript
#!/usr/bin/env node
|
|
import { spawn, spawnSync } from 'node:child_process';
|
|
import net from 'node:net';
|
|
import { existsSync, readFileSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import { chromium } from 'playwright';
|
|
|
|
const root = resolve(new URL('..', import.meta.url).pathname);
|
|
|
|
function run(command, args, options = {}) {
|
|
const result = spawnSync(command, args, {
|
|
cwd: options.cwd ?? root,
|
|
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;
|
|
}
|
|
|
|
function delay(ms) {
|
|
return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
|
|
}
|
|
|
|
function freePort() {
|
|
return new Promise((resolvePort, reject) => {
|
|
const server = net.createServer();
|
|
server.on('error', reject);
|
|
server.listen(0, '127.0.0.1', () => {
|
|
const address = server.address();
|
|
const port = typeof address === 'object' && address ? address.port : 0;
|
|
server.close(() => resolvePort(port));
|
|
});
|
|
});
|
|
}
|
|
|
|
async function waitForHttp(url, timeoutMs) {
|
|
const deadline = Date.now() + timeoutMs;
|
|
let lastError = null;
|
|
while (Date.now() < deadline) {
|
|
try {
|
|
const response = await fetch(url);
|
|
if (response.ok) return;
|
|
lastError = new Error(`${url} returned ${response.status}`);
|
|
} catch (error) {
|
|
lastError = error;
|
|
}
|
|
await delay(100);
|
|
}
|
|
throw lastError ?? new Error(`Timed out waiting for ${url}`);
|
|
}
|
|
|
|
function hasHiveCastEnv() {
|
|
return Boolean(process.env.MATRIX_API_KEY && process.env.MATRIX_CLOUD && process.env.MATRIX_SPACE);
|
|
}
|
|
|
|
function chromiumLaunchOptions() {
|
|
const explicit = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE;
|
|
if (explicit) return { headless: true, executablePath: explicit };
|
|
for (const candidate of ['/usr/bin/google-chrome', '/usr/bin/google-chrome-stable', '/usr/bin/chromium', '/usr/bin/chromium-browser']) {
|
|
if (existsSync(candidate)) return { headless: true, executablePath: candidate };
|
|
}
|
|
return { headless: true };
|
|
}
|
|
|
|
function assertContains(file, text) {
|
|
const body = readFileSync(resolve(root, file), 'utf8');
|
|
if (!body.includes(text)) {
|
|
throw new Error(`${file} does not contain expected text: ${text}`);
|
|
}
|
|
}
|
|
|
|
function parseLastJsonObject(stdout) {
|
|
const text = stdout.trim();
|
|
for (let i = 0; i < text.length; i += 1) {
|
|
if (text[i] !== '{') continue;
|
|
try {
|
|
return JSON.parse(text.slice(i));
|
|
} catch {
|
|
// Keep scanning later object starts.
|
|
}
|
|
}
|
|
throw new Error(`No JSON proof object found in stdout:\n${stdout}`);
|
|
}
|
|
|
|
async function proveWebLive() {
|
|
const port = await freePort();
|
|
const origin = `http://127.0.0.1:${port}`;
|
|
const vite = spawn('pnpm', [
|
|
'--filter',
|
|
'matrix-sdk-demo-fresh-web-page',
|
|
'exec',
|
|
'vite',
|
|
'--host',
|
|
'127.0.0.1',
|
|
'--port',
|
|
String(port),
|
|
'--strictPort',
|
|
], {
|
|
cwd: root,
|
|
env: process.env,
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
});
|
|
let stdout = '';
|
|
let stderr = '';
|
|
vite.stdout.on('data', (chunk) => {
|
|
stdout += String(chunk);
|
|
});
|
|
vite.stderr.on('data', (chunk) => {
|
|
stderr += String(chunk);
|
|
});
|
|
|
|
try {
|
|
await waitForHttp(origin, 15_000);
|
|
|
|
const browser = await chromium.launch(chromiumLaunchOptions());
|
|
const page = await browser.newPage();
|
|
const consoleErrors = [];
|
|
const failedRequests = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') consoleErrors.push(msg.text());
|
|
});
|
|
page.on('requestfailed', (request) => {
|
|
failedRequests.push({
|
|
url: request.url(),
|
|
failure: request.failure()?.errorText ?? 'unknown',
|
|
});
|
|
});
|
|
|
|
try {
|
|
await page.goto(origin, { waitUntil: 'domcontentloaded', timeout: 15_000 });
|
|
await page.waitForFunction(() => {
|
|
const text = document.querySelector('pre')?.textContent ?? '';
|
|
return text.includes('"ok": true') && text.includes('fresh-web-page');
|
|
}, { timeout: 20_000 });
|
|
const body = await page.locator('pre').innerText();
|
|
const proof = JSON.parse(body);
|
|
if (proof.ok !== true || proof.demo !== 'fresh-web-page') {
|
|
throw new Error(`Unexpected fresh web page proof: ${body}`);
|
|
}
|
|
if (consoleErrors.length > 0 || failedRequests.length > 0) {
|
|
throw new Error(`Browser proof had errors: ${JSON.stringify({ consoleErrors, failedRequests })}`);
|
|
}
|
|
return {
|
|
...proof,
|
|
origin,
|
|
consoleErrors,
|
|
failedRequests,
|
|
};
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
} catch (error) {
|
|
throw new Error([
|
|
error instanceof Error ? error.message : String(error),
|
|
stdout ? `vite stdout:\n${stdout}` : '',
|
|
stderr ? `vite stderr:\n${stderr}` : '',
|
|
].filter(Boolean).join('\n'));
|
|
} finally {
|
|
vite.kill('SIGTERM');
|
|
await delay(250);
|
|
if (!vite.killed) vite.kill('SIGKILL');
|
|
}
|
|
}
|
|
|
|
run('pnpm', ['--filter', '@open-matrix/core', 'build']);
|
|
run('pnpm', ['--filter', '@open-matrix/sdk', 'build']);
|
|
run('pnpm', ['--filter', 'matrix-sdk-demo-fresh-server-actor', 'build']);
|
|
run('pnpm', ['--filter', 'matrix-sdk-demo-fresh-web-page', 'build']);
|
|
|
|
assertContains('demos/fresh-server-actor/src/server.ts', "from '@open-matrix/sdk'");
|
|
assertContains('demos/fresh-server-actor/src/server.ts', 'exchangeHiveCastActorCredential');
|
|
assertContains('demos/fresh-web-page/src/main.ts', "from '@open-matrix/sdk'");
|
|
assertContains('demos/fresh-web-page/vite.config.ts', 'exchangeHiveCastActorCredential');
|
|
assertContains('demos/fresh-web-page/README.md', 'served by Vite, not by Matrix Web');
|
|
|
|
const live = hasHiveCastEnv();
|
|
let serverLive = null;
|
|
let webLive = null;
|
|
if (live) {
|
|
const result = run('pnpm', ['--filter', 'matrix-sdk-demo-fresh-server-actor', 'check'], { stdio: 'pipe' });
|
|
serverLive = parseLastJsonObject(result.stdout);
|
|
if (serverLive.ok !== true || serverLive.demo !== 'fresh-server-actor') {
|
|
throw new Error(`Unexpected fresh server actor proof: ${result.stdout}`);
|
|
}
|
|
webLive = await proveWebLive();
|
|
}
|
|
|
|
console.log(JSON.stringify({
|
|
ok: true,
|
|
proof: 'fresh-samples',
|
|
built: [
|
|
'matrix-sdk-demo-fresh-server-actor',
|
|
'matrix-sdk-demo-fresh-web-page',
|
|
],
|
|
hivecastEnvPresent: live,
|
|
serverLive,
|
|
webLive,
|
|
}, null, 2));
|