#!/usr/bin/env node const debug = require('debug')('cli') const https = require("https"); const fs = require("fs"); const path = require("path"); const os = require("os"); const { URL } = require("url"); const yaml = require("yaml"); // === CLI PARSING === const rawArgs = process.argv.slice(2); const asJSON = rawArgs.includes("--json"); const asMarkdown = rawArgs.includes("--markdown"); if (rawArgs.includes("--help")) { process.stdout.write(`USAGE: ${path.basename(process.argv[0])} {search} --json - format as JSON --markdown - format as Markdown table --help - show help\n`) process.exit(0) } const query = rawArgs.find(arg => !arg.startsWith("--")) || null; // === CONFIG SEARCH === function findTeaConfig() { const xdg = process.env.XDG_CONFIG_HOME; const home = os.homedir(); const platform = process.platform; const paths = []; if (xdg) paths.push(path.join(xdg, "tea", "config.yml")); if (platform === "darwin") { paths.push(path.join(home, "Library", "Application Support", "tea", "config.yml")); } else { paths.push(path.join(home, ".config", "tea", "config.yml")); } paths.push(path.join(home, ".tea", "tea.yml")); for (const p of paths) { if (fs.existsSync(p)) return p; } return null; } function loadGiteaLogin() { const configPath = findTeaConfig(); if (!configPath) { console.error("Could not find tea config.yml or tea.yml"); process.exit(1); } const file = fs.readFileSync(configPath, "utf8"); const parsed = yaml.parse(file); const logins = parsed.logins || []; if (logins.length === 0) { console.error("No logins found in config"); process.exit(1); } const login = logins.find((l) => l.default) || logins[0]; debug(`Using config: ${configPath}`); debug(`Gitea user: ${login.user}`); debug(`Gitea URL: ${login.url}`); debug(`Auth token: ${login.token ? '[present]' : '[missing]'}`); return { baseUrl: login.url.replace(/\/$/, ""), token: login.token, user: login.user, }; } function fetchJSON(baseUrl, path, token) { return new Promise((resolve, reject) => { const url = new URL(path, baseUrl); const options = { headers: { Authorization: `token ${token}`, Accept: "application/json", }, }; https.get(url, options, (res) => { let body = ""; res.on("data", (chunk) => (body += chunk)); res.on("end", () => { try { const json = JSON.parse(body); resolve(json); } catch (err) { reject(new Error("Failed to parse JSON: " + err.message)); } }); }).on("error", reject); }); } function printPlain(packages) { packages.forEach(pkg => { console.log(`${pkg.name}@${pkg.version} — ${pkg.created_at}`); }); } function printMarkdown(packages) { console.log(`| Package | Version | Created At |`); console.log(`|---------|---------|------------|`); packages.forEach(pkg => { const name = `\`${pkg.name}\``; const version = `\`${pkg.version}\``; const created = new Date(pkg.created_at).toISOString().split("T")[0]; console.log(`| ${name} | ${version} | ${created} |`); }); } function printJSON(packages) { console.log(JSON.stringify(packages, null, 2)); } async function listPackages() { const { baseUrl, token, user } = loadGiteaLogin(); const queryParams = new URLSearchParams({ type: "npm" }); if (query) queryParams.append("q", query); const endpoint = `/api/v1/packages/${user}?${queryParams.toString()}`; debug(`📦 Fetching from: ${baseUrl}${endpoint}`); try { const packages = await fetchJSON(baseUrl, endpoint, token); if (!Array.isArray(packages)) throw new Error("Unexpected response"); if (asJSON) { printJSON(packages); } else if (asMarkdown) { printMarkdown(packages); } else { printPlain(packages); } } catch (err) { console.error("Error:", err.message); } } listPackages();