151 lines
4.6 KiB
TypeScript
Raw Permalink Normal View History

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<string>([
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);
}