@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
JavaScript
;
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