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