@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
223 lines (222 loc) • 6.76 kB
JavaScript
import { relinka } from "@reliverse/relinka";
import {
selectPrompt,
inputPrompt,
confirmPrompt,
multiselectPrompt
} from "@reliverse/rempts";
import { getUserPkgManager } from "../../../../../utils/dependencies/getUserPkgManager.js";
import { handleDownload } from "../../../../../utils/downloading/handleDownload.js";
import { ensureGithubToken } from "../../../../../utils/instanceGithub.js";
import { askProjectName } from "../../../../../utils/prompts/askProjectName.js";
import { cd } from "../../../../../utils/terminalHelpers.js";
function normalizeGitHubUrl(url) {
return url.trim().replace(
/^https?:\/\/(www\.)?(github|gitlab|bitbucket|sourcehut)\.com\//i,
""
).replace(/^(github|gitlab|bitbucket|sourcehut)\.com\//i, "").replace(/\.git$/i, "");
}
const REPO_OWNERS = {
blefnk: [
"all-in-one-nextjs-template",
"astro-starlight-template",
"create-next-app",
"create-t3-app",
"relivator-nextjs-template",
"versator-nextjs-template"
],
rse: [
"template-browser-extension",
"acme",
"cli",
"pm",
"prompts",
"relico",
"relinka"
],
onwidget: ["astrowind"],
"shadcn-ui": ["taxonomy"],
"47ng": ["nuqs"],
biomejs: ["biome"],
pmndrs: ["zustand"],
unjs: ["template"],
"webpro-nl": ["knip"]
};
function createMenuOptions(repos, owner, config) {
const customRepos = (config.customUserFocusedRepos ?? []).concat(config.customDevsFocusedRepos ?? []).map(normalizeGitHubUrl).filter((repo) => repo.startsWith(`${owner}/`)).map((repo) => repo.split("/")[1]).filter((repo) => repo !== void 0);
const allRepos = config.hideRepoSuggestions && customRepos.length > 0 ? customRepos : [...repos, ...customRepos];
if (!config.multipleRepoCloneMode) {
return [
{
label: "\u{1F4DD} I want to provide a custom repository name",
value: "custom"
},
...allRepos.map(
(repo) => ({
label: repo,
value: repo,
hint: customRepos.includes(repo) ? "custom" : void 0
})
)
];
}
return allRepos.map(
(repo) => ({
label: repo,
value: repo,
hint: customRepos.includes(repo) ? "custom" : void 0
})
);
}
async function promptForRepo({
title,
owner,
options,
config
}) {
if (config.multipleRepoCloneMode) {
const selections = await multiselectPrompt({ title, options });
if (selections.length === 0) {
relinka("error", "Please select at least one repository.");
return promptForRepo({ title, owner, options, config });
}
const isCustomMap = /* @__PURE__ */ new Map();
const repos = selections.map((repo) => {
const fullRepo = `${owner}/${repo}`;
isCustomMap.set(fullRepo, false);
return fullRepo;
});
return { repos, isCustomMap };
} else {
const selection = await selectPrompt({ title, options });
if (selection === "custom") {
const customRepo = await inputPrompt({
title: `Enter a repository name for ${owner}`,
content: `This will be combined as ${owner}/<your-input>`
});
return { repo: `${owner}/${customRepo}`, isCustom: true };
}
return { repo: `${owner}/${selection}`, isCustom: false };
}
}
async function downloadAndSetupRepo(owner, repoFullName, config, memory, isDev, cwd, isCustom) {
let privacy = "public";
if (!REPO_OWNERS[owner]?.includes(repoFullName.split("/")[1])) {
privacy = await selectPrompt({
title: `Is repo ${repoFullName} public or private?`,
options: [
{ label: "Public", value: "public" },
{ label: "Private", value: "private" }
]
});
}
const { packageManager } = await getUserPkgManager();
let shouldInstallDeps = false;
if (!isDev) {
shouldInstallDeps = await confirmPrompt({
title: "Do you want me to install dependencies?",
content: `I can run "${packageManager} install" in the directory of the cloned repo.`
});
}
const projectName = await askProjectName({ repoName: repoFullName });
const gitPreference = await selectPrompt({
title: "How would you like to handle Git history?",
content: `(project: ${projectName} | repo: ${repoFullName})`,
options: [
{
label: "Preserve original Git history",
hint: "keeps the original .git folder",
value: "preserve"
},
{
label: "Start fresh Git history",
hint: "initializes a new .git folder",
value: "fresh"
}
]
});
let githubToken = "";
if (privacy === "private") {
githubToken = await ensureGithubToken(memory, "prompt");
}
const { source, dir } = await handleDownload({
cwd,
isDev,
skipPrompts: false,
projectPath: "",
projectName,
selectedRepo: repoFullName,
githubToken,
preserveGit: gitPreference === "preserve",
config: gitPreference === "fresh" ? config : void 0,
install: shouldInstallDeps,
isCustom,
isTemplateDownload: false,
cache: false
});
relinka("success", `\u{1F389} ${source} was downloaded to ${dir}`);
}
export async function showCloneProjectMenu({
isDev,
cwd,
config,
memory
}) {
if (isDev) {
await cd("tests-runtime");
}
relinka(
"success",
"Please note: This menu only allows cloning repositories.",
"If you want a fully personalized project bootstrapped with a desired template, re-run the CLI and choose the `\u2728 Create a brand new project` option instead."
);
const ownerOptions = [
...Object.keys(REPO_OWNERS).map((owner2) => ({
label: owner2,
value: owner2
})),
{ label: "\u{1F4DD} Enter a custom owner", value: "custom" },
{ label: "\u{1F448} Exit", value: "exit" }
];
const selectedOwner = await selectPrompt({
title: "Select or enter a repository owner",
options: ownerOptions
});
if (selectedOwner === "exit") {
relinka("info", "Exiting without cloning any repository.");
return;
}
const owner = selectedOwner === "custom" ? await inputPrompt({
title: "Enter the GitHub username or organization"
}) : selectedOwner;
const ownerRepos = REPO_OWNERS[owner] || [];
const repoPromptResult = await promptForRepo({
title: `Select repositories from ${owner}`,
owner,
options: createMenuOptions(ownerRepos, owner, config),
config
});
if ("repos" in repoPromptResult) {
for (const repo of repoPromptResult.repos) {
await downloadAndSetupRepo(
owner,
repo,
config,
memory,
isDev,
cwd,
repoPromptResult.isCustomMap.get(repo) || false
);
}
} else {
await downloadAndSetupRepo(
owner,
repoPromptResult.repo,
config,
memory,
isDev,
cwd,
repoPromptResult.isCustom
);
}
}