UNPKG

@agametis/create-app-for-fm

Version:

Tool for selecting a starter project for FileMaker applications

297 lines (273 loc) 9.13 kB
import fs from "fs-extra"; import path from "path"; import prompts from "prompts"; import {GITHUB_CONFIG} from "../config.js"; import {t} from "../i18n.js"; import { cloneRepository, initializePackageManager, installDependencies, cleanupAndExit, cancelOnce, } from "../utils.js"; export async function createProject() { console.log(t("create.title")); // Start interactive prompts (sequential to avoid rendering next prompt after cancel) const responses = {}; try { // 1) Where to create the project const locAns = await prompts( { type: "select", name: "location", message: t("create.locationChoice"), choices: [ { title: t("create.newDirectory"), value: "new" }, { title: t("create.currentDirectory"), value: "current" }, ], initial: 0, }, { onCancel: () => { cancelOnce(1); return true; }, } ); if (!locAns || !locAns.location) { cancelOnce(1); return; } responses.location = locAns.location; // 2) Ask for project name only when creating a new directory if (responses.location === "new") { const nameAns = await prompts( { type: "text", name: "projectName", message: t("create.projectName"), initial: t("create.initialProjectName"), }, { onCancel: () => { cancelOnce(1); return true; }, } ); if (!nameAns || !nameAns.projectName) { cancelOnce(1); return; } responses.projectName = nameAns.projectName; } // 3) Select template const tmplAns = await prompts( { type: "select", name: "template", message: t("common.selectTemplate"), choices: GITHUB_CONFIG.repositories.map((repo) => ({ title: repo.name, value: `${GITHUB_CONFIG.baseUrl}${repo.path}`, })), initial: 0, }, { onCancel: () => { cancelOnce(1); return true; }, } ); if (!tmplAns || !tmplAns.template) { cancelOnce(1); return; } responses.template = tmplAns.template; } catch (e) { if (e instanceof Error && e.message === "canceled") { cancelOnce(1); return; } throw e; } // Determine project directory let projectDir; if (responses.location === "current") { projectDir = process.cwd(); console.log(`${t("create.setupInCurrentDir")}${projectDir}`); } else { projectDir = path.join(process.cwd(), responses.projectName); console.log(`${t("create.setupInNewDir")}${responses.projectName}`); // Create project directory if it doesn't exist if (!fs.existsSync(projectDir)) { fs.mkdirSync(projectDir); } } // Always use npm as package manager const packageManager = "npm"; console.log(`${t("common.selectedPackageManager")}${packageManager}`); console.log(`${t("create.selectedTemplate")}${responses.template}`); // Skip initialization - we'll check if we need it after cloning the template console.log(t("common.preparingTemplate")); // Clone template from GitHub repo try { // Check if directory is not empty (for current directory option) if (fs.existsSync(projectDir)) { const files = fs.readdirSync(projectDir); if (files.length > 0 && responses.location === "new") { // Only package.json should be there from the init if (files.length > 1 || !files.includes("package.json")) { console.error(t("create.directoryExists")); console.log(t("create.chooseAnotherName")); cleanupAndExit(1); return; // abort flow } } else if (files.length > 0 && responses.location === "current") { // For current directory, ask for confirmation before proceeding try { const { confirmOverwrite } = await prompts({ type: "confirm", name: "confirmOverwrite", message: t("common.directoryNotEmpty"), initial: false, }, { onCancel: () => { cancelOnce(1); return true; }, }); if (!confirmOverwrite) { console.log(t("common.operationCancelled")); cleanupAndExit(1); return; // user chose not to overwrite } } catch (e) { if (e instanceof Error && e.message === "canceled") { cancelOnce(1); return; // cancelled during confirm } throw e; } } } try { // Clone the template repository silently using helper function const tempCloneDir = path.join(process.cwd(), "temp-clone"); const cloneSuccess = await cloneRepository( responses.template, tempCloneDir ); if (!cloneSuccess) { cleanupAndExit(1); return; // stop if clone failed } // Copy all files from the cloned repo to the project directory (excluding .git) fs.copySync(tempCloneDir, projectDir, { filter: (src) => !src.includes(".git"), overwrite: true, }); // Check if the template has a package.json const templatePackageJsonPath = path.join(projectDir, "package.json"); const hasPackageJson = fs.existsSync(templatePackageJsonPath); // Initialize package manager if needed if (!hasPackageJson) { initializePackageManager(packageManager, projectDir); } else { console.log(t("create.packageJsonFound")); // Install dependencies installDependencies(packageManager, projectDir); } // Clean up the temporary clone silently fs.removeSync(tempCloneDir); console.log(t("common.templateCopied")); } catch (cloneError) { // If there's an error during cloning, show a more user-friendly message console.error( `${t("create.downloadError")}${cloneError.message.split("\n")[0]}` ); cleanupAndExit(1); return; // abort after clone error } } catch (error) { console.error(`${t("common.error")}${error.message}`); cleanupAndExit(1); return; // ensure no further execution } console.log(t("create.setupComplete")); // Display next steps information console.log(t("common.nextSteps")); // Different instructions based on whether it's a new directory or current directory if (responses.location === "new") { console.log(`${t("create.changeToDirectory")}${responses.projectName}`); } // Check if package.json exists in the project directory to provide appropriate instructions const packageJsonPath = path.join(projectDir, "package.json"); const nodeModulesPath = path.join(projectDir, "node_modules"); const dependenciesInstalled = fs.existsSync(nodeModulesPath); if (fs.existsSync(packageJsonPath)) { try { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); const stepNumber = responses.location === "new" ? 2 : 1; // Check if there are scripts in package.json if (packageJson.scripts && Object.keys(packageJson.scripts).length > 0) { // Check for common scripts and suggest them if ( packageJson.scripts.dev || packageJson.scripts.start || packageJson.scripts.serve ) { const devCommand = packageJson.scripts.dev ? "dev" : packageJson.scripts.start ? "start" : "serve"; console.log( ` ${stepNumber}. ${t( "common.startDevServer" )}: ${packageManager} ${ packageManager === "npm" ? "run " : "" }${devCommand}` ); } else { // If no dev/start script, show available scripts const scriptKeys = Object.keys(packageJson.scripts); console.log(` ${stepNumber}. ${t("common.availableScripts")}`); scriptKeys.forEach((script) => { console.log( ` - ${packageManager} ${ packageManager === "npm" ? "run " : "" }${script}` ); }); } } else if (!dependenciesInstalled && packageJson) { // No scripts found and dependencies not installed console.log( ` ${stepNumber}${t( "common.installDependencies" )}${packageManager} install` ); } } catch (error) { // Fallback if package.json can't be parsed if (packageJsonPath) { console.log( ` ${responses.location === "new" ? "2" : "1"}${t( "common.installDependencies" )}${packageManager} install` ); } } } else { // No package.json found console.log( ` ${responses.location === "new" ? "2" : "1"}${t("common.checkReadme")}` ); } console.log(t("create.checkReadmeInDirectory")); console.log(t("create.successMessage")); cleanupAndExit(0); return; // explicit end of function }