scaffolder-toolkit
Version:
🚀 A universal command-line tool for developers to automate project scaffolding and streamline their workflows.
289 lines (277 loc) • 10.3 kB
JavaScript
import { e as execute, D as DevkitError, t, f as fs, G as GitError, F as FILE_NAMES, l as logger, a as executeCommand } from './main-CB-u5F8M.js';
import path from 'path';
import os from 'os';
import 'commander';
import 'fs';
import 'url';
import 'chalk';
import 'ora';
import 'node:util';
import 'node:child_process';
import 'execa';
import '@inquirer/prompts';
async function installDependencies(options) {
const { projectName, packageManager } = options;
const projectPath = path.join(process.cwd(), projectName);
try {
await execute(packageManager, ["install"], {
cwd: projectPath,
stdio: "inherit",
});
}
catch (error) {
throw new DevkitError(t("errors.scaffolding.install_fail"), {
cause: error,
});
}
}
function getRepoNameFromUrl(url) {
const parts = url.split("/");
let repoName = parts.pop() || "";
if (repoName.endsWith(".git")) {
repoName = repoName.slice(0, -4);
}
return repoName;
}
async function cloneRepo(url, repoPath) {
try {
await fs.ensureDir(repoPath);
await execute("git", ["clone", url, "."], {
cwd: repoPath,
stdio: "ignore",
});
}
catch (error) {
throw new GitError(t("errors.cache.clone_fail"), url, { cause: error });
}
}
async function pullRepo(repoPath) {
try {
await execute("git", ["pull"], { cwd: repoPath, stdio: "ignore" });
}
catch (error) {
throw new GitError(t("errors.cache.refresh_fail"), undefined, {
cause: error,
});
}
}
async function isRepoFresh(repoPath, strategy) {
if (strategy === "never-refresh") {
return true;
}
if (strategy === "always-refresh") {
return false;
}
try {
const stat = await fs.stat(path.join(repoPath, ".git/FETCH_HEAD"));
const oneDayInMs = 24 * 60 * 60 * 1000;
return Date.now() - stat.mtime.getTime() < oneDayInMs;
}
catch {
return false;
}
}
async function doesRepoExist(repoPath) {
try {
await fs.stat(repoPath);
return true;
}
catch {
return false;
}
}
async function updateJavascriptProjectName(projectPath, newProjectName) {
const packageJsonPath = path.join(projectPath, FILE_NAMES.packageJson);
if (!fs.existsSync(packageJsonPath)) {
logger.error(t("errors.system.package_file_not_found"), "TEMPL");
return;
}
try {
const packageJson = await fs.readJson(packageJsonPath);
await fs.writeJson(packageJsonPath, {
...packageJson,
name: newProjectName,
});
}
catch (error) {
const errorMessage = t("errors.system.package_name_update_fail");
if (error instanceof Error) {
logger.error(`${errorMessage}: ${error.message}`, "TEMPL");
}
else {
logger.error(errorMessage, "TEMPL");
}
if (error instanceof Error && error.stack) {
logger.dimmed(error.stack);
}
}
}
function getFilesToFilter(language) {
const commonFiles = Object.values(FILE_NAMES.common);
const languageSpecificFiles = FILE_NAMES[language].lockFiles;
return [...commonFiles, ...languageSpecificFiles];
}
async function copyJavascriptTemplate(repoPath, destination) {
const filesToFilter = getFilesToFilter("javascript");
await fs.copy(repoPath, destination, {
filter: (src) => {
const isFiltered = filesToFilter.some((file) => src.includes(file));
return !isFiltered;
},
});
}
const CACHE_DIR = path.join(os.homedir(), ".devkit", "cache");
async function getTemplateFromCache(options) {
const { url, projectName, spinner, strategy } = options;
const destination = path.join(process.cwd(), projectName);
try {
const repoName = getRepoNameFromUrl(url);
const repoPath = path.join(CACHE_DIR, repoName);
spinner.text = logger.colors.cyan(logger.colors.bold(`Checking cache for: ${repoName}...`));
spinner.start();
const repoExists = await doesRepoExist(repoPath);
if (!repoExists) {
spinner.text = logger.colors.cyan(logger.colors.italic(t("messages.status.cache_clone_start", { url })));
await cloneRepo(url, repoPath);
spinner.succeed(logger.colors.green(logger.colors.bold(t("messages.success.template_added"))));
}
else {
const fresh = await isRepoFresh(repoPath, strategy);
if (!fresh) {
spinner.text = logger.colors.cyan(t("messages.status.cache_refresh_start"));
await pullRepo(repoPath);
spinner.succeed(logger.colors.green(t("messages.success.template_updated")));
}
else {
spinner.info(logger.colors.yellow(t("messages.status.cache_use_info", { repoName })));
}
}
spinner.text = logger.colors.cyan(t("messages.status.cache_copy_start"));
await copyJavascriptTemplate(repoPath, destination);
await updateJavascriptProjectName(destination, projectName);
spinner.succeed(logger.colors.green(logger.colors.bold(t("messages.success.new_project"))));
}
catch (error) {
spinner.fail(logger.colors.red(t("errors.cache.copy_fail")));
const message = t("errors.cache.copy_fail");
if (error instanceof Error) {
logger.error(`${message}: ${error.message}`, "CACHE");
}
else {
logger.error(message, "CACHE");
}
throw error;
}
}
async function runCliCommand(options) {
const { command, projectName, packageManager } = options;
const finalCommand = command.replace("{pm}", packageManager);
try {
if (!finalCommand.trim()) {
throw new DevkitError(t("errors.validation.invalid_command", { command: finalCommand }));
}
await executeCommand(`${finalCommand} ${projectName}`, {
stdio: "inherit",
});
}
catch (error) {
const cause = error.stderr || error.message;
throw new DevkitError(t("errors.scaffolding.run_fail"), { cause });
}
}
async function copyLocalTemplate(options) {
const { sourcePath, projectName } = options;
const projectPath = path.join(process.cwd(), projectName);
try {
let finalSourcePath = sourcePath;
if (finalSourcePath.startsWith("file://")) {
finalSourcePath = finalSourcePath.substring(7);
}
if (!path.isAbsolute(finalSourcePath)) {
finalSourcePath = path.join(process.cwd(), finalSourcePath);
}
await copyJavascriptTemplate(finalSourcePath, projectPath);
await updateJavascriptProjectName(projectPath, projectName);
}
catch (error) {
throw new DevkitError(t("errors.scaffolding.copy_fail"), { cause: error });
}
}
async function scaffoldTemplate(projectName, templateConfig, packageManager, cacheStrategy, spinner) {
const { location } = templateConfig;
if (location.includes("{pm}")) {
spinner.text = logger.colors.cyan(logger.colors.bold(t("messages.scaffolding.run_start", {
command: location,
})));
spinner.stop();
await runCliCommand({
command: location,
projectName,
packageManager});
return { isOfficialCli: true, projectDirCreated: false };
}
else if (location.startsWith("http") || location.startsWith("git@")) {
await getTemplateFromCache({
url: location,
projectName,
spinner,
strategy: cacheStrategy,
});
return { isOfficialCli: false, projectDirCreated: true };
}
else {
spinner.text = logger.colors.cyan(t("messages.scaffolding.copy_start"));
spinner.start();
await copyLocalTemplate({
sourcePath: location,
projectName});
spinner.succeed(logger.colors.green(t("messages.scaffolding.copy_success")));
return { isOfficialCli: false, projectDirCreated: true };
}
}
function logSuccessMessages(projectName) {
logger.log(logger.colors.green(logger.colors.bold(t("messages.success.scaffolding_complete"))));
logger.log(logger.colors.white(logger.colors.bold(logger.colors.italic(t("messages.success.next_steps")))));
logger.log(logger.colors.green(logger.colors.bold(` cd ${projectName}\n git init && git add -A && git commit -m "Initial commit"\n`)));
}
async function scaffoldProject(options) {
const { projectName, templateConfig, packageManager, cacheStrategy } = options;
const spinner = logger.spinner(t("messages.status.scaffolding_project"));
let projectDirCreated = false;
let isOfficialCli = false;
try {
spinner.start();
const result = await scaffoldTemplate(projectName, templateConfig, packageManager, cacheStrategy, spinner);
isOfficialCli = result.isOfficialCli;
projectDirCreated = result.projectDirCreated;
if (!isOfficialCli) {
spinner.text = logger.colors.cyan(logger.colors.bold(`${t("messages.scaffolding.install_start", { pm: packageManager })}\n`));
spinner.stop();
await installDependencies({ projectName, packageManager, spinner });
}
if (!isOfficialCli) {
logSuccessMessages(projectName);
}
}
catch (err) {
spinner.fail(logger.colors.red(t("errors.scaffolding.unexpected")));
if (projectDirCreated) {
try {
await fs.remove(projectName);
logger.warning(logger.colors.yellow(t("messages.status.project_removed", { project: projectName })));
// oxlint-disable-next-line no-unused-vars
}
catch (cleanupErr) {
logger.error(t("errors.scaffolding.fail", { project: projectName }), "CLEANUP");
}
}
const message = t("errors.scaffolding.unexpected");
if (err instanceof Error) {
logger.error(`${message}: ${err.cause}`, "UNKNOWN");
}
else {
logger.error(message, "UNKNOWN");
}
}
}
export { scaffoldProject };