#!/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, mode: "force_on", issueId: process.env.QA_ISSUE_ID || "AUTOREM", }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (arg === "--mode") { const value = argv[index + 1]; if (!value) { throw new Error("--mode requires force_on or force_off"); } options.mode = value; index += 1; } else if (arg.startsWith("--mode=")) { options.mode = arg.slice("--mode=".length); } else if (arg.startsWith("--base-url=")) { options.baseUrl = arg.slice("--base-url=".length); } } if (!["force_on", "force_off"].includes(options.mode)) { throw new Error(`invalid mode: ${options.mode}`); } options.baseUrl = options.baseUrl.trim().replace(/\/$/, ""); options.issueId = options.issueId.trim() || "AUTOREM"; return options; } 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": "autorem-payment-batch-flag-restore", }, 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; } async function listFlags(baseUrl, accessToken) { const result = await fetchJson(`${baseUrl}/api/system/feature-flags`, { headers: { authorization: `Bearer ${accessToken}`, accept: "application/json", }, }); if (!result.ok) { throw new Error(`feature flag list failed: HTTP ${result.status} ${summarizeError(result.json).code ?? ""}`.trim()); } return result.json?.data?.items ?? []; } function findRequiredFlag(items, flagKey) { const item = items.find((candidate) => candidate.flagKey === flagKey); if (!item) { throw new Error(`missing feature flag: ${flagKey}`); } return item; } function summarizeFlag(item) { return { flagKey: item.flagKey, overrideMode: item.overrideMode, rolloutType: item.rolloutType, version: item.version, effectiveEnabled: item.effectiveEnabled, effectiveReason: item.effectiveReason, }; } function printFlag(prefix, item) { const summary = summarizeFlag(item); console.log( `[restore-payment-batch-flag] ${prefix} ${summary.flagKey} override=${summary.overrideMode} effective=${summary.effectiveEnabled} reason=${summary.effectiveReason} version=${summary.version}`, ); } async function updatePaymentFlag(baseUrl, accessToken, paymentFlag, mode, issueId) { const result = await fetchJson(`${baseUrl}/api/system/feature-flags/payment_batch_v1`, { method: "PUT", headers: { authorization: `Bearer ${accessToken}`, "content-type": "application/json", accept: "application/json", }, body: JSON.stringify({ overrideMode: mode, rolloutType: "global", departmentIds: [], note: `${issueId} restore formal payment batch flag to ${mode}`, expectedVersion: paymentFlag.version, }), }); if (!result.ok) { throw new Error(`payment_batch_v1 update failed: HTTP ${result.status} ${summarizeError(result.json).code ?? ""}`.trim()); } return result.json?.data?.item ?? null; } function assertFinalState(paymentFlag, erpFlag, mode) { const paymentOk = mode === "force_on" ? paymentFlag.overrideMode === "force_on" && paymentFlag.effectiveEnabled === true : paymentFlag.overrideMode === "force_off" && paymentFlag.effectiveEnabled === false; const erpOk = erpFlag.overrideMode === "force_off" && erpFlag.effectiveEnabled === false; if (!paymentOk) { throw new Error(`payment_batch_v1 final state mismatch: override=${paymentFlag.overrideMode} effective=${paymentFlag.effectiveEnabled}`); } if (!erpOk) { throw new Error(`erp_sync final state mismatch: override=${erpFlag.overrideMode} effective=${erpFlag.effectiveEnabled}`); } } async function main() { const options = parseArgs(process.argv.slice(2)); if (process.env.AUTOREM_RESTORE_APPROVED !== "1") { throw new Error("AUTOREM_RESTORE_APPROVED=1 is required"); } const credential = getAdminCredential(); if (!credential) { throw new Error("missing LIVE_SMOKE_ADMIN_* credentials"); } const accessToken = await login(options.baseUrl, credential); const beforeItems = await listFlags(options.baseUrl, accessToken); const beforePayment = findRequiredFlag(beforeItems, "payment_batch_v1"); const beforeErp = findRequiredFlag(beforeItems, "erp_sync"); printFlag("before", beforePayment); printFlag("before", beforeErp); if (beforePayment.overrideMode === options.mode) { console.log(`[restore-payment-batch-flag] payment_batch_v1 already ${options.mode}; no update sent`); } else { const updated = await updatePaymentFlag(options.baseUrl, accessToken, beforePayment, options.mode, options.issueId); if (updated) { printFlag("updated", updated); } } const afterItems = await listFlags(options.baseUrl, accessToken); const afterPayment = findRequiredFlag(afterItems, "payment_batch_v1"); const afterErp = findRequiredFlag(afterItems, "erp_sync"); assertFinalState(afterPayment, afterErp, options.mode); printFlag("after", afterPayment); printFlag("after", afterErp); console.log(`[restore-payment-batch-flag] status=PASS base=${options.baseUrl} mode=${options.mode}`); } main().catch((error) => { console.error(`[restore-payment-batch-flag][ERROR] ${error instanceof Error ? error.message : String(error)}`); process.exit(1); });