npm-gitea/index.js
2025-04-29 23:18:08 -06:00

146 lines
3.7 KiB
JavaScript
Executable File

#!/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();