import type { ViewMode } from "../../types/index.ts"; import type { CLIOptions } from "./list.ts"; import { showStatus } from "./list.ts"; import { scanAllDirectories, filterProjects } from "../../scanner/index.ts"; import { defaultGitHubService } from "../../services/github.ts"; import { fetchUnifiedRepos, filterByViewMode, sortUnifiedRepos, filterUnifiedRepos, cloneGitHubRepo, getUnifiedStats, } from "../../github/unified.ts"; import { ensureAuthenticated } from "../../github/auth.ts"; import { formatUnifiedRepoList, formatWarning, formatError, formatInfo, formatScanning, formatOperationItem, formatOperationSummary, formatUnifiedStatusJson, formatUnifiedStatusDisplay, formatCloneItem, } from "../formatters.ts"; /** * Create GitHub repos for projects without remotes */ export async function createGitHubRepos(options: CLIOptions & { isPrivate?: boolean }): Promise { const { config, filter, isPrivate = true } = options; // Check GitHub auth - auto-login if not set const token = await ensureAuthenticated(); if (!token) { console.log(formatError("GitHub authentication required.")); console.log(formatInfo("Run 'gitforest login' to authenticate.")); process.exit(1); } console.log(formatScanning()); const projects = await scanAllDirectories(config); let noRemoteProjects = projects.filter((p) => p.type === "git" && !p.status?.hasRemote); if (filter) { noRemoteProjects = filterProjects(noRemoteProjects, filter); } if (noRemoteProjects.length === 0) { console.log(formatWarning("No projects without remotes found.")); return; } console.log(formatInfo(`\nCreating ${isPrivate ? "private" : "public"} repos for ${noRemoteProjects.length} projects...\n`)); let success = 0; for (const project of noRemoteProjects) { const result = await defaultGitHubService.createRepo({ name: project.name, isPrivate, localPath: project.path, }); console.log(formatOperationItem(project.name, result.success, result.error)); if (result.success) success++; } console.log(`\n${formatOperationSummary("Created", success, noRemoteProjects.length)}`); } /** * Archive GitHub repositories */ export async function archiveRepos(options: CLIOptions & { repos: string[]; yes?: boolean }): Promise { const { repos, yes = false } = options; if (!yes) { throw new Error( "Archiving GitHub repositories is destructive. Re-run with --yes (or -y) after reviewing the repository list." ); } // Check GitHub auth - auto-login if not set const token = await ensureAuthenticated(); if (!token) { console.log(formatError("GitHub authentication required.")); console.log(formatInfo("Run 'gitforest login' to authenticate.")); process.exit(1); } console.log(formatInfo(`\nArchiving ${repos.length} repositories...\n`)); let success = 0; for (const repo of repos) { const result = await defaultGitHubService.archiveRepo(repo); console.log(formatOperationItem(repo, result.success, result.error)); if (result.success) success++; } console.log(`\n${formatOperationSummary("Archived", success, repos.length)}`); } /** * List GitHub repositories (not cloned locally) */ export async function listGitHubRepos(options: CLIOptions & { view?: ViewMode }): Promise { const { config, filter, json, verbose, view = "github" } = options; // Check for GitHub token - auto-login if not set const token = await ensureAuthenticated(); if (!token) { console.log(formatError("GitHub authentication required.")); console.log(formatInfo("Run 'gitforest login' to authenticate.")); process.exit(1); } console.log(formatScanning("Scanning local directories...")); const localProjects = await scanAllDirectories(config); console.log(formatScanning("Fetching GitHub repositories...")); const { unified, error } = await fetchUnifiedRepos(localProjects); if (error) { console.log(formatWarning(error)); } // Apply view filter let result = filterByViewMode(unified, view); // Apply text filter if (filter) { result = filterUnifiedRepos(result, filter); } // Sort result = sortUnifiedRepos(result, config.display.sortBy, config.display.sortDirection); const stats = getUnifiedStats(unified); console.log(formatUnifiedRepoList(result, stats, view, { json, verbose })); } /** * Show unified status (local + GitHub) */ export async function showUnifiedStatus(options: CLIOptions): Promise { const { config, json } = options; // Auto-resolve token from `gh` if env isn't set, matching other GitHub commands. const token = await ensureAuthenticated(true); if (!token) { console.log(formatWarning("GitHub auth not configured - showing local status only")); await showStatus(options); return; } console.log(formatScanning("Scanning local directories...")); const localProjects = await scanAllDirectories(config); console.log(formatScanning("Fetching GitHub repositories...")); const { unified, error } = await fetchUnifiedRepos(localProjects); if (error) { console.log(formatWarning(error)); } const stats = getUnifiedStats(unified); const statusData = { stats, githubOnly: unified.filter((r) => r.source === "github"), localOnly: unified.filter((r) => r.source === "local"), dirty: unified.filter((r) => r.local?.status?.isDirty), unpushed: unified.filter((r) => r.local?.status?.isAhead), unpulled: unified.filter((r) => r.local?.status?.isBehind), }; if (json) { console.log(formatUnifiedStatusJson(statusData)); return; } console.log(formatUnifiedStatusDisplay(statusData)); } /** * Clone GitHub repositories that aren't local */ export async function cloneGitHubRepos(options: CLIOptions & { repos?: string[]; targetDir?: string; useHTTPS?: boolean; }): Promise { const { config, filter, repos: specificRepos, targetDir, useHTTPS = false } = options; // Check GitHub auth - auto-login if not set const token = await ensureAuthenticated(); if (!token) { console.log(formatError("GitHub authentication required.")); console.log(formatInfo("Run 'gitforest login' to authenticate.")); process.exit(1); } // Determine target directory const cloneDir = targetDir ?? config.directories[0]?.path ?? process.cwd(); console.log(formatScanning("Scanning local directories...")); const localProjects = await scanAllDirectories(config); console.log(formatScanning("Fetching GitHub repositories...")); const { unified, error } = await fetchUnifiedRepos(localProjects); if (error) { console.log(formatWarning(error)); } // Get repos to clone let toClone = unified.filter((r) => r.source === "github"); // Filter by specific repos if provided if (specificRepos && specificRepos.length > 0) { toClone = toClone.filter((r) => specificRepos.some((name) => r.name.toLowerCase() === name.toLowerCase() || r.github?.fullName.toLowerCase() === name.toLowerCase() ) ); } else if (filter) { toClone = filterUnifiedRepos(toClone, filter); } if (toClone.length === 0) { console.log(formatWarning("No repositories to clone.")); return; } console.log(formatInfo(`\nCloning ${toClone.length} repositories to ${cloneDir}...\n`)); let success = 0; for (const repo of toClone) { const result = await cloneGitHubRepo(repo, cloneDir, !useHTTPS); console.log(formatCloneItem(repo.github?.fullName, result.success, result.path, result.error)); if (result.success) success++; } console.log(`\n${formatOperationSummary("Cloned", success, toClone.length)}`); }