import * as fs from 'node:fs'; import * as path from 'node:path'; import type { ILoadedMatrixPackageEnvironment } from './package-environment.js'; export interface IResolvedRunnerTransportPlan { readonly envName: string; readonly root: string; readonly mode: 'embedded' | 'external'; readonly url: string; readonly wsUrl?: string; readonly credentialsRef?: string; readonly port?: number; readonly wsPort?: number; readonly binaryPath?: string; readonly dataDir?: string; readonly pidFile?: string; } export function resolveNatsBinary(baseDir: string, explicitBinaryPath?: string): string { const binaryOverride = readOptionalString(explicitBinaryPath); if (binaryOverride) { return path.resolve(baseDir, binaryOverride); } const envBinary = readOptionalString(process.env.MATRIX_NATS_SERVER_BINARY) ?? readOptionalString(process.env.NATS_SERVER_BINARY); if (envBinary) { return path.resolve(envBinary); } const binaryName = process.platform === 'win32' ? 'nats-server.exe' : 'nats-server'; const searchRoots = new Set([ baseDir, path.resolve(baseDir, '..'), path.resolve(baseDir, '..', '..'), path.resolve(baseDir, '..', '..', '..'), path.resolve(baseDir, '..', '..', '..', '..'), ]); for (const root of searchRoots) { const candidates = [ path.resolve(root, 'projects', 'matrix-3', 'packages', 'daemon', 'dist', 'bin', binaryName), path.resolve(root, 'projects', 'nats-server', binaryName), path.resolve(root, 'packages', 'daemon', 'dist', 'bin', binaryName), path.resolve(root, 'nats-server', binaryName), ]; for (const candidate of candidates) { if (pathExists(candidate)) { return candidate; } } } const pathEntries = (process.env.PATH ?? '') .split(path.delimiter) .filter((entry) => entry.length > 0); for (const entry of pathEntries) { const candidate = path.join(entry, binaryName); if (pathExists(candidate)) { return candidate; } } return binaryName; } export function resolveRunnerTransportPlan( packageDir: string, selectedEnvironment: ILoadedMatrixPackageEnvironment, rootOverride?: string, ): IResolvedRunnerTransportPlan { const nats = selectedEnvironment.environment.nats ?? {}; const root = readOptionalString(rootOverride) ?? selectedEnvironment.environment.runtime.root; const mode = inferMode(nats.mode, nats.url); if (mode === 'external') { const url = readOptionalString(nats.url); if (!url) { throw new Error( `Package environment "${selectedEnvironment.envName}" requires nats.url when nats.mode is external`, ); } const wsUrl = readOptionalString(nats.wsUrl); const credentialsRef = readOptionalString(nats.credentialsRef); return { envName: selectedEnvironment.envName, root, mode: 'external', url, ...(wsUrl ? { wsUrl } : {}), ...(credentialsRef ? { credentialsRef } : {}), }; } const port = readOptionalNumber(nats.port) ?? 4222; const wsPort = readOptionalNumber(nats.wsPort); const url = readOptionalString(nats.url) ?? `nats://127.0.0.1:${port}`; const wsUrl = readOptionalString(nats.wsUrl) ?? (wsPort ? `ws://127.0.0.1:${wsPort}` : undefined); const dataDir = path.resolve( packageDir, readOptionalString(nats.dataDir) ?? path.join('.matrix', 'state', selectedEnvironment.envName, 'nats'), ); const pidFile = path.resolve( packageDir, readOptionalString(nats.pidFile) ?? path.join('.matrix', 'state', selectedEnvironment.envName, 'nats-server.pid'), ); const binaryPath = resolveNatsBinary(packageDir, readOptionalString(nats.binaryPath)); return { envName: selectedEnvironment.envName, root, mode: 'embedded', url, port, ...(wsUrl ? { wsUrl } : {}), ...(wsPort !== undefined ? { wsPort } : {}), binaryPath, dataDir, pidFile, }; } function inferMode(mode: string | undefined, url: string | undefined): 'embedded' | 'external' { const normalizedMode = readOptionalString(mode)?.toLowerCase(); if (normalizedMode === 'external' || readOptionalString(url)) { return 'external'; } return 'embedded'; } function readOptionalString(value: unknown): string | undefined { if (typeof value !== 'string') { return undefined; } const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : undefined; } function readOptionalNumber(value: unknown): number | undefined { if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) { return undefined; } return value; } function pathExists(filePath: string): boolean { return fs.existsSync(filePath); }