UNPKG

@cosmstack/repoeject

Version:

Interactive CLI tool to safely find and delete old or inactive GitHub repositories. Clean up your GitHub profile by removing abandoned projects, old experiments, and unused forks.

211 lines 7.45 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.initGitHub = initGitHub; exports.authenticateWithOAuth = authenticateWithOAuth; exports.ensureAuthentication = ensureAuthentication; exports.fetchUserRepositories = fetchUserRepositories; exports.fetchRepositoryCommits = fetchRepositoryCommits; exports.deleteRepository = deleteRepository; const rest_1 = require("@octokit/rest"); const inquirer_1 = __importDefault(require("inquirer")); const ora_1 = __importDefault(require("ora")); const config_1 = require("../config"); const open_1 = __importDefault(require("open")); const node_http_1 = __importDefault(require("node:http")); const node_url_1 = __importDefault(require("node:url")); const config_store_1 = require("./config-store"); let octokit = null; const oauthConfig = { clientId: "YOUR_CLIENT_ID", clientSecret: "YOUR_CLIENT_SECRET", scopes: ["delete_repo", "repo"], redirectUri: "http://localhost:3000/callback", }; function initGitHub(token) { octokit = new rest_1.Octokit({ auth: token, userAgent: "repoeject", timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, baseUrl: "https://api.github.com", }); return octokit; } async function authenticateWithOAuth() { const authUrl = `https://github.com/login/oauth/authorize?client_id=${oauthConfig.clientId}&scope=${oauthConfig.scopes.join(",")}&redirect_uri=${oauthConfig.redirectUri}`; console.log("Opening GitHub authorization page..."); (0, open_1.default)(authUrl); return new Promise((resolve, reject) => { const server = node_http_1.default.createServer(async (req, res) => { const { pathname, query } = node_url_1.default.parse(req.url || "", true); if (pathname === "/callback" && query.code) { try { const tokenData = await exchangeCodeForToken(query.code); res.writeHead(200, { "Content-Type": "text/html" }); res.end("<h1>Authentication successful!</h1><p>You can close this window and return to the CLI.</p>"); server.close(); resolve(tokenData); } catch (error) { reject(error); } } }); server.listen(3000); }); } async function exchangeCodeForToken(code) { const response = await fetch("https://github.com/login/oauth/access_token", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify({ client_id: oauthConfig.clientId, client_secret: oauthConfig.clientSecret, code, redirect_uri: oauthConfig.redirectUri, }), }); const data = await response.json(); return { accessToken: data.access_token, refreshToken: data.refresh_token, expiresAt: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined, tokenType: "oauth", }; } async function ensureAuthentication() { if (octokit) return octokit; let token = process.env.GITHUB_TOKEN; let tokenData; if (!token) { token = await (0, config_store_1.getGitHubToken)(); } else { if (token) { tokenData = { accessToken: token, tokenType: "pat", }; } } if (!token) { const spinner = (0, ora_1.default)("No GitHub token found").start(); spinner.info(); const { authMethod } = await inquirer_1.default.prompt([ { type: "list", name: "authMethod", message: "How would you like to authenticate?", choices: [ { name: "Open browser for GitHub authorization", value: "oauth", disabled: !config_1.config.authMethods.oauth, }, { name: "Enter personal access token manually", value: "token", disabled: !config_1.config.authMethods.token, }, ], }, ]); if (authMethod === "oauth") { tokenData = await authenticateWithOAuth(); token = tokenData.accessToken; } else { const { inputToken } = await inquirer_1.default.prompt([ { type: "password", name: "inputToken", message: "Enter your GitHub personal access token:", validate: (input) => input.length > 0 ? true : "Token is required", }, ]); token = inputToken; tokenData = { accessToken: token, tokenType: "pat", }; } if (tokenData) { (0, config_store_1.saveGitHubToken)(tokenData); } } if (!token) throw new Error("GitHub token is required"); return initGitHub(token); } async function fetchUserRepositories() { const github = await ensureAuthentication(); const spinner = (0, ora_1.default)("Fetching your repositories...").start(); try { const repos = []; let page = 1; let hasNextPage = true; while (hasNextPage && page <= config_1.config.github.maxPages) { const response = await github.repos.listForAuthenticatedUser({ per_page: config_1.config.github.perPage, page, sort: "updated", direction: "asc", }); repos.push(...response.data); if (response.data.length < config_1.config.github.perPage) { hasNextPage = false; } else { page++; } spinner.text = `Fetching your repositories... (${repos.length} found)`; } spinner.succeed(`Found ${repos.length} repositories`); return repos; } catch (error) { spinner.fail("Failed to fetch repositories"); throw new Error(`GitHub API error: ${error instanceof Error ? error.message : "Unknown error"}`); } } async function fetchRepositoryCommits(owner, repo) { const github = await ensureAuthentication(); try { const response = await github.repos.listCommits({ owner, repo, per_page: 100, }); return response.data; } catch (error) { if (error instanceof Error && "status" in error && (error.status === 409 || error.status === 404)) { return []; } throw error; } } async function deleteRepository(owner, repo) { const github = await ensureAuthentication(); try { await github.repos.delete({ owner, repo, }); return true; } catch (error) { throw new Error(`Failed to delete repository ${owner}/${repo}: ${error instanceof Error ? error.message : "Unknown error"}`); } } //# sourceMappingURL=github.js.map