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

216 lines (215 loc) 7.86 kB
import path from "@reliverse/pathkit"; import fs from "@reliverse/relifso"; import { relinka } from "@reliverse/relinka"; import { destr } from "destr"; import { readPackageJSON } from "pkg-types"; import { glob } from "tinyglobby"; import { extractRepoInfo, replaceStringsInFiles } from "./reps-impl.js"; import { CommonPatterns, HardcodedStrings } from "./reps-keys.js"; async function getPackageNames(projectPath) { try { const pkgPath = path.join(projectPath, "package.json"); if (!await fs.pathExists(pkgPath)) return []; const pkg = await readPackageJSON(pkgPath); const allDeps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {}, ...pkg.peerDependencies || {} }; return Object.keys(allDeps); } catch (error) { relinka("warn", "Failed to read package.json:", String(error)); return []; } } async function getImportPaths(projectPath) { const importPaths = /* @__PURE__ */ new Set(); try { const files = await glob("**/*.{js,ts,jsx,tsx}", { cwd: projectPath, ignore: ["node_modules/**", "dist/**", ".next/**", "build/**"] }); for (const file of files) { const content = await fs.readFile(path.join(projectPath, file), "utf-8"); const importMatches = [ ...content.matchAll(/(?:import|from)\s+['"]([^'"]+)['"]/g), ...content.matchAll(/import\(['"]([^'"]+)['"]\)/g) ]; for (const match of importMatches) { const importPath = match[1]; if (importPath && !importPath.startsWith(".")) { importPaths.add(importPath); } } } } catch (error) { relinka("warn", "Failed to gather import paths:", String(error)); } return [...importPaths]; } async function getRepositoryUrl(projectPath) { try { const pkgPath = path.join(projectPath, "package.json"); if (!await fs.pathExists(pkgPath)) return ""; const pkg = await readPackageJSON(pkgPath); if (pkg.repository) { if (typeof pkg.repository === "string") { if (!pkg.repository.includes("://")) { return `https://github.com/${pkg.repository}`; } return pkg.repository; } if (typeof pkg.repository === "object" && pkg.repository.url) { return pkg.repository.url; } } if (pkg.name?.startsWith("@")) { const [scope, name] = pkg.name.slice(1).split("/"); return `https://github.com/${scope}/${name}`; } return ""; } catch (error) { relinka("warn", "Failed to read package.json:", String(error)); return ""; } } function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } function capitalizeWithDashes(str) { return str.split("-").map((word) => capitalize(word)).join(" "); } function createCaseVariations(oldValue, newValue) { return { [oldValue.toLowerCase()]: newValue.toLowerCase(), [capitalize(oldValue)]: capitalize(newValue), [oldValue.toUpperCase()]: newValue.toUpperCase(), [capitalizeWithDashes(oldValue)]: capitalizeWithDashes(newValue) }; } export async function handleReplacements(projectPath, selectedRepo, externalrseth, config, existingRepo, showSuccessMessage, isTemplateDownload) { const term = isTemplateDownload ? "template" : "repo"; relinka("verbose", "Personalizing texts in the initialized files..."); const [packageNames, importPaths] = await Promise.all([ getPackageNames(projectPath), getImportPaths(projectPath) ]); let externalConfig; if (existingRepo && externalrseth && await fs.pathExists(externalrseth)) { try { const externalConfigContent = await fs.readFile(externalrseth, "utf-8"); const parsed = destr(externalConfigContent); if (parsed && typeof parsed === "object") { externalConfig = parsed; relinka( "info", "Found external config from existing repo, will use its values for replacements" ); } } catch (error) { relinka("warn", "Failed to parse external config:", String(error)); } } const { inputRepoAuthor, inputRepoName } = extractRepoInfo(selectedRepo); let templateUrl = await getRepositoryUrl(projectPath); if (!templateUrl) { templateUrl = HardcodedStrings.RelivatorDomain; } const replacementsMap = { ...createCaseVariations(HardcodedStrings.GeneralTemplate, "project"), [HardcodedStrings.RelivatorDomain]: config.primaryDomain, [templateUrl]: config.primaryDomain, [`${inputRepoName}.vercel.app`]: `${config.projectName}.vercel.app`, ...createCaseVariations(inputRepoName, config.projectName), ...createCaseVariations( HardcodedStrings.RelivatorShort, config.projectName ), ...createCaseVariations(inputRepoAuthor, config.frontendUsername), ...createCaseVariations( HardcodedStrings.DefaultAuthor, config.frontendUsername ), [CommonPatterns.githubUrl(inputRepoAuthor, inputRepoName)]: CommonPatterns.githubUrl(config.frontendUsername, config.projectName), [CommonPatterns.githubUrl( HardcodedStrings.DefaultAuthor, HardcodedStrings.RelivatorLower )]: CommonPatterns.githubUrl(config.frontendUsername, config.projectName), [CommonPatterns.packageName(inputRepoName)]: CommonPatterns.packageName( config.projectName ), [CommonPatterns.packageName(HardcodedStrings.RelivatorLower)]: CommonPatterns.packageName(config.projectName), [HardcodedStrings.RelivatorTitle]: config.projectDescription ? `${capitalizeWithDashes(config.projectName)} - ${config.projectDescription}` : `${capitalizeWithDashes(config.projectName)} - A modern web application for your business needs`, [HardcodedStrings.DefaultEmail]: config.frontendUsername.includes("@") ? config.frontendUsername : `${config.frontendUsername}@${config.primaryDomain}` }; if (externalConfig) { if (externalConfig.projectName && externalConfig.projectName !== config.projectName) { replacementsMap[externalConfig.projectName] = config.projectName; replacementsMap[externalConfig.projectName.toLowerCase()] = config.projectName.toLowerCase(); replacementsMap[capitalize(externalConfig.projectName)] = capitalizeWithDashes(config.projectName); } if (externalConfig.projectAuthor && externalConfig.projectAuthor !== config.frontendUsername) { replacementsMap[externalConfig.projectAuthor] = config.frontendUsername; } if (externalConfig.projectDescription) { replacementsMap[externalConfig.projectDescription] = config.projectDescription ?? ""; } } const validReplacements = Object.fromEntries( Object.entries(replacementsMap).filter( ([key, value]) => key && value && key !== value && key.length > 1 ) ); try { await replaceStringsInFiles(projectPath, validReplacements, { verbose: true, fileExtensions: [ ".js", ".ts", ".jsx", ".tsx", ".json", "package.json", ".jsonc", ".md", ".mdx", ".html", ".css", ".scss", ".env", ".env.example", "README.md" ], excludedDirs: [ "node_modules", ".git", "build", ".next", "dist", "coverage", ".cache", ".vercel", ".github" ], stringExclusions: [ "@types/", /^(?:https?:\/\/)?[^\s/$.?#].[^\s]*\.[a-z]{2,}(?:\/[^\s]*)?$/i.source, ...packageNames, ...importPaths ], dryRun: false, skipBinaryFiles: true, maxConcurrency: 10, stopOnError: false }); if (showSuccessMessage) { relinka("success", `Successfully personalized ${term} files!`); } } catch (error) { relinka( "error", `\u274C Failed to personalize ${term} files:`, error instanceof Error ? error.message : String(error) ); } }