196 lines
4.8 KiB
JavaScript
196 lines
4.8 KiB
JavaScript
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/<tenderId>.",
|
|
);
|
|
}
|
|
|
|
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);
|
|
}
|