import { readFileSync, existsSync } from "node:fs"; import { resolve } from "node:path"; const DEFAULT_TIMEOUT_MS = 20000; function loadDotenv(filePath) { if (!existsSync(filePath)) { return; } const raw = readFileSync(filePath, "utf8"); for (const line of raw.split(/\r?\n/)) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("#")) { continue; } const eqIndex = trimmed.indexOf("="); if (eqIndex === -1) { continue; } const key = trimmed.slice(0, eqIndex).trim(); if (!key || process.env[key] !== undefined) { continue; } let value = trimmed.slice(eqIndex + 1).trim(); if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { value = value.slice(1, -1); } process.env[key] = value; } } function parseArgs(argv) { const args = { baseUrl: process.env.LICITAYA_BASE_URL, endpoint: process.env.LICITAYA_TEST_ENDPOINT, accept: process.env.LICITAYA_ACCEPT || "application/json", method: "GET", timeoutMs: Number.parseInt(process.env.LICITAYA_TIMEOUT_MS || "", 10) || DEFAULT_TIMEOUT_MS, }; for (let i = 0; i < argv.length; i += 1) { const current = argv[i]; const next = argv[i + 1]; if (current === "--base-url" && next) { args.baseUrl = next; i += 1; continue; } if (current === "--endpoint" && next) { args.endpoint = next; i += 1; continue; } if (current === "--accept" && next) { args.accept = next; i += 1; continue; } if (current === "--method" && next) { args.method = next.toUpperCase(); i += 1; continue; } if (current === "--timeout" && next) { const parsed = Number.parseInt(next, 10); if (Number.isFinite(parsed) && parsed > 0) { args.timeoutMs = parsed; } i += 1; continue; } } return args; } function buildUrl(baseUrl, endpoint) { if (!endpoint) { throw new Error("Missing LICITAYA_TEST_ENDPOINT (or --endpoint)."); } if (endpoint.includes("<") || endpoint.includes(">")) { throw new Error( "LICITAYA_TEST_ENDPOINT still contains placeholders. Use a real path such as /tender/search or /tender/.", ); } if (/^https?:\/\//i.test(endpoint)) { return new URL(endpoint); } if (!baseUrl) { throw new Error("Missing LICITAYA_BASE_URL (or --base-url)."); } const normalizedBase = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`; const cleanEndpoint = endpoint.startsWith("/") ? endpoint.slice(1) : endpoint; return new URL(cleanEndpoint, normalizedBase); } function previewBody(rawBody, contentType) { const trimmed = rawBody.trim(); if (!trimmed) { return "(empty body)"; } const isJson = contentType.includes("application/json") || trimmed.startsWith("{") || trimmed.startsWith("["); if (isJson) { try { const json = JSON.parse(trimmed); return JSON.stringify(json, null, 2); } catch { return trimmed.slice(0, 3000); } } return trimmed.slice(0, 3000); } loadDotenv(resolve(process.cwd(), ".env")); const apiKey = process.env.LICITAYA_API_KEY || process.env.X_API_KEY || process.env.X_API_KEY_LICITAYA; if (!apiKey) { console.error("Missing API key. Set LICITAYA_API_KEY in .env or shell env."); process.exit(1); } const args = parseArgs(process.argv.slice(2)); let url; try { url = buildUrl(args.baseUrl, args.endpoint); } catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exit(1); } const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), args.timeoutMs); try { const response = await fetch(url, { method: args.method, headers: { "X-API-KEY": apiKey, Accept: args.accept, }, signal: controller.signal, }); const contentType = response.headers.get("content-type") || ""; const rawBody = await response.text(); const bodyPreview = previewBody(rawBody, contentType); console.log(`URL: ${url.toString()}`); console.log(`Method: ${args.method}`); console.log(`Status: ${response.status} ${response.statusText}`); console.log(`Content-Type: ${contentType || "(none)"}`); console.log("--- Response Preview ---"); console.log(bodyPreview); if (response.status === 404 && url.pathname.endsWith("/tender/search")) { console.error("No tenders matched the current filters. Try a broader keyword or fewer filters."); } if (!response.ok) { process.exit(1); } } catch (error) { if (error instanceof Error && error.name === "AbortError") { console.error(`Request timed out after ${args.timeoutMs} ms.`); } else { console.error(error instanceof Error ? error.message : String(error)); } process.exit(1); } finally { clearTimeout(timeout); }