#!/usr/bin/env node const DEFAULT_BASE_URL = "https://oa.hyinda.com"; function parseArgs(argv) { const options = { baseUrl: process.env.LIVE_SMOKE_BASE_URL || DEFAULT_BASE_URL, json: false, expectations: [], }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (arg === "--json") { options.json = true; } else if (arg === "--expect") { const value = argv[index + 1]; if (!value) { throw new Error("--expect requires key=value"); } options.expectations.push(value); index += 1; } else if (arg.startsWith("--expect=")) { options.expectations.push(arg.slice("--expect=".length)); } else if (arg.startsWith("--base-url=")) { options.baseUrl = arg.slice("--base-url=".length); } } options.baseUrl = options.baseUrl.trim().replace(/\/$/, ""); return options; } function parseExpectation(value) { const [flagKey, expectedState] = value.split("="); if (!flagKey || !expectedState) { throw new Error(`invalid expectation: ${value}`); } const allowed = new Set(["force_on", "force_off", "enabled", "disabled"]); if (!allowed.has(expectedState)) { throw new Error(`invalid expected state for ${flagKey}: ${expectedState}`); } return { flagKey, expectedState }; } function getAdminCredential() { const identifier = process.env.LIVE_SMOKE_ADMIN_IDENTIFIER?.trim(); const password = process.env.LIVE_SMOKE_ADMIN_PASSWORD?.trim(); if (!identifier || !password) { return null; } return { identifier, password }; } async function fetchJson(url, init = {}) { const response = await fetch(url, { ...init, signal: AbortSignal.timeout(20_000), }); 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, json }; } function summarizeError(json) { const error = json?.error ?? {}; return { code: typeof error.code === "string" ? error.code : null, message: typeof error.message === "string" ? error.message.slice(0, 120) : null, }; } async function login(baseUrl, credential) { const result = await fetchJson(`${baseUrl}/api/auth/login`, { method: "POST", headers: { "content-type": "application/json", "x-device-fingerprint": "launch-auto-feature-flags", }, body: JSON.stringify({ identifier: credential.identifier, password: credential.password, }), }); const accessToken = result.json?.data?.accessToken; if (!result.ok || typeof accessToken !== "string" || !accessToken) { throw new Error(`admin login failed: HTTP ${result.status} ${summarizeError(result.json).code ?? ""}`.trim()); } return accessToken; } function checkExpectation(item, expectedState) { if (!item) { return false; } if (expectedState === "force_on") { return item.overrideMode === "force_on" && item.effectiveEnabled === true; } if (expectedState === "force_off") { return item.overrideMode === "force_off" && item.effectiveEnabled === false; } if (expectedState === "enabled") { return item.effectiveEnabled === true; } return item.effectiveEnabled === false; } function buildFlagSummary(item) { if (!item) { return null; } return { flagKey: item.flagKey, overrideMode: item.overrideMode, rolloutType: item.rolloutType, version: item.version, envEnabled: item.envEnabled, effectiveEnabled: item.effectiveEnabled, effectiveReason: item.effectiveReason, }; } function printHuman(summary) { console.log(`[live-feature-flags] status=${summary.status} base=${summary.baseUrl}`); for (const result of summary.results) { const item = result.item; const detail = item ? `override=${item.overrideMode} effective=${item.effectiveEnabled} reason=${item.effectiveReason}` : "missing"; console.log( `[live-feature-flags] ${result.flagKey} expected=${result.expectedState} status=${result.status} ${detail}`, ); } } async function main() { const options = parseArgs(process.argv.slice(2)); const expectations = options.expectations.map(parseExpectation); if (!expectations.length) { throw new Error("at least one --expect key=value is required"); } const credential = getAdminCredential(); if (!credential) { throw new Error("missing LIVE_SMOKE_ADMIN_* credentials"); } const accessToken = await login(options.baseUrl, credential); const flags = await fetchJson(`${options.baseUrl}/api/system/feature-flags`, { headers: { authorization: `Bearer ${accessToken}`, accept: "application/json", }, }); if (!flags.ok) { throw new Error(`feature flag list failed: HTTP ${flags.status} ${summarizeError(flags.json).code ?? ""}`.trim()); } const items = flags.json?.data?.items ?? []; const results = expectations.map((expectation) => { const item = items.find((candidate) => candidate.flagKey === expectation.flagKey); const passed = checkExpectation(item, expectation.expectedState); return { ...expectation, status: passed ? "PASS" : "FAIL", item: buildFlagSummary(item), }; }); const status = results.every((result) => result.status === "PASS") ? "PASS" : "FAIL"; const summary = { checkedAt: new Date().toISOString(), baseUrl: options.baseUrl, status, readOnly: true, results, }; if (options.json) { console.log(JSON.stringify(summary, null, 2)); } else { printHuman(summary); } if (status !== "PASS") { process.exit(1); } } main().catch((error) => { console.error(`[live-feature-flags][ERROR] ${error instanceof Error ? error.message : String(error)}`); process.exit(1); });