186 lines
6.1 KiB
JavaScript
186 lines
6.1 KiB
JavaScript
#!/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;
|
|
});
|
|
}
|