import { describe, it } from 'node:test'; import { strict as assert } from 'node:assert'; import * as fs from 'node:fs'; import * as net from 'node:net'; import * as os from 'node:os'; import * as path from 'node:path'; import { spawn } from 'node:child_process'; import { resolveNatsBinary } from '../../src/utils/runner-transport.js'; import { assertExitCode, parseLastJson, runMxCli } from '../helpers/cli-harness.js'; interface IWebSocketLike { addEventListener( type: 'open' | 'error' | 'close', listener: (event: unknown) => void, options?: { once?: boolean }, ): void; close(): void; } async function getAvailablePorts(count: number): Promise { const servers = await Promise.all( Array.from({ length: count }, () => new Promise((resolve, reject) => { const server = net.createServer(); server.once('error', reject); server.listen(0, '127.0.0.1', () => resolve(server)); })), ); const ports = servers.map((server) => { const address = server.address(); assert.ok(address && typeof address === 'object'); return address.port; }); await Promise.all(servers.map((server) => new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }))); return ports; } async function createRunInPlaceFixture(rootDir: string): Promise { const packageDir = path.join(rootDir, 'sample-run-package'); const runtimeDir = path.join(packageDir, 'dist', 'runtime'); fs.mkdirSync(runtimeDir, { recursive: true }); fs.mkdirSync(path.join(packageDir, '.matrix'), { recursive: true }); const [natsPort, wsPort] = await getAvailablePorts(2); fs.writeFileSync( path.join(packageDir, 'package.json'), JSON.stringify({ name: '@acme/sample-run-package', version: '1.0.0', type: 'module', main: './dist/runtime/index.js', }, null, 2), 'utf8', ); fs.writeFileSync( path.join(packageDir, 'matrix.json'), JSON.stringify({ name: '@acme/sample-run-package', version: '1.0.0', runtime: { language: 'typescript', entry: './dist/runtime/index.js', factory: { kind: 'factory', export: 'createSampleRunService', bootstrap: { overrides: { mode: 'run-in-place', }, }, instance: { packageName: '@acme/sample-run-package', packageRevision: '1.0.0', class: 'service', rootKind: 'package-root', mount: 'sample-run.local', autoStart: true, }, }, }, components: [ { type: 'SampleRunService', export: 'SampleRunService', mount: 'sample-run', autoStart: true, }, ], permissions: { fsPolicy: 'none', }, }, null, 2), 'utf8', ); fs.writeFileSync( path.join(packageDir, '.matrix', 'dev.environment.json'), JSON.stringify({ name: 'dev', runtime: { root: 'COM.TEST.RUNNER', runtimeId: 'sample-run-dev', }, nats: { mode: 'embedded', port: natsPort, wsPort, binaryPath: resolveNatsBinary(process.cwd()), }, }, null, 2), 'utf8', ); fs.writeFileSync( path.join(runtimeDir, 'index.js'), `export async function createSampleRunService(overrides = {}, _services, _configResolution, bootstrapContext) { return { ok: true, overrides, bootContext: bootstrapContext, runtime: { async shutdown() { return undefined; } } }; } `, 'utf8', ); return packageDir; } async function createServeFixture(rootDir: string): Promise<{ packageDir: string; httpPort: number; }> { const packageDir = path.join(rootDir, 'sample-serve-package'); const distDir = path.join(packageDir, 'dist'); fs.mkdirSync(path.join(packageDir, '.matrix'), { recursive: true }); fs.mkdirSync(distDir, { recursive: true }); fs.mkdirSync(path.join(packageDir, 'branding'), { recursive: true }); const [httpPort, natsPort, wsPort] = await getAvailablePorts(3); fs.writeFileSync( path.join(packageDir, 'package.json'), JSON.stringify({ name: '@acme/sample-serve-package', version: '1.0.0', type: 'module', }, null, 2), 'utf8', ); fs.writeFileSync( path.join(packageDir, 'matrix.json'), JSON.stringify({ name: '@acme/sample-serve-package', version: '1.0.0', webapp: { distDir: 'dist', entry: 'index.html', appName: 'sample-serve', }, permissions: { fsPolicy: 'none', }, }, null, 2), 'utf8', ); fs.writeFileSync( path.join(packageDir, '.matrix', 'dev.environment.json'), JSON.stringify({ name: 'dev', runtime: { root: 'COM.TEST.SERVE', runtimeId: 'sample-serve-dev', }, nats: { mode: 'embedded', port: natsPort, wsPort, binaryPath: resolveNatsBinary(process.cwd()), }, http: { enabled: true, port: httpPort, }, }, null, 2), 'utf8', ); fs.writeFileSync( path.join(distDir, 'index.html'), 'Sample Serve
Sample Serve Fixture
\n', 'utf8', ); fs.writeFileSync( path.join(distDir, 'app.js'), 'console.log("sample-serve");\n', 'utf8', ); fs.writeFileSync( path.join(packageDir, 'branding', 'manifest.json'), JSON.stringify({ name: 'Sample Serve', short_name: 'Sample', start_url: '/', display: 'standalone', icons: [{ src: '/branding/favicon.svg', sizes: '16x16', type: 'image/svg+xml' }], }, null, 2), 'utf8', ); fs.writeFileSync( path.join(packageDir, 'branding', 'favicon.svg'), '\n', 'utf8', ); return { packageDir, httpPort }; } async function waitForHttpReady( url: string, child: ReturnType, timeoutMs = 15_000, ): Promise { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { if (child.exitCode !== null) { throw new Error(`mx run exited early with code ${child.exitCode}`); } try { const response = await fetch(url); if (response.ok) { return; } } catch { // Retry until the deadline expires. } await new Promise((resolve) => setTimeout(resolve, 100)); } throw new Error(`Timed out waiting for ${url}`); } describe('mx run package front door', () => { it('runs a package from the current folder via matrix.json runtime.factory and package-local environment', async () => { const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-run-package-')); const packageDir = await createRunInPlaceFixture(tempRoot); assert.equal(fs.existsSync(path.join(packageDir, '.matrix', 'host.status.json')), false); const result = await runMxCli( ['node', 'mx', 'run', '.', '--env', 'dev', '--check', '--json'], { cwd: packageDir }, ); assertExitCode(result, 0); const payload = parseLastJson(result.logs) as Record; assert.equal(payload.packageName, '@acme/sample-run-package'); assert.equal(payload.packageDir, packageDir); assert.equal(payload.environmentName, 'dev'); assert.equal(payload.root, 'COM.TEST.RUNNER'); assert.equal(payload.mount, 'sample-run.local'); assert.equal(payload.entryPath, path.join(packageDir, 'dist', 'runtime', 'index.js')); assert.equal(payload.runnerTransport?.mode, 'embedded'); assert.equal(payload.runnerTransport?.root, 'COM.TEST.RUNNER'); assert.match(String(payload.runnerTransport?.url), /^nats:\/\/127\.0\.0\.1:\d+$/); assert.match(String(payload.runnerTransport?.wsUrl), /^ws:\/\/127\.0\.0\.1:\d+$/); }); it('fails clearly when a directory target does not declare a runtime factory', async () => { const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-run-missing-service-')); const packageDir = path.join(tempRoot, 'missing-service'); fs.mkdirSync(packageDir, { recursive: true }); const result = await runMxCli( ['node', 'mx', 'run', '.'], { cwd: packageDir }, ); assertExitCode(result, 1); assert.match( result.errors.join('\n'), /does not declare a matrix\.json runtime\.factory/, ); }); it('runs a webapp package as a Matrix asset runtime without a package HTTP server', async () => { const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-run-webapp-assets-')); const { packageDir } = await createServeFixture(tempRoot); const result = await runMxCli( ['node', 'mx', 'run', '.', '--env', 'dev', '--check', '--json'], { cwd: packageDir }, ); assertExitCode(result, 0); const payload = parseLastJson(result.logs) as Record; assert.equal(payload.packageName, '@acme/sample-serve-package'); assert.equal(payload.mode, 'webapp-assets'); assert.equal(payload.assetMount, 'system.runtimes.sample-serve-dev.http'); assert.equal(payload.baseUrl, undefined); assert.equal(payload.runnerTransport?.mode, 'embedded'); }); it('publishes a served webapp HTTP origin in run metadata', async () => { const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-run-webapp-origin-')); const { packageDir, httpPort } = await createServeFixture(tempRoot); const result = await runMxCli( ['node', 'mx', 'run', '.', '--env', 'dev', '--serve', '--check', '--json'], { cwd: packageDir }, ); assertExitCode(result, 0); const payload = parseLastJson(result.logs) as Record; assert.equal(payload.packageName, '@acme/sample-serve-package'); assert.equal(payload.mode, 'standalone-webapp'); assert.equal(payload.assetMount, 'system.runtimes.sample-serve-dev.http'); assert.equal(payload.baseUrl, `http://127.0.0.1:${httpPort}`); assert.equal(payload.httpPort, httpPort); assert.equal(payload.runnerTransport?.mode, 'embedded'); }); it('serves built webapp assets with same-origin bootstrap and nats-ws routes', async () => { const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-run-serve-')); const { packageDir, httpPort } = await createServeFixture(tempRoot); const mxCliDir = path.resolve(import.meta.dirname, '../..'); const cliEntry = path.resolve(import.meta.dirname, '../../src/index.ts'); const tsxLoader = path.resolve(import.meta.dirname, '../../../../node_modules/tsx/dist/loader.mjs'); const child = spawn( process.execPath, ['--import', tsxLoader, cliEntry, 'run', packageDir, '--env', 'dev', '--serve'], { cwd: mxCliDir, stdio: ['ignore', 'pipe', 'pipe'], }, ); const stdout: string[] = []; const stderr: string[] = []; child.stdout.on('data', (chunk: Buffer) => { stdout.push(chunk.toString('utf8')); }); child.stderr.on('data', (chunk: Buffer) => { stderr.push(chunk.toString('utf8')); }); try { const baseUrl = `http://127.0.0.1:${httpPort}`; await waitForHttpReady(`${baseUrl}/healthz`, child); const html = await fetch(`${baseUrl}/`); assert.equal(html.status, 200); assert.match(await html.text(), /Sample Serve Fixture/); const bootstrap = await fetch(`${baseUrl}/api/bootstrap`); assert.equal(bootstrap.status, 200); const payload = await bootstrap.json() as { root?: string; transportPlan?: { wsPath?: string; authMode?: string; sessionMode?: string }; }; assert.equal(payload.root, 'COM.TEST.SERVE'); assert.equal(payload.transportPlan?.wsPath, '/nats-ws'); assert.equal(payload.transportPlan?.authMode, 'anonymous'); assert.equal(payload.transportPlan?.sessionMode, 'local-client'); const manifest = await fetch(`${baseUrl}/manifest.json`); assert.equal(manifest.status, 200); const branding = await fetch(`${baseUrl}/branding/favicon.svg`); assert.equal(branding.status, 200); const WebSocketCtor = (globalThis as typeof globalThis & { WebSocket?: new (url: string) => IWebSocketLike; }).WebSocket; assert.ok(WebSocketCtor, 'Node runtime must expose WebSocket for same-origin proxy proof'); const ws = new WebSocketCtor(`ws://127.0.0.1:${httpPort}/nats-ws`); await new Promise((resolve, reject) => { ws.addEventListener('open', () => { ws.close(); }, { once: true }); ws.addEventListener('error', (event) => { reject(new Error(`WebSocket upgrade failed: ${String(event)}`)); }, { once: true }); ws.addEventListener('close', () => { resolve(); }, { once: true }); }); } finally { child.kill('SIGTERM'); await new Promise((resolve) => { child.once('exit', () => resolve()); }); } assert.match(stdout.join(''), /serving @acme\/sample-serve-package/); assert.doesNotMatch(stderr.join(''), /Run target not found|does not declare|Error:/); }); });