create-ignite
Version:
🚀 Universal CLI to scaffold modern web projects - React, Vue, Next.js, Express, and more with zero configuration
446 lines (422 loc) • 11 kB
JavaScript
/**
* User input collection with improved prompts
*/
import prompts from "prompts";
import path from "path";
import os from "os";
import { readFile, writeFile, access } from "fs/promises";
import chalk from "chalk";
import {
FRAMEWORKS,
CSS_FRAMEWORKS,
STATE_MANAGEMENT,
LANGUAGES,
PACKAGE_MANAGERS,
PROJECT_TYPES,
CONFIG_FILE_NAME,
} from "./constants.js";
import { validateConfiguration } from "./validators.js";
const configPath = path.join(os.homedir(), CONFIG_FILE_NAME);
export function onCancel() {
console.log(chalk.yellow("\n[!] Operation cancelled by user"));
process.exit(0);
}
/**
* Load saved configuration
*/
async function loadConfig() {
try {
await access(configPath);
const content = await readFile(configPath, "utf-8");
return JSON.parse(content);
} catch {
return null;
}
}
/**
* Save configuration
*/
async function saveConfig(config) {
try {
await writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
} catch (error) {
console.warn(chalk.yellow("[!] Could not save configuration"));
}
}
/**
* Get user inputs with smart defaults
*/
export async function getUserInputs(projectName) {
const savedConfig = await loadConfig();
let useSavedConfig = false;
// Ask if user wants to use saved config
if (savedConfig) {
console.log(chalk.cyan("\n[C] Previous configuration found:\n"));
console.log(chalk.gray(` Framework: ${chalk.white(savedConfig.framework || "N/A")}`));
console.log(chalk.gray(` Language: ${chalk.white(savedConfig.language || "N/A")}`));
console.log(chalk.gray(` CSS: ${chalk.white(savedConfig.cssFramework || "N/A")}`));
console.log(chalk.gray(` State Mgmt: ${chalk.white(savedConfig.stateManagement || "none")}`));
const { useExisting } = await prompts(
{
type: "select",
name: "useExisting",
message: "How would you like to proceed?",
choices: [
{ title: "Use previous configuration", value: true },
{ title: "Start fresh (new setup)", value: false },
],
initial: 0,
},
{ onCancel }
);
useSavedConfig = useExisting;
if (useSavedConfig) {
return { ...savedConfig, projectName };
}
}
// Determine project type first
const { projectType } = await prompts(
{
type: "select",
name: "projectType",
message: "What type of project do you want to create?",
choices: [
{
title: "Frontend (SPA/SSR)",
description: "React, Vue, Next.js, Nuxt",
value: PROJECT_TYPES.FRONTEND,
},
{
title: "Backend API",
description: "Express, Fastify",
value: PROJECT_TYPES.BACKEND,
},
{
title: "Full-Stack Application",
description: "Complete monorepo with frontend + backend",
value: PROJECT_TYPES.FULLSTACK,
},
],
initial: 0,
},
{ onCancel }
);
let framework, cssFramework, stateManagement, installRouter, installIcons, installAxios, language;
// Frontend-specific prompts
if (projectType === PROJECT_TYPES.FRONTEND) {
const frontendAnswers = await prompts(
[
{
type: "select",
name: "framework",
message: "Which framework do you want to use?",
choices: [
{
title: "React (Vite)",
description: "Fast, modern React with Vite",
value: FRAMEWORKS.REACT,
},
{
title: "Next.js",
description: "The React framework for production",
value: FRAMEWORKS.NEXTJS,
},
{
title: "Vue 3 (Vite)",
description: "Progressive JavaScript framework",
value: FRAMEWORKS.VUE,
},
{
title: "Nuxt 3",
description: "The Intuitive Vue framework",
value: FRAMEWORKS.NUXT,
},
],
initial: 0,
},
{
type: "select",
name: "language",
message: "Which language?",
choices: [
{ title: "TypeScript", value: LANGUAGES.TYPESCRIPT },
{ title: "JavaScript", value: LANGUAGES.JAVASCRIPT },
],
initial: 0,
},
{
type: "select",
name: "cssFramework",
message: "Which CSS framework?",
choices: [
{ title: "Tailwind CSS v4", value: CSS_FRAMEWORKS.TAILWIND },
{ title: "Bootstrap 5", value: CSS_FRAMEWORKS.BOOTSTRAP },
{ title: "Material-UI (MUI)", value: CSS_FRAMEWORKS.MATERIAL_UI },
{ title: "Chakra UI", value: CSS_FRAMEWORKS.CHAKRA_UI },
{ title: "None", value: CSS_FRAMEWORKS.NONE },
],
initial: 0,
},
{
type: "select",
name: "stateManagement",
message: "State management library?",
choices: (prev, values) => {
const isReact = values.framework === FRAMEWORKS.REACT || values.framework === FRAMEWORKS.NEXTJS;
const isVue = values.framework === FRAMEWORKS.VUE || values.framework === FRAMEWORKS.NUXT;
if (isReact) {
return [
{ title: "None", value: STATE_MANAGEMENT.NONE },
{ title: "Redux Toolkit", value: STATE_MANAGEMENT.REDUX },
{ title: "Zustand", value: STATE_MANAGEMENT.ZUSTAND },
{ title: "MobX", value: STATE_MANAGEMENT.MOBX },
];
} else if (isVue) {
return [
{ title: "None", value: STATE_MANAGEMENT.NONE },
{ title: "Pinia (recommended)", value: STATE_MANAGEMENT.PINIA },
{ title: "Vuex", value: STATE_MANAGEMENT.VUEX },
];
}
return [{ title: "None", value: STATE_MANAGEMENT.NONE }];
},
initial: 0,
},
{
type: "toggle",
name: "installRouter",
message: "Install router?",
initial: true,
active: "yes",
inactive: "no",
},
{
type: "toggle",
name: "installIcons",
message: "Install icon library?",
initial: true,
active: "yes",
inactive: "no",
},
{
type: "toggle",
name: "installAxios",
message: "Install Axios (HTTP client)?",
initial: true,
active: "yes",
inactive: "no",
},
],
{ onCancel }
);
framework = frontendAnswers.framework;
cssFramework = frontendAnswers.cssFramework;
stateManagement = frontendAnswers.stateManagement;
installRouter = frontendAnswers.installRouter;
installIcons = frontendAnswers.installIcons;
installAxios = frontendAnswers.installAxios;
}
// Backend-specific prompts
if (projectType === PROJECT_TYPES.BACKEND) {
const backendAnswers = await prompts(
[
{
type: "select",
name: "framework",
message: "Which backend framework?",
choices: [
{
title: "Express",
description: "Fast, unopinionated web framework",
value: FRAMEWORKS.EXPRESS,
},
{
title: "Fastify",
description: "Fast and low overhead web framework",
value: FRAMEWORKS.FASTIFY,
},
],
initial: 0,
},
{
type: "select",
name: "language",
message: "Which language?",
choices: [
{ title: "TypeScript", value: LANGUAGES.TYPESCRIPT },
{ title: "JavaScript", value: LANGUAGES.JAVASCRIPT },
],
initial: 0,
},
{
type: "toggle",
name: "installCors",
message: "Install CORS middleware?",
initial: true,
active: "yes",
inactive: "no",
},
{
type: "toggle",
name: "installDotenv",
message: "Install dotenv (environment variables)?",
initial: true,
active: "yes",
inactive: "no",
},
],
{ onCancel }
);
framework = backendAnswers.framework;
cssFramework = CSS_FRAMEWORKS.NONE;
stateManagement = STATE_MANAGEMENT.NONE;
installRouter = false;
installIcons = false;
installAxios = false;
}
// Full-stack specific prompts
if (projectType === PROJECT_TYPES.FULLSTACK) {
const fullstackAnswers = await prompts(
[
{
type: "select",
name: "language",
message: "Which language?",
choices: [
{ title: "TypeScript", value: LANGUAGES.TYPESCRIPT },
{ title: "JavaScript", value: LANGUAGES.JAVASCRIPT },
],
initial: 0,
},
{
type: "select",
name: "cssFramework",
message: "Which CSS framework for frontend?",
choices: [
{
title: "Tailwind CSS v4",
description: "Utility-first CSS with Vite plugin",
value: CSS_FRAMEWORKS.TAILWIND,
},
{
title: "Bootstrap 5",
description: "Popular CSS framework",
value: CSS_FRAMEWORKS.BOOTSTRAP,
},
{
title: "None",
description: "Vanilla CSS",
value: CSS_FRAMEWORKS.NONE,
},
],
initial: 0,
},
{
type: "select",
name: "stateManagement",
message: "State management for frontend?",
choices: [
{
title: "Redux Toolkit",
description: "Official Redux with toolkit",
value: STATE_MANAGEMENT.REDUX,
},
{
title: "Zustand",
description: "Simple and fast state management",
value: STATE_MANAGEMENT.ZUSTAND,
},
{
title: "None",
description: "Use React hooks only",
value: STATE_MANAGEMENT.NONE,
},
],
initial: 0,
},
],
{ onCancel }
);
framework = FRAMEWORKS.FULLSTACK;
cssFramework = fullstackAnswers.cssFramework;
stateManagement = fullstackAnswers.stateManagement;
language = fullstackAnswers.language;
installRouter = true;
installIcons = true;
installAxios = true;
}
// Common prompts
const commonAnswers = await prompts(
[
{
type: "select",
name: "packageManager",
message: "Which package manager?",
choices: [
{ title: "npm", value: PACKAGE_MANAGERS.NPM },
{ title: "yarn", value: PACKAGE_MANAGERS.YARN },
{ title: "pnpm", value: PACKAGE_MANAGERS.PNPM },
],
initial: 0,
},
{
type: "toggle",
name: "gitInit",
message: "Initialize git repository?",
initial: true,
active: "yes",
inactive: "no",
},
{
type: "toggle",
name: "installESLint",
message: "Install ESLint?",
initial: true,
active: "yes",
inactive: "no",
},
{
type: "toggle",
name: "installPrettier",
message: "Install Prettier?",
initial: true,
active: "yes",
inactive: "no",
},
],
{ onCancel }
);
const config = {
projectName,
projectType,
framework,
language: commonAnswers.language || (await prompts({
type: "select",
name: "language",
message: "Which language?",
choices: [
{ title: "TypeScript", value: LANGUAGES.TYPESCRIPT },
{ title: "JavaScript", value: LANGUAGES.JAVASCRIPT },
],
initial: 0,
}, { onCancel })).language,
cssFramework,
stateManagement,
installRouter,
installIcons,
installAxios,
packageManager: commonAnswers.packageManager,
gitInit: commonAnswers.gitInit,
installESLint: commonAnswers.installESLint,
installPrettier: commonAnswers.installPrettier,
};
// Validate configuration
const validationResult = validateConfiguration(config);
if (validationResult !== true) {
console.error(chalk.red(`\n[X] Configuration error: ${validationResult}`));
process.exit(1);
}
// Save config for future use
await saveConfig(config);
return config;
}