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

431 lines (430 loc) 15.6 kB
import { getProjectContent } from "@reliverse/cfg"; import { re } from "@reliverse/relico"; import { relinka } from "@reliverse/relinka"; import { selectPrompt, inputPrompt, runCmd } from "@reliverse/rempts"; import { getAuthCmd } from "../../../cmds.js"; import { useLanguine } from "../../../../libs/sdk/add/add-local/i18n/languine.js"; import { envArgImpl } from "../../../../libs/sdk/env/env-impl.js"; import { manageDrizzleSchema } from "../../../../libs/sdk/init/mm-deprecated/drizzle/manageDrizzleSchema.js"; import { handleIntegrations } from "../../../../libs/sdk/init/mm-deprecated/editor-impl.js"; import { manageShadcn } from "../../../../libs/sdk/init/mm-deprecated/shadcn/shadcn-mod.js"; import { deployProject } from "../../../../libs/sdk/init/use-template/cp-modules/git-deploy-prompts/deploy.js"; import { createCommit, handleGithubRepo, initGitDir, pushGitCommits } from "../../../../libs/sdk/init/use-template/cp-modules/git-deploy-prompts/git.js"; import { checkGithubRepoOwnership } from "../../../../libs/sdk/init/use-template/cp-modules/git-deploy-prompts/github.js"; import { ensureDbInitialized } from "../../../../libs/sdk/init/use-template/cp-modules/git-deploy-prompts/helpers/handlePkgJsonScripts.js"; import { checkVercelDeployment } from "../../../../libs/sdk/init/use-template/cp-modules/git-deploy-prompts/vercel/vercel-check.js"; import { experimental } from "../../../../libs/sdk/utils/badgeNotifiers.js"; import { convertDatabaseProvider, convertPrismaToDrizzle } from "../../../../libs/sdk/utils/codemods/convertDatabase.js"; import { handleCleanup } from "../../../../libs/sdk/utils/handlers/handleCleanup.js"; import { handleCodemods } from "../../../../libs/sdk/utils/handlers/handleCodemods.js"; import { initGithubSDK } from "../../../../libs/sdk/utils/instanceGithub.js"; import { initVercelSDK } from "../../../../libs/sdk/utils/instanceVercel.js"; import { checkScriptExists } from "../../../../libs/sdk/utils/pkgJsonHelpers.js"; import { askInstallDeps } from "../../../../libs/sdk/utils/prompts/askInstallDeps.js"; import { askUsernameFrontend } from "../../../../libs/sdk/utils/prompts/askUsernameFrontend.js"; export async function handleOpenProjectMenu(projects, isDev, memory, cwd, maskInput, config) { const frontendUsername = await askUsernameFrontend(config, false); if (!frontendUsername) { throw new Error( "Failed to determine your frontend username. Please try again or notify the CLI developers." ); } let selectedProject; if (projects.length === 1) { selectedProject = projects[0]; } else { const projectOptions = projects.map((project) => ({ label: project.name, value: project.path, ...project.needsDepsInstall ? { hint: re.dim("no deps found, <enter> to install") } : project.hasGit && project.gitStatus ? { hint: re.dim( `${project.gitStatus?.uncommittedChanges ?? 0} uncommitted changes, ${project.gitStatus?.unpushedCommits ?? 0} unpushed commits` ) } : {} })); const selectedPath = await selectPrompt({ title: "Select a project to manage", options: [...projectOptions, { label: "Exit", value: "exit" }] }); if (selectedPath === "exit") return; selectedProject = projects.find((p) => p.path === selectedPath); } if (!selectedProject) { relinka("error", "Project not found"); return; } let hasNodeModules = false; if (selectedProject.needsDepsInstall) { const depsStillMissing = await askInstallDeps(selectedProject.path); if (!depsStillMissing) { selectedProject.needsDepsInstall = false; hasNodeModules = true; } } const projectContent = await getProjectContent(selectedProject.path); if (!hasNodeModules) { hasNodeModules = projectContent.optionalContent.dirNodeModules; } const gitStatusInfo = selectedProject.hasGit ? ` (${selectedProject.gitStatus?.uncommittedChanges ?? 0} uncommitted changes, ${selectedProject.gitStatus?.unpushedCommits ?? 0} unpushed commits)` : ""; const depsWarning = hasNodeModules ? "" : "Some addons were disabled because dependencies are not installed."; const action = await selectPrompt({ title: `[@reliverse/relifso] Managing project: ${selectedProject.name}${gitStatusInfo}`, content: depsWarning ? re.bold(depsWarning) : "", options: [ { label: "Generate BetterAuth schema", value: "better-auth", hint: re.dim("better better-auth cli") }, { label: "Git and deploy operations", value: "git-deploy", hint: re.dim("commit and push changes") }, { label: "Translate selected project", value: "languine", hint: re.dim("powerful i18n addon") }, { label: "Compose .env file", value: "env", hint: re.dim("create .env file") }, { label: !hasNodeModules ? re.gray(`Code modifications ${experimental}`) : `Code modifications ${experimental}`, value: "codemods", hint: re.dim("apply code transformations"), disabled: !hasNodeModules }, { label: !hasNodeModules ? re.gray(`Integrations ${experimental}`) : `Integrations ${experimental}`, value: "integrations", hint: re.dim("manage project integrations"), disabled: !hasNodeModules }, { label: !hasNodeModules ? re.gray(`Database operations ${experimental}`) : `Database operations ${experimental}`, value: "convert-db", hint: re.dim("convert between database types"), disabled: !hasNodeModules }, { label: !hasNodeModules ? re.gray(`Add shadcn/ui components ${experimental}`) : `Add shadcn/ui components ${experimental}`, value: "shadcn", hint: re.dim("manage ui components"), disabled: !hasNodeModules }, { label: !hasNodeModules ? re.gray(`Drizzle schema ${experimental}`) : `Drizzle schema ${experimental}`, value: "drizzle-schema", hint: re.dim("manage database schema"), disabled: !hasNodeModules }, { label: !hasNodeModules ? re.gray(`Cleanup project ${experimental}`) : `Cleanup project ${experimental}`, value: "cleanup", hint: re.dim("clean up project files"), disabled: !hasNodeModules }, { label: "\u{1F448} Exit", value: "exit", hint: re.dim("press ctrl+c at any time") } ] }); if (action === "exit") return; switch (action) { case "better-auth": { relinka( "info", "The following args will be passed to the rse auth command:" ); relinka("log", `--config ${selectedProject.path}/src/lib/auth.ts`); relinka( "log", `--output ${selectedProject.path}/src/db/schema/user/tables.ts` ); await runCmd(await getAuthCmd(), [ `--config ${selectedProject.path}/src/lib/auth.ts --output ${selectedProject.path}/src/db/schema/user/tables.ts` ]); break; } case "git-deploy": { const githubResult = await initGithubSDK( memory, frontendUsername, maskInput ); if (!githubResult) { throw new Error( "Failed to initialize GitHub SDK. Please notify the CLI developers." ); } const [githubToken, githubInstance, githubUsername] = githubResult; const vercelResult = await initVercelSDK(memory, maskInput); if (!vercelResult) { throw new Error( "Failed to initialize Vercel SDK. Please notify the CLI developers." ); } const [vercelToken, vercelInstance] = vercelResult; let showCreateGithubOption = true; let hasGithubRepo = false; const hasDbPush = await checkScriptExists( selectedProject.path, "db:push" ); const shouldRunDbPush = false; const { exists, isOwner } = await checkGithubRepoOwnership( githubInstance, githubUsername, selectedProject.name ); showCreateGithubOption = !exists; hasGithubRepo = exists && isOwner; const gitOptions = [ ...selectedProject.hasGit ? [ { label: "Create commit", value: "commit" }, ...selectedProject.gitStatus?.unpushedCommits && hasGithubRepo ? [ { label: `Push ${selectedProject.gitStatus.unpushedCommits} commits`, value: "push" } ] : [] ] : [{ label: "Initialize Git repository", value: "init" }], ...showCreateGithubOption ? [ { label: "Re/init git and create GitHub repository", value: "github" } ] : [], ...selectedProject.hasGit && hasGithubRepo ? [{ label: "Deploy project", value: "deploy" }] : [], { label: "\u{1F448} Exit", value: "exit" } ]; const gitAction = await selectPrompt({ title: "Git and Deploy Operations", options: gitOptions }); if (gitAction === "exit") return; if (gitAction === "init") { relinka("verbose", "[A] initGitDir"); const success = await initGitDir({ cwd, isDev, projectPath: selectedProject.path, projectName: selectedProject.name, allowReInit: true, createCommit: true, config: selectedProject.config, isTemplateDownload: false }); if (success) { relinka("success", "Git repository initialized successfully"); selectedProject.hasGit = true; } } else if (gitAction === "commit") { const message = await inputPrompt({ title: "Enter commit message" }); if (message) { const success = await createCommit({ cwd, isDev, projectPath: selectedProject.path, projectName: selectedProject.name, message, config: selectedProject.config, isTemplateDownload: false }); if (success) { relinka("success", "Commit created successfully"); if (selectedProject.gitStatus) { selectedProject.gitStatus.unpushedCommits = (selectedProject.gitStatus.unpushedCommits || 0) + 1; selectedProject.gitStatus.uncommittedChanges = 0; } } } } else if (gitAction === "push") { const success = await pushGitCommits({ cwd, isDev, projectName: selectedProject.name, projectPath: selectedProject.path }); if (success) { relinka("success", "Commits pushed successfully"); if (selectedProject.gitStatus) { selectedProject.gitStatus.unpushedCommits = 0; } } } else if (gitAction === "github") { const success = await handleGithubRepo({ skipPrompts: false, cwd, isDev, memory, config, projectName: selectedProject.name, projectPath: selectedProject.path, maskInput, githubUsername, selectedTemplate: "blefnk/relivator-nextjs-template", isTemplateDownload: false, githubInstance, githubToken }); if (success) { relinka("success", "GitHub repository created successfully"); } } else if (gitAction === "deploy") { const dbStatus = await ensureDbInitialized( hasDbPush, shouldRunDbPush, !hasNodeModules, selectedProject.path ); if (dbStatus === "cancel") { relinka("info", "Deployment cancelled."); return; } const isDeployed = await checkVercelDeployment( selectedProject.name, githubUsername, githubToken, githubInstance ); if (isDeployed) { relinka( "success", "Project already has Vercel deployments configured on GitHub.", "New deployments are automatically triggered on new commits." ); return; } relinka( "info", "No existing deployment found. Initializing new deployment..." ); const { deployService } = await deployProject( githubInstance, vercelInstance, vercelToken, githubToken, false, selectedProject.name, selectedProject.config, selectedProject.path, "", memory, "update", githubUsername ); if (deployService !== "none") { relinka( "success", `Project deployed successfully to ${deployService.charAt(0).toUpperCase() + deployService.slice(1)}` ); } } break; } case "languine": { await useLanguine(selectedProject.path); break; } case "codemods": { await handleCodemods(selectedProject.config, selectedProject.path); break; } case "integrations": { await handleIntegrations(selectedProject.path, isDev); break; } case "convert-db": { const conversionType = await selectPrompt({ title: "What kind of conversion would you like to perform?", options: [ { label: "Convert from Prisma to Drizzle", value: "prisma-to-drizzle" }, { label: "Convert database provider", value: "change-provider" } ] }); if (conversionType === "prisma-to-drizzle") { const targetDb = await selectPrompt({ title: "Select target database type:", options: [ { label: "PostgreSQL", value: "postgres" }, { label: "MySQL", value: "mysql" }, { label: "SQLite", value: "sqlite" } ] }); await convertPrismaToDrizzle(selectedProject.path, targetDb); } else if (conversionType === "change-provider") { const fromProvider = await selectPrompt({ title: "Convert from:", options: [ { label: "PostgreSQL", value: "postgres" }, { label: "MySQL", value: "mysql" }, { label: "SQLite", value: "sqlite" } ] }); const toProviderOptions = [ { label: "PostgreSQL", value: "postgres" }, { label: "MySQL", value: "mysql" }, { label: "SQLite", value: "sqlite" } ]; if (fromProvider === "postgres") { toProviderOptions.push({ label: "LibSQL/Turso", value: "libsql" }); } const toProvider = await selectPrompt({ title: "Convert to:", options: toProviderOptions.filter( (opt) => opt.value !== fromProvider ) }); await convertDatabaseProvider( selectedProject.path, fromProvider, toProvider ); } break; } case "shadcn": { await manageShadcn(selectedProject.path); break; } case "drizzle-schema": { await manageDrizzleSchema(selectedProject.path, false); break; } case "cleanup": { await handleCleanup(cwd, selectedProject.path, isDev); break; } case "env": { await envArgImpl(isDev, selectedProject.path); break; } case "updater": { break; } case "i18n": { break; } default: { relinka("error", "Invalid action selected"); break; } } }