diff --git a/index.js b/index.js index 55be215..6edaf9a 100644 --- a/index.js +++ b/index.js @@ -1,170 +1,218 @@ #!/usr/bin/env node -import { input, select, confirm, checkbox } from "@inquirer/prompts" -import { select as selectPro } from 'inquirer-select-pro'; +import { input, select, confirm, checkbox } from "@inquirer/prompts"; +import { select as selectPro } from "inquirer-select-pro"; import path from "path"; -import { run, createFolder, deleteFile } from './lib/utils.js'; -import { initializePWA } from './lib/pwa.js'; -import { setupCSSFramework } from './lib/css-frameworks.js'; -import { createAxiosSetup, createAppComponent, createPWAReadme } from './lib/templates.js'; +import { run, createFolder, deleteFile } from "./lib/utils.js"; +import { initializePWA } from "./lib/pwa.js"; +import { setupCSSFramework } from "./lib/css-frameworks.js"; +import { setupStateManagement } from "./lib/store.js"; +import { createAxiosSetup, createAppComponent, createPWAReadme } from "./lib/templates.js"; import { setupRoutingFramework } from "./lib/router-setup.js"; import { initializeGit } from "./lib/setup-git.js"; const getExtraPackages = async (input) => { - if (!input) return []; //if no input, return empty array + if (!input) return []; - const res = await fetch( - `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(input)}` - ); + const res = await fetch( + `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(input)}` + ); - const data = await res.json(); - if (!data.objects) return []; //if no results, return empty array + const data = await res.json(); + if (!data.objects) return []; - return data.objects.map((pkg) => ({ - name: `${pkg.package.name} \x1b[2m${pkg.package.description || ''}\x1b[0m`, //x1b[2m makes text dim, \x1b[0m resets it] - value: pkg.package.name, - })) + return data.objects.map((pkg) => ({ + name: `${pkg.package.name} \x1b[2m${pkg.package.description || ""}\x1b[0m`, + value: pkg.package.name, + })); }; -const GITHUB_REPO_URL = "https://github.com/harshgupta20/quickstart-react/issues"; +const GITHUB_REPO_URL = + "https://github.com/harshgupta20/quickstart-react/issues"; (async () => { - // 1. Collect user inputs - const projectName = await input({ message: "Enter project name:", required: true }); - const language = await select({ - message: "Choose project language:", - choices: ["TypeScript", "JavaScript"] - }) - const cssFramework = await select({ - message: "Choose a CSS framework:", - choices: ["Tailwind", "Bootstrap (CDN)", "React Bootstrap", "MUI"] - }); - const routingFramework = await select({ - message: "Choose a routing framework:", - choices: ["React Router", "Tanstack Router",] - }) - const isPWA = await confirm({ message: "Do you want to make this a Progressive Web App (PWA)?", default: false }); - - const packages = await checkbox({ - message: "Select optional packages:", - choices: [ - { name: "Axios", value: "axios" }, - { name: "React Icons", value: "react-icons" }, - { name: "React Hook Form", value: "react-hook-form" }, - { name: "Yup", value: "yup" }, - { name: "Formik", value: "formik" }, - { name: "Moment.js", value: "moment" } - ] - }); - - const extraPackages = await selectPro({ - message: 'Search extra packages to add', - multiple: true, - clearInputWhenSelected: true, - pageSize: 10, - options: getExtraPackages, - theme: { - style: { - renderSelectedOptions: (selectedOptions) => { - return selectedOptions.map(option => option.value).join(', '); - } - } - } - }); - - let selectedExtraPackages = []; - if (extraPackages.length > 0) { - selectedExtraPackages = await checkbox({ - message: "These extra packages will be installed:", - choices: extraPackages.map(pkg => ({ - name: pkg, - value: pkg, - checked: true, - })), - }) - } - - const projectPath = path.join(process.cwd(), projectName); - const isTS = language == "TypeScript"; - - console.log(`\nšŸš€ Creating ${projectName}${isPWA ? ' with PWA capabilities' : ''}...`); - - // 2. Create Vite project - run(`npm create vite@latest ${projectName} -- --template ${isTS ? "react-ts" : "react"}`); - - // 3. Create all necessary folder structure first - const folders = ["components", "pages", "hooks", "store", "utils", "assets"]; - - // Create the routes folder (for tanstack router) and the necessary packages for Router setup - const routingConfig = { - "Tanstack Router": { - folders: ["routes"], - packages: ["@tanstack/react-router", "@tanstack/react-router-devtools"], - devPackages: ["@tanstack/router-plugin"] + // 1. Collect user inputs + const projectName = await input({ + message: "Enter project name:", + required: true, + }); + + const language = await select({ + message: "Choose project language:", + choices: ["TypeScript", "JavaScript"], + }); + + const cssFramework = await select({ + message: "Choose a CSS framework:", + choices: ["Tailwind", "Bootstrap (CDN)", "React Bootstrap", "MUI"], + }); + + const routingFramework = await select({ + message: "Choose a routing framework:", + choices: ["React Router", "Tanstack Router"], + }); + + const stateManagement = await select({ + message: "Choose a state management library:", + choices: ["None", "Zustand", "Redux Toolkit", "Jotai"], + }); + + const isPWA = await confirm({ + message: "Do you want to make this a Progressive Web App (PWA)?", + default: false, + }); + + const packages = await checkbox({ + message: "Select optional packages:", + choices: [ + { name: "Axios", value: "axios" }, + { name: "React Icons", value: "react-icons" }, + { name: "React Hook Form", value: "react-hook-form" }, + { name: "Yup", value: "yup" }, + { name: "Formik", value: "formik" }, + { name: "Moment.js", value: "moment" }, + ], + }); + + const extraPackages = await selectPro({ + message: "Search extra packages to add", + multiple: true, + clearInputWhenSelected: true, + pageSize: 10, + options: getExtraPackages, + theme: { + style: { + renderSelectedOptions: (selectedOptions) => { + return selectedOptions.map((option) => option.value).join(", "); }, - "React Router": { - folders: [], - packages: ["react-router"], - devPackages: [] - } - }; - - const config = routingConfig[routingFramework] || { folders: [], packages: [], devPackages: [] }; - folders.push(...config.folders); - const routingPackages = config.packages; - folders.forEach((folder) => { - createFolder(path.join(projectPath, "src", folder)); + }, + }, + }); + + let selectedExtraPackages = []; + if (extraPackages.length > 0) { + selectedExtraPackages = await checkbox({ + message: "These extra packages will be installed:", + choices: extraPackages.map((pkg) => ({ + name: pkg, + value: pkg, + checked: true, + })), }); - - // 4. Install packages - const allPackages = [...routingPackages, ...packages, ...selectedExtraPackages]; - if (allPackages.length > 0) { - run(`npm install ${allPackages.join(" ")}`, projectPath); - if (config.devPackages.length > 0) { - run(`npm i -D ${config.devPackages.join(" ")}`, projectPath); - } + } + + const projectPath = path.join(process.cwd(), projectName); + const isTS = language === "TypeScript"; + + console.log( + `\nšŸš€ Creating ${projectName}${isPWA ? " with PWA capabilities" : ""}...` + ); + + // 2. Create Vite project + run( + `npm create vite@latest ${projectName} -- --template ${ + isTS ? "react-ts" : "react" + }` + ); + + // 3. Create all necessary folder structure + const folders = ["components", "pages", "hooks", "utils", "assets"]; + + // Routing config + const routingConfig = { + "Tanstack Router": { + folders: ["routes"], + packages: [ + "@tanstack/react-router", + "@tanstack/react-router-devtools", + ], + devPackages: ["@tanstack/router-plugin"], + }, + "React Router": { + folders: [], + packages: ["react-router-dom"], + devPackages: [], + }, + }; + + const config = + routingConfig[routingFramework] || { folders: [], packages: [], devPackages: [] }; + folders.push(...config.folders); + const routingPackages = config.packages; + + folders.forEach((folder) => { + createFolder(path.join(projectPath, "src", folder)); + }); + + // 4. Install packages + const defaultPackages = []; + if (stateManagement === "Zustand") defaultPackages.push("zustand"); + if (stateManagement === "Redux Toolkit") defaultPackages.push("@reduxjs/toolkit", "react-redux"); + if (stateManagement === "Jotai") defaultPackages.push("jotai"); + + const allPackages = [ + ...routingPackages, + ...defaultPackages, + ...packages, + ...selectedExtraPackages, + ]; + + if (allPackages.length > 0) { + run(`npm install ${allPackages.join(" ")}`, projectPath); + if (config.devPackages.length > 0) { + run(`npm i -D ${config.devPackages.join(" ")}`, projectPath); } + } + + // 5. Setup PWA if selected + if (isPWA) { + initializePWA(projectPath, projectName, isTS); + } + + // 6. Setup CSS framework + setupCSSFramework(cssFramework, projectPath, isTS); + + // 7. Setup Axios if selected + if (packages.includes("axios")) { + createAxiosSetup(projectPath, isTS); + } + + // 8. Clean up default boilerplate files + deleteFile(path.join(projectPath, "src", "App.css")); + if (cssFramework !== "Tailwind") { + deleteFile(path.join(projectPath, "src", "index.css")); + } + + // 9. Generate clean templates + createAppComponent(projectPath, projectName, isPWA, isTS); + setupRoutingFramework(projectPath, routingFramework, cssFramework, isTS); + + // 10. Setup state management + setupStateManagement(stateManagement, projectPath, isTS); + + // 11. Create comprehensive README + createPWAReadme(projectPath, projectName, cssFramework, packages, isPWA, isTS); + + // 12. Initialize Git repository + initializeGit(projectPath); + + // 13. Success message + console.log("\nāœ… Setup complete!"); + if (isPWA) { + console.log("šŸ“± PWA features enabled - your app can be installed on mobile devices!"); + console.log( + "āš ļø Important: Replace placeholder SVG icons with proper PNG icons for production" + ); + } + console.log(`\nNext steps:\n cd ${projectName}\n npm install\n npm run dev`); - // 5. Setup PWA if selected (after folder structure is created) - if (isPWA) { - initializePWA(projectPath, projectName, isTS); - } - - // 6. Setup CSS framework - setupCSSFramework(cssFramework, projectPath, isTS); - - // 7. Setup Axios if selected - if (packages.includes("axios")) { - createAxiosSetup(projectPath, isTS); - } - - // 8. Clean up default boilerplate files - deleteFile(path.join(projectPath, "src", "App.css")); - if (cssFramework !== "Tailwind") { - deleteFile(path.join(projectPath, "src", "index.css")); - } - - // 9. Generate clean templates - createAppComponent(projectPath, projectName, isPWA, isTS); - setupRoutingFramework(projectPath, routingFramework, cssFramework, isTS); - - // 10. Create comprehensive README - createPWAReadme(projectPath, projectName, cssFramework, packages, isPWA, isTS); - - // 11. Initialize Git repository - initializeGit(projectPath); - - // 12. Success message - console.log("\nāœ… Setup complete!"); - if (isPWA) { - console.log("šŸ“± PWA features enabled - your app can be installed on mobile devices!"); - console.log("āš ļø Important: Replace placeholder SVG icons with proper PNG icons for production"); - } - console.log(`\nNext steps:\n cd ${projectName}\n npm install\n npm run dev`); + if (isPWA) { + console.log( + `\nšŸ“± To test PWA:\n npm run build\n npm run preview\n Open http://localhost:5173 and test install/offline features` + ); + } - if (isPWA) { - console.log(`\nšŸ“± To test PWA:\n npm run build\n npm run preview\n Open http://localhost:5173 and test install/offline features`); - } - - console.log(`\nšŸ™Œ Found a bug or want to improve this project?\nSubmit a PR or open an issue here: ${GITHUB_REPO_URL}\n`); - console.log("\nHappy coding! šŸŽ‰"); + console.log( + `\nšŸ™Œ Found a bug or want to improve this project?\nSubmit a PR or open an issue here: ${GITHUB_REPO_URL}\n` + ); + console.log("\nHappy coding! šŸŽ‰"); })(); diff --git a/lib/store.js b/lib/store.js new file mode 100644 index 0000000..457e9f0 --- /dev/null +++ b/lib/store.js @@ -0,0 +1,52 @@ +import fs from "fs"; +import path from 'path'; +import { createFolder } from './utils.js'; + +export const setupStateManagement = (stateManagement, projectPath) => { + const storePath = path.join(projectPath, "src", "store"); + createFolder(storePath); + + if (stateManagement === "Zustand") { + fs.writeFileSync( + path.join(storePath, "useStore.js"), + `import { create } from 'zustand'; + +export const useStore = create((set) => ({ + count: 0, + increase: () => set((state) => ({ count: state.count + 1 })), +}));` + ); + } + + if (stateManagement === "Redux Toolkit") { + fs.writeFileSync( + path.join(storePath, "store.js"), + `import { configureStore, createSlice } from '@reduxjs/toolkit'; + +const counterSlice = createSlice({ + name: 'counter', + initialState: { value: 0 }, + reducers: { + increment: (state) => { state.value += 1 }, + }, +}); + +export const { increment } = counterSlice.actions; + +export const store = configureStore({ + reducer: { + counter: counterSlice.reducer, + }, +});` + ); + } + + if (stateManagement === "Jotai") { + fs.writeFileSync( + path.join(storePath, "atoms.js"), + `import { atom } from 'jotai'; + +export const countAtom = atom(0);` + ); + } +}