UNPKG

@reliverse/rse

Version:

@reliverse/rse is your all-in-one companion for bootstrapping and improving any kind of projects (especially web apps built with frameworks like Next.js) — whether you're kicking off something new or upgrading an existing app. It is also a little AI-power

364 lines (363 loc) 10.9 kB
import path from "@reliverse/pathkit"; import { re } from "@reliverse/relico"; import fs from "@reliverse/relifso"; import { relinka } from "@reliverse/relinka"; import { inputPrompt, selectPrompt, deleteLastLine } from "@reliverse/rempts"; import { simpleGit } from "simple-git"; import { cliName } from "../../../../constants.js"; import { getEffectiveDir } from "../../../../utils/getEffectiveDir.js"; import { cd, pwd } from "../../../../utils/terminalHelpers.js"; import { checkGithubRepoOwnership, createGithubRepo } from "./github.js"; import { isDirHasGit } from "./utils-git-github.js"; import { handleExistingRepo } from "./utils-repo-exists.js"; async function validateProjectPath(effectiveDir) { const exists = await fs.pathExists(effectiveDir); if (!exists) { relinka("error", `Project directory does not exist: ${effectiveDir}`); } return exists; } async function removeGitDir(effectiveDir) { const gitDir = path.join(effectiveDir, ".git"); try { await fs.remove(gitDir); relinka("verbose", "Removed existing .git directory"); return true; } catch (error) { relinka( "error", "Failed to remove existing .git directory:", error instanceof Error ? error.message : String(error) ); return false; } } export async function initializeGitRepo(git, alreadyGit, config, isTemplateDownload) { if (isTemplateDownload) { relinka("verbose", "Skipping git initialization for template download"); return; } if (!alreadyGit) { await git.init(); deleteLastLine(); relinka("success", "Git repository initialized"); const branchName = config.repoBranch && config.repoBranch !== "main" ? config.repoBranch : "main"; try { await git.raw(["branch", "-M", branchName]); } catch (error) { relinka( "warn", "Failed to rename default branch:", error instanceof Error ? error.message : String(error) ); } } else { relinka("info", "Using existing git repository"); } } async function createGitCommit(git, effectiveDir, alreadyGit, isTemplateDownload, message) { if (isTemplateDownload) { relinka("verbose", "Skipping commit creation for template download"); return; } const status = await git.status(); if (status.files.length === 0 && !alreadyGit) { relinka("info", "No files to commit. Creating an empty commit"); await fs.writeFile(path.join(effectiveDir, ".gitkeep"), ""); await git.add(".gitkeep"); await git.commit(message ?? `Initial commit by ${cliName}`); relinka("success", "Created empty initial commit"); } else if (status.files.length > 0) { await git.add("."); await git.commit( message ?? (alreadyGit ? `Update by ${cliName}` : `Initial commit by ${cliName}`) ); relinka( "success", alreadyGit ? "Changes committed" : "Initial commit created" ); } else { relinka("info", "No changes to commit in existing repository"); } } export async function initGitDir(params) { if (params.isTemplateDownload) { relinka("verbose", "Skipping git initialization for template download"); return true; } const effectiveDir = getEffectiveDir(params); try { if (!await validateProjectPath(effectiveDir)) { return false; } const alreadyGit = await isDirHasGit( params.cwd, params.isDev, params.projectName, params.projectPath ); if (alreadyGit && params.allowReInit) { deleteLastLine(); relinka("verbose", "Reinitializing existing git repository..."); if (!await removeGitDir(effectiveDir)) return false; const git2 = simpleGit({ baseDir: effectiveDir }); await initializeGitRepo( git2, false, params.config, params.isTemplateDownload ); if (params.createCommit) { await createGitCommit( git2, effectiveDir, false, params.isTemplateDownload ); } return true; } const git = simpleGit({ baseDir: effectiveDir }); await initializeGitRepo( git, alreadyGit, params.config, params.isTemplateDownload ); if (params.createCommit) { await createGitCommit( git, effectiveDir, alreadyGit, params.isTemplateDownload ); } return true; } catch (error) { const alreadyGit = await isDirHasGit( params.cwd, params.isDev, params.projectName, params.projectPath ); relinka( "error", `Failed to ${alreadyGit ? "update" : "initialize"} git: ${error instanceof Error ? error.message : String(error)}` ); relinka( "info", `You can ${alreadyGit ? "commit changes" : "initialize git"} manually: cd ${effectiveDir} ${alreadyGit ? 'git add .\ngit commit -m "Update"' : 'git init\ngit add .\ngit commit -m "Initial commit"'}` ); return false; } } export async function createCommit(params) { if (params.isTemplateDownload) { relinka("verbose", "Skipping commit creation for template download"); return true; } const effectiveDir = getEffectiveDir(params); try { if (!await validateProjectPath(effectiveDir)) return false; const alreadyGit = await isDirHasGit( params.cwd, params.isDev, params.projectName, params.projectPath ); const git = simpleGit({ baseDir: effectiveDir }); await initializeGitRepo( git, alreadyGit, params.config, params.isTemplateDownload ); await createGitCommit( git, effectiveDir, alreadyGit, params.isTemplateDownload, params.message ); return true; } catch (error) { const alreadyGit = await isDirHasGit( params.cwd, params.isDev, params.projectName, params.projectPath ); relinka( "error", `Failed to ${alreadyGit ? "create commit" : "initialize"} git: ${error instanceof Error ? error.message : String(error)}` ); relinka( "info", `You can ${alreadyGit ? "commit changes" : "initialize git"} manually: cd ${effectiveDir} ${alreadyGit ? 'git add .\ngit commit -m "Update"' : 'git init\ngit add .\ngit commit -m "Initial commit"'}` ); return false; } } async function isRepoOwner(githubUsername, repoName, githubToken, githubInstance) { if (!githubToken) { relinka("error", "GitHub token not found in rse's memory"); return false; } try { const { isOwner } = await checkGithubRepoOwnership( githubInstance, githubUsername, repoName ); return isOwner; } catch (error) { relinka( "error", "Error checking repository ownership:", error instanceof Error ? error.message : String(error) ); return false; } } export async function handleGithubRepo(params) { if (params.isTemplateDownload) { relinka( "verbose", "Skipping GitHub repository handling for template download" ); return true; } const effectiveDir = getEffectiveDir(params); if (!params.memory || !params.githubUsername || !params.githubToken) { relinka("error", "Something went wrong. Please notify CLI developers."); return false; } const repoExists = await isRepoOwner( params.githubUsername, params.projectName, params.githubToken, params.githubInstance ); if (!repoExists) { return await createGithubRepo( params.githubInstance, params.projectName, params.githubUsername, effectiveDir, params.isDev, params.cwd, params.config, params.isTemplateDownload ); } else { if (params.isDev) { await cd(effectiveDir); pwd(); } let choice = "commit"; const alreadyExistsDecision = params.config.existingRepoBehavior; if (params.skipPrompts) { switch (alreadyExistsDecision) { case "autoYes": choice = "commit"; break; case "autoYesSkipCommit": choice = "skip"; break; case "autoNo": choice = "new"; break; } } else { choice = await selectPrompt({ title: `Repository ${params.githubUsername}/${params.projectName} already exists and you own it. What would you like to do?`, content: "Note: A commit will be created and pushed only if there are uncommitted changes.", options: [ { value: "commit", label: `${re.greenBright("\u2705 Recommended")} Use existing repository and create+push new commit` }, { value: "skip", label: "Use existing repository, but skip creating a commit" }, { value: "new", label: "Initialize a brand-new GitHub repository with a different name" } ], defaultValue: "commit" }); } if (choice === "commit" || choice === "skip") { await handleExistingRepo(params, choice === "commit", params.isDev); } else if (choice === "new") { const newName = await inputPrompt({ title: "Enter a new repository name:", validate: (value) => { const trimmed = value.trim(); if (!trimmed) return "Repository name is required"; if (!/^[a-zA-Z0-9-_]+$/.test(trimmed)) return "Invalid repository name format"; return true; } }); if (!newName || typeof newName !== "string") { relinka("error", "Invalid repository name provided"); return false; } return await createGithubRepo( params.githubInstance, newName, params.githubUsername, effectiveDir, params.isDev, params.cwd, params.config, params.isTemplateDownload ); } return true; } } export async function pushGitCommits(params) { const effectiveDir = getEffectiveDir(params); try { if (!await isDirHasGit( params.cwd, params.isDev, params.projectName, params.projectPath )) { relinka("error", "Not a git repository. Please initialize git first."); return false; } const git = simpleGit({ baseDir: effectiveDir }); const currentBranch = (await git.branch()).current; if (!currentBranch) { relinka("error", "No current branch found."); return false; } const status = await git.status(); if (status.ahead === 0) { relinka("info", "No commits to push."); return true; } await git.push("origin", currentBranch); relinka( "success", `Pushed ${status.ahead} commit(s) to origin/${currentBranch}` ); return true; } catch (error) { relinka( "error", `Failed to push commits: ${error instanceof Error ? error.message : String(error)}` ); return false; } }