#!/usr/bin/env node import { mkdtemp, rm, mkdir, writeFile, readdir, readFile, readlink } from 'node:fs/promises'; import { existsSync } from 'node:fs'; import { tmpdir } from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { spawnSync } from 'node:child_process'; const scriptDir = path.dirname(fileURLToPath(import.meta.url)); const workspaceDir = path.resolve(scriptDir, '..'); const packages = [ '@open-matrix/contracts', '@open-matrix/federation', '@open-matrix/omega-core', '@open-matrix/core', '@open-matrix/browser-host', '@open-matrix/browser-kit', '@open-matrix/sdk', '@open-matrix/cli', ]; function run(command, args, options = {}) { const result = spawnSync(command, args, { cwd: options.cwd ?? workspaceDir, stdio: options.stdio ?? 'inherit', env: { ...process.env, ...options.env }, encoding: 'utf8', }); if (result.status !== 0) { throw new Error(`${command} ${args.join(' ')} failed with exit ${result.status}`); } return result; } async function findTarball(tarballDir, packageName) { const packageDir = path.join(workspaceDir, 'packages', packageName.split('/').at(-1) === 'omega-core' ? 'omega-core' : packageName.split('/').at(-1)); const packageJson = JSON.parse(await readFile(path.join(packageDir, 'package.json'), 'utf8')); const expectedPrefix = packageJson.name.replace('@', '').replace('/', '-'); const entries = await readdir(tarballDir); const tarball = entries.find((entry) => entry.startsWith(expectedPrefix) && entry.endsWith('.tgz')); if (!tarball) { throw new Error(`Packed tarball missing for ${packageName} (${expectedPrefix}-*.tgz)`); } return path.join(tarballDir, tarball); } async function assertNoSymlinkToHarness(dir) { const pending = [dir]; while (pending.length > 0) { const current = pending.pop(); const entries = await readdir(current, { withFileTypes: true }); for (const entry of entries) { const full = path.join(current, entry.name); if (entry.isSymbolicLink()) { const target = await readlink(full); if (target.includes('matrix-work-harness')) { throw new Error(`Consumer node_modules symlink points at harness: ${full}`); } continue; } if (entry.isDirectory()) { if (entry.name === '.pnpm' || entry.name === '.bin') continue; pending.push(full); } } } } async function main() { const proofRoot = await mkdtemp(path.join(tmpdir(), 'matrix-sdk-consumer-proof.')); const tarballDir = path.join(proofRoot, 'tarballs'); const consumerDir = path.join(proofRoot, 'consumer'); let keep = process.env.MATRIX_SDK_KEEP_CONSUMER_PROOF === '1'; try { await mkdir(tarballDir, { recursive: true }); await mkdir(path.join(consumerDir, 'src'), { recursive: true }); run('pnpm', ['run', 'clean']); run('pnpm', ['run', 'build']); for (const packageName of packages) { run('pnpm', ['--filter', packageName, 'pack', '--pack-destination', tarballDir]); } const tarballs = Object.fromEntries( await Promise.all(packages.map(async (packageName) => [packageName, await findTarball(tarballDir, packageName)])) ); const tarballDependencies = Object.fromEntries( Object.entries(tarballs).map(([packageName, tarballPath]) => [packageName, `file:${tarballPath}`]) ); await writeFile(path.join(consumerDir, 'package.json'), JSON.stringify({ name: 'matrix-sdk-external-consumer-proof', version: '0.0.0', private: true, type: 'module', scripts: { build: 'tsc -p tsconfig.json', run: 'node dist/index.js', }, dependencies: tarballDependencies, devDependencies: { '@types/node': '^25.0.10', typescript: '^5.7.0', }, pnpm: { overrides: tarballDependencies, }, }, null, 2)); await writeFile(path.join(consumerDir, 'tsconfig.json'), JSON.stringify({ compilerOptions: { target: 'ES2022', module: 'NodeNext', moduleResolution: 'NodeNext', strict: true, skipLibCheck: true, outDir: 'dist', }, include: ['src/**/*.ts'], }, null, 2)); await writeFile(path.join(consumerDir, 'src', 'browser-actor.ts'), `import { MatrixActorHtmlElement } from '@open-matrix/core'; import type { MatrixAppReadyDetail } from '@open-matrix/browser-host/app-ready'; export class DemoBrowserActor extends MatrixActorHtmlElement { static accepts = { 'ui.ping': {} }; describeReady(detail: MatrixAppReadyDetail): string { return detail.appName; } } `); await writeFile(path.join(consumerDir, 'src', 'index.ts'), `import assert from 'node:assert/strict'; import { InMemoryBroker, InMemoryTransport, MatrixActor, MatrixRuntime, TopicRouter, } from '@open-matrix/core'; import { MatrixActor as SdkMatrixActor } from '@open-matrix/sdk'; class EchoActor extends MatrixActor { static accepts = { echo: {}, }; onEcho(payload: { message?: string }) { return { ok: true, message: payload.message ?? null, source: 'external-sdk-consumer', }; } } async function request(transport: InMemoryTransport, mount: string, op: string, payload: unknown) { const correlationId = 'consumer-proof-1'; const replyTo = '$replies.consumer-proof-1'; const response = new Promise((resolve, reject) => { const timeout = setTimeout(() => { unsubscribe(); reject(new Error('request timed out')); }, 2000); const unsubscribe = transport.subscribe(replyTo, (value) => { clearTimeout(timeout); unsubscribe(); resolve(value); }); }); transport.publish(TopicRouter.inbox(mount), { op, payload, replyTo, correlationId, }); return response; } const broker = new InMemoryBroker(); const transport = new InMemoryTransport(broker, { name: 'external-consumer' }); const runtime = new MatrixRuntime({ transport, logging: false }); try { assert.equal(SdkMatrixActor, MatrixActor); await runtime.create(EchoActor, 'demo.echo'); const response = await request(transport, 'demo.echo', 'echo', { message: 'hello-sdk' }); assert.deepEqual(response, { ok: true, correlationId: 'consumer-proof-1', result: { ok: true, message: 'hello-sdk', source: 'external-sdk-consumer', }, }); console.log(JSON.stringify({ ok: true, mount: 'demo.echo', op: 'echo' })); } finally { await runtime.shutdown(); } `); run('pnpm', ['install', '--ignore-scripts'], { cwd: consumerDir }); run('pnpm', ['exec', 'matrix', '--help'], { cwd: consumerDir, stdio: 'pipe' }); run('pnpm', ['run', 'build'], { cwd: consumerDir }); const runtimeProof = run('pnpm', ['run', 'run'], { cwd: consumerDir, stdio: 'pipe' }); const stdout = runtimeProof.stdout.trim(); const parsed = JSON.parse(stdout.split('\n').at(-1)); if (parsed.ok !== true || parsed.mount !== 'demo.echo' || parsed.op !== 'echo') { throw new Error(`Unexpected runtime proof output: ${stdout}`); } const consumerFiles = [ await readFile(path.join(consumerDir, 'package.json'), 'utf8'), await readFile(path.join(consumerDir, 'tsconfig.json'), 'utf8'), await readFile(path.join(consumerDir, 'src', 'index.ts'), 'utf8'), await readFile(path.join(consumerDir, 'src', 'browser-actor.ts'), 'utf8'), ].join('\n'); if (consumerFiles.includes('matrix-work-harness') || consumerFiles.includes('projects/matrix-3')) { throw new Error('Consumer proof contains a Matrix-3/harness path reference'); } if (existsSync(path.join(consumerDir, 'node_modules'))) { await assertNoSymlinkToHarness(path.join(consumerDir, 'node_modules')); } console.log(JSON.stringify({ ok: true, proof: 'external-consumer', consumerDir, packages, runtime: parsed, matrix3PathReferences: false, }, null, 2)); } catch (error) { keep = true; throw error; } finally { if (!keep) { await rm(proofRoot, { recursive: true, force: true }); } else { console.error(`[prove-external-consumer] kept proof directory: ${proofRoot}`); } } } main().catch((error) => { console.error(error instanceof Error ? error.stack : error); process.exit(1); });