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