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

268 lines (267 loc) 9.59 kB
import { getOrCreateRseConfig } from "@reliverse/cfg"; import { detectProjectsWithRseConfig } from "@reliverse/cfg"; import { getProjectContent } from "@reliverse/cfg"; import path from "@reliverse/pathkit"; import { re } from "@reliverse/relico"; import { ensuredir } from "@reliverse/relifso"; import { isDirectoryEmpty } from "@reliverse/relifso"; import { relinka } from "@reliverse/relinka"; import { nextStepsPrompt, selectPrompt } from "@reliverse/rempts"; import { simpleGit } from "simple-git"; import { checkMissingDependencies } from "../../../libs/sdk/add/add-local/core/deps.js"; import { getPromptContent } from "../../../libs/sdk/add/add-local/core/prompts.js"; import { getTemplateUpdateInfo, updateProjectTemplateDate } from "../../../libs/sdk/add/add-local/core/templates.js"; import { promptGitDeploy } from "../../../libs/sdk/init/use-template/cp-modules/git-deploy-prompts/gdp-mod.js"; import { initializeGitRepo } from "../../../libs/sdk/init/use-template/cp-modules/git-deploy-prompts/git.js"; import { createPackageJSON } from "../../../libs/sdk/utils/createPackageJSON.js"; import { createTSConfig } from "../../../libs/sdk/utils/createTSConfig.js"; import { askAppOrLib } from "../../../libs/sdk/utils/prompts/askAppOrLib.js"; import { askInstallDeps } from "../../../libs/sdk/utils/prompts/askInstallDeps.js"; import { askOpenInIDE } from "../../../libs/sdk/utils/prompts/askOpenInIDE.js"; import { askProjectName } from "../../../libs/sdk/utils/prompts/askProjectName.js"; import { shouldInitGit } from "../../../libs/sdk/utils/prompts/shouldInitGit.js"; import { getOrCreateReliverseMemory } from "../../../libs/sdk/utils/reliverseMemory.js"; import { findTsconfigUp } from "../../../libs/sdk/utils/tsconfigHelpers.js"; const NEW_PROJECT_OPTION = "new-project"; const EXIT_OPTION = "exit"; function buildProjectSelectionMenuOptions(cwd, detectedProjects, directoryEmpty) { const baseOptions = detectedProjects.map((detectedProject) => ({ label: `Edit: ${path.relative(cwd, detectedProject.path)}`, value: detectedProject.path, hint: re.dim(detectedProject.path) })); baseOptions.push({ label: "Create new project", value: NEW_PROJECT_OPTION, hint: re.dim("create a new project") }); baseOptions.push({ label: "Exit", value: EXIT_OPTION, hint: re.dim("exits the manual builder") }); return { title: "rse Project Selection", content: directoryEmpty ? `Directory ${cwd} is empty` : "Choose an existing project or create a new one.", options: baseOptions }; } export async function handleProjectSelectionMenu(cwd, isDev) { try { const detectedProjects = await detectProjectsWithRseConfig(cwd, isDev); const directoryEmpty = await isDirectoryEmpty(cwd); const menuData = buildProjectSelectionMenuOptions( cwd, detectedProjects, directoryEmpty ); const selectedOption = await selectPrompt(menuData); if (selectedOption === EXIT_OPTION) { process.exit(0); } if (selectedOption === NEW_PROJECT_OPTION) { const projectName = await askProjectName({}); const projectPath = path.resolve(cwd, projectName); await initMinimalrseProject(projectPath, projectName, isDev); return projectPath; } return selectedOption; } catch (error) { relinka( "error", `An error occurred during project selection: ${error instanceof Error ? error.message : String(error)}` ); process.exit(1); } } export async function initMinimalrseProject(projectPath, projectName, isDev) { try { const projectType = await askAppOrLib(projectName); const isLib = projectType === "lib"; const projectFramework = isLib ? "npm-jsr" : "unknown"; await ensuredir(projectPath); await createPackageJSON(projectPath, projectName, isLib); await createTSConfig(projectPath, isLib, isDev); let customTsconfigPath; if (isDev) { const foundTsconfig = await findTsconfigUp(path.resolve(projectPath)); if (foundTsconfig) { relinka("verbose", `Found parent tsconfig: ${foundTsconfig}`); customTsconfigPath = foundTsconfig; } else { relinka("warn", "No parent-level tsconfig.json found in dev mode."); } } const { config } = await getOrCreateRseConfig({ projectPath, isDev, overrides: { projectFramework }, customTsconfigPath }); if (isDev) { const shouldInit = await shouldInitGit(isDev); if (shouldInit) { const git = simpleGit({ baseDir: projectPath }); await initializeGitRepo(git, false, config, false); } } else { const memory = await getOrCreateReliverseMemory(); await promptGitDeploy({ isLib: false, projectName, config, projectPath, primaryDomain: "", hasDbPush: false, shouldRunDbPush: false, shouldInstallDeps: false, isDev, memory, cwd: projectPath, maskInput: false, skipPrompts: false, selectedTemplate: "unknown", isTemplateDownload: false, frontendUsername: "" }); } await nextStepsPrompt({ title: `Created new project "${projectName}" with minimal rse config.`, content: [ "It's recommended to:", "1. Edit the generated config files as needed.", "2. Rerun the manual builder to apply changes.", "p.s. Fast way to open manual builder:", isDev ? "`bun dev:init` or `bun dev:add` (the same thing)" : "`rse init` or `rse add` (the same thing)" ] }); try { await askOpenInIDE({ projectPath, isDev }); } catch (error) { relinka( "warn", `Could not open project in IDE: ${error instanceof Error ? error.message : String(error)}` ); } } catch (error) { relinka( "error", `Failed to initialize project: ${error instanceof Error ? error.message : String(error)}` ); process.exit(1); } } export async function showExistingProjectMenu(cwd, isDev) { try { const { requiredContent, optionalContent } = await getProjectContent(cwd); const { depsMissing } = await checkMissingDependencies( cwd, requiredContent, optionalContent ); const { updateAvailable, updateInfo } = await getTemplateUpdateInfo( cwd, isDev, requiredContent.fileRseConfig ); const menuOptions = buildExistingProjectMenuOptions( depsMissing, updateAvailable, updateInfo ); const promptContent = getPromptContent(depsMissing, updateAvailable); const action = await selectPrompt({ title: "Manual Builder Mode", content: promptContent, options: menuOptions }); if (action === "install-deps") { await askInstallDeps(cwd); } else if (action === "update-template" && updateInfo?.latestDate) { await updateProjectTemplateDate(cwd, updateInfo.latestDate, isDev); relinka("info", "Template date updated. Pull changes if needed."); } else if (action === "edit-settings") { relinka( "info", "Feature not implemented yet. Please edit your rse config file manually." ); } return { areDependenciesMissing: depsMissing }; } catch (error) { relinka( "error", `Error handling existing project: ${error instanceof Error ? error.message : String(error)}` ); process.exit(1); } } function buildExistingProjectMenuOptions(depsMissing, updateAvailable, updateInfo) { const menuOptions = []; if (depsMissing) { menuOptions.push({ label: "\u{1F50C} Install dependencies", value: "install-deps", hint: re.dim("runs npm/yarn/pnpm/bun install") }); } if (updateAvailable && updateInfo) { menuOptions.push({ label: "\u{1F503} Update project template", value: "update-template", hint: re.dim( `Current: ${updateInfo.currentDate.slice(0, 10)}, Latest: ${updateInfo.latestDate?.slice( 0, 10 )}` ) }); } menuOptions.push({ label: "\u{1F4DD} Edit project settings", value: "edit-settings", hint: re.dim("on https://reliverse.org"), disabled: depsMissing }); menuOptions.push({ label: "\u{1F448} Exit", value: EXIT_OPTION }); return menuOptions; } export function determineProjectStatus(requiredContent) { const hasrse = Boolean(requiredContent.fileRseConfig); const hasPackageJson = Boolean(requiredContent.filePackageJson); const isExistingProject = Object.values(requiredContent).every(Boolean); if (!hasrse && hasPackageJson) return "new"; if (isExistingProject) return "existing"; return "incomplete"; } export async function handleNewProject(cwd, isDev) { try { relinka("info", "Setting up rse config for this project..."); await getOrCreateRseConfig({ projectPath: cwd, isDev, overrides: {} }); relinka("success", "rse config created. Please re-run the builder."); return { areDependenciesMissing: false }; } catch (error) { relinka( "error", `Failed to setup new project configuration: ${error instanceof Error ? error.message : String(error)}` ); process.exit(1); } } export async function handleExistingProject(cwd, isDev) { return showExistingProjectMenu(cwd, isDev); } export function handleIncompleteProject() { relinka("info", "Project doesn't meet requirements for manual builder menu."); relinka("info", "Ensure you have a package.json and rse config file."); return { areDependenciesMissing: true }; }