Merge Memory V2 runtime facade
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
function normalizeBaseUrl(value) {
|
||||
return String(value ?? '').trim().replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function requireValue(name, value) {
|
||||
if (!value) throw new Error(`${name} requires a value`);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function parseMemoryV2AppCanaryArgs(argv = [], env = process.env) {
|
||||
const options = {
|
||||
baseUrl: normalizeBaseUrl(env.MEMORY_V2_APP_BASE_URL || 'http://127.0.0.1:8081'),
|
||||
requireEnabled: false,
|
||||
requireTargetHealthy: false,
|
||||
expectedBackend: null,
|
||||
expectedSelectedBackend: null,
|
||||
timeoutMs: Number(env.MEMORY_V2_APP_CANARY_TIMEOUT_MS || 8000),
|
||||
help: false,
|
||||
};
|
||||
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const arg = argv[i];
|
||||
if (arg === '--base-url') {
|
||||
options.baseUrl = normalizeBaseUrl(requireValue(arg, argv[++i]));
|
||||
} else if (arg === '--require-enabled') {
|
||||
options.requireEnabled = true;
|
||||
} else if (arg === '--require-target-healthy') {
|
||||
options.requireTargetHealthy = true;
|
||||
} else if (arg === '--expect-backend') {
|
||||
options.expectedBackend = requireValue(arg, argv[++i]);
|
||||
} else if (arg === '--expect-selected-backend') {
|
||||
options.expectedSelectedBackend = requireValue(arg, argv[++i]);
|
||||
} else if (arg === '--timeout-ms') {
|
||||
options.timeoutMs = Number(requireValue(arg, argv[++i]));
|
||||
} else if (arg === '-h' || arg === '--help') {
|
||||
options.help = true;
|
||||
} else {
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.baseUrl) throw new Error('--base-url is required');
|
||||
if (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0) {
|
||||
throw new Error('--timeout-ms must be a positive number');
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function usage() {
|
||||
return `Usage:
|
||||
node scripts/check-memory-v2-app-canary.mjs [options]
|
||||
|
||||
Options:
|
||||
--base-url <url> Portal base URL. Defaults to MEMORY_V2_APP_BASE_URL or http://127.0.0.1:8081.
|
||||
--require-enabled Fail unless /api/runtime/status reports memory.enabled=true.
|
||||
--expect-backend <name> Fail unless memory.backend equals this value.
|
||||
--expect-selected-backend <name> Fail unless memory.selectedBackend equals this value.
|
||||
--require-target-healthy Fail unless at least one runtime target is healthy.
|
||||
--timeout-ms <ms> Request timeout. Defaults to 8000.
|
||||
`;
|
||||
}
|
||||
|
||||
async function fetchJson(fetchImpl, url, timeoutMs) {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||||
try {
|
||||
const response = await fetchImpl(url, { signal: controller.signal });
|
||||
const text = await response.text();
|
||||
let json = null;
|
||||
try {
|
||||
json = text ? JSON.parse(text) : null;
|
||||
} catch {
|
||||
json = null;
|
||||
}
|
||||
return {
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
text,
|
||||
json,
|
||||
};
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
function check(name, ok, details = {}) {
|
||||
return { name, ok: Boolean(ok), ...details };
|
||||
}
|
||||
|
||||
export async function runMemoryV2AppCanaryCli({
|
||||
argv = process.argv.slice(2),
|
||||
env = process.env,
|
||||
stdout = process.stdout,
|
||||
fetchImpl = globalThis.fetch,
|
||||
} = {}) {
|
||||
const options = parseMemoryV2AppCanaryArgs(argv, env);
|
||||
if (options.help) {
|
||||
stdout.write(usage());
|
||||
return 0;
|
||||
}
|
||||
if (typeof fetchImpl !== 'function') {
|
||||
throw new Error('fetch is not available in this Node.js runtime');
|
||||
}
|
||||
|
||||
const runtime = await fetchJson(fetchImpl, `${options.baseUrl}/api/runtime/status`, options.timeoutMs);
|
||||
const apiStatus = await fetchJson(fetchImpl, `${options.baseUrl}/api/status`, options.timeoutMs);
|
||||
const authStatus = await fetchJson(fetchImpl, `${options.baseUrl}/auth/status`, options.timeoutMs);
|
||||
|
||||
const memory = runtime.json?.memory ?? null;
|
||||
const targets = Array.isArray(runtime.json?.targets) ? runtime.json.targets : [];
|
||||
const healthyTargets = targets.filter((target) => target?.healthy);
|
||||
const checks = [
|
||||
check('runtime_status_http_ok', runtime.ok, { status: runtime.status }),
|
||||
check('runtime_status_ok', runtime.json?.ok === true),
|
||||
check('memory_status_present', Boolean(memory)),
|
||||
check('api_status_http_ok', apiStatus.ok, { status: apiStatus.status }),
|
||||
check('auth_status_http_ok', authStatus.ok, { status: authStatus.status }),
|
||||
];
|
||||
|
||||
if (options.requireEnabled) {
|
||||
checks.push(check('memory_enabled', memory?.enabled === true, { actual: memory?.enabled ?? null }));
|
||||
}
|
||||
if (options.expectedBackend) {
|
||||
checks.push(check('memory_backend', memory?.backend === options.expectedBackend, {
|
||||
expected: options.expectedBackend,
|
||||
actual: memory?.backend ?? null,
|
||||
}));
|
||||
}
|
||||
if (options.expectedSelectedBackend) {
|
||||
checks.push(check('memory_selected_backend', memory?.selectedBackend === options.expectedSelectedBackend, {
|
||||
expected: options.expectedSelectedBackend,
|
||||
actual: memory?.selectedBackend ?? null,
|
||||
}));
|
||||
}
|
||||
if (options.requireTargetHealthy) {
|
||||
checks.push(check('runtime_target_healthy', healthyTargets.length > 0, {
|
||||
healthyTargets: healthyTargets.length,
|
||||
targets: targets.length,
|
||||
}));
|
||||
}
|
||||
|
||||
const report = {
|
||||
ok: checks.every((item) => item.ok),
|
||||
baseUrl: options.baseUrl,
|
||||
summary: {
|
||||
memory: memory
|
||||
? {
|
||||
enabled: memory.enabled,
|
||||
backend: memory.backend,
|
||||
selectedBackend: memory.selectedBackend,
|
||||
failOpen: memory.failOpen,
|
||||
vectorEnabled: memory.vectorEnabled,
|
||||
}
|
||||
: null,
|
||||
targets: {
|
||||
total: targets.length,
|
||||
healthy: healthyTargets.length,
|
||||
},
|
||||
apiStatus: {
|
||||
status: apiStatus.status,
|
||||
body: apiStatus.text.slice(0, 80),
|
||||
},
|
||||
authStatus: {
|
||||
status: authStatus.status,
|
||||
authenticated: authStatus.json?.authenticated ?? null,
|
||||
mode: authStatus.json?.mode ?? null,
|
||||
},
|
||||
},
|
||||
checks,
|
||||
};
|
||||
|
||||
stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
||||
return report.ok ? 0 : 1;
|
||||
}
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
runMemoryV2AppCanaryCli().then((code) => {
|
||||
process.exitCode = code;
|
||||
}).catch((err) => {
|
||||
console.error(err instanceof Error ? err.message : err);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user