#!/usr/bin/env node 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 flags const args = process.argv.slice(2); const asJSON = args.includes("--json"); const asMarkdown = args.includes("--markdown"); // === STEP 1: Load config from tea YAML === function findTeaConfig() { const xdg = process.env.XDG_CONFIG_HOME; const home = os.homedir(); const platform = process.platform; const paths = []; // 1. XDG_CONFIG_HOME override if (xdg) { paths.push(path.join(xdg, "tea", "config.yml")); } // 2. Platform-specific default if (platform === "darwin") { // macOS: ~/Library/Application Support/tea/config.yml paths.push(path.join(home, "Library", "Application Support", "tea", "config.yml")); } else { // Linux/others: ~/.config/tea/config.yml paths.push(path.join(home, ".config", "tea", "config.yml")); } // 3. Legacy fallback paths.push(path.join(home, ".tea", "tea.yml")); // 4. First existing config wins 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]; return { baseUrl: login.url.replace(/\/$/, ""), token: login.token, user: login.user, }; } // === STEP 2: Fetch package list from Gitea === 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); }); } // === STEP 3: Output functions === 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)); } // === STEP 4: Main logic === async function listPackages() { const { baseUrl, token, user } = loadGiteaLogin(); const isOrg = false; // You can change this or extend to CLI option const endpoint = isOrg ? `/api/v1/orgs/${user}/packages/npm` : `/api/v1/users/${user}/packages/npm`; 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();