diff --git a/index.js b/index.js
index ec5d59f..8ed5a8b 100644
--- a/index.js
+++ b/index.js
@@ -5,6 +5,8 @@ import { run, createFolder, deleteFile } from './lib/utils.js';
import { initializePWA } from './lib/pwa.js';
import { setupCSSFramework } from './lib/css-frameworks.js';
import { createAxiosSetup, createAppComponent, setupRouterMain, createPWAReadme } from './lib/templates.js';
+import { setupTestingFramework } from './lib/testing.js';
+import { setupDevTools } from './lib/dev-tools.js';
(async () => {
// 1. Collect user inputs
@@ -26,6 +28,17 @@ import { createAxiosSetup, createAppComponent, setupRouterMain, createPWAReadme
message: "Do you want to make this a Progressive Web App (PWA)?",
default: false
},
+ {
+ type: "list",
+ name: "testingFramework",
+ message: "Choose a testing framework:",
+ choices: [
+ { name: "None", value: "none" },
+ { name: "Vitest + React Testing Library", value: "vitest" },
+ { name: "Jest + React Testing Library", value: "jest" },
+ { name: "Cypress (E2E)", value: "cypress" }
+ ]
+ },
{
type: "checkbox",
name: "packages",
@@ -36,12 +49,26 @@ import { createAxiosSetup, createAppComponent, setupRouterMain, createPWAReadme
{ name: "React Hook Form", value: "react-hook-form" },
{ name: "Yup", value: "yup" },
{ name: "Formik", value: "formik" },
- { name: "Moment.js", value: "moment" }
+ { name: "Moment.js", value: "moment" },
+ { name: "Zustand (State Management)", value: "zustand" },
+ { name: "TanStack Query", value: "@tanstack/react-query" },
+ { name: "Framer Motion", value: "framer-motion" },
+ { name: "React Helmet (SEO)", value: "react-helmet-async" }
+ ]
+ },
+ {
+ type: "checkbox",
+ name: "devTools",
+ message: "Select development tools:",
+ choices: [
+ { name: "ESLint + Prettier", value: "eslint-prettier" },
+ { name: "Husky (Git Hooks)", value: "husky" },
+ { name: "Commitizen (Conventional Commits)", value: "commitizen" }
]
}
]);
- const { projectName, cssFramework, isPWA, packages } = answers;
+ const { projectName, cssFramework, isPWA, testingFramework, packages, devTools } = answers;
const projectPath = path.join(process.cwd(), projectName);
console.log(`\n๐ Creating ${projectName}${isPWA ? ' with PWA capabilities' : ''}...`);
@@ -49,52 +76,103 @@ import { createAxiosSetup, createAppComponent, setupRouterMain, createPWAReadme
// 2. Create Vite project
run(`npm create vite@latest ${projectName} -- --template react`);
- // 3. Create all necessary folder structure first
- const folders = ["components", "pages", "hooks", "store", "utils", "assets"];
- folders.forEach((folder) => {
- createFolder(path.join(projectPath, "src", folder));
- });
+ // 3. Setup CSS framework
+ setupCSSFramework(cssFramework, projectPath);
+
+ // 4. Setup testing framework
+ if (testingFramework !== "none") {
+ setupTestingFramework(testingFramework, projectPath);
+ }
- // 4. Install packages
+ // 5. Install PWA functionality
+ if (isPWA) {
+ initializePWA(projectPath, projectName);
+ }
+
+ // 6. Install packages with legacy peer deps for compatibility
const defaultPackages = ["react-router-dom"];
const allPackages = [...defaultPackages, ...packages];
if (allPackages.length > 0) {
- run(`npm install ${allPackages.join(" ")}`, projectPath);
+ run(`npm install ${allPackages.join(" ")} --legacy-peer-deps`, projectPath);
}
- // 5. Setup PWA if selected (after folder structure is created)
- if (isPWA) {
- initializePWA(projectPath, projectName);
+ // 7. Setup development tools
+ if (devTools.length > 0) {
+ setupDevTools(devTools, projectPath, testingFramework);
}
- // 6. Setup CSS framework
- setupCSSFramework(cssFramework, projectPath);
+ // 8. Create folder structure
+ const folders = ["components", "pages", "hooks", "store", "utils", "assets"];
+ folders.forEach((folder) => {
+ createFolder(path.join(projectPath, "src", folder));
+ });
- // 7. Setup Axios if selected
+ // 9. Setup Axios if selected
if (packages.includes("axios")) {
createAxiosSetup(projectPath);
}
- // 8. Clean up default boilerplate files
+ // 10. 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
+ // 11. Generate clean templates
createAppComponent(projectPath, projectName, isPWA);
setupRouterMain(projectPath, cssFramework);
- // 10. Create comprehensive README
+ // 12. Create comprehensive README
createPWAReadme(projectPath, projectName, cssFramework, packages, isPWA);
- // 11. Success message
+ // 13. Enhanced success message
console.log("\nโ
Setup complete!");
+ console.log(`\n๐ Your ${projectName} project is ready!`);
+ console.log(`\n๐ Project includes:`);
+
+ if (testingFramework !== "none") {
+ const testingName = testingFramework === "vitest" ? "Vitest" :
+ testingFramework === "jest" ? "Jest" : "Cypress";
+ console.log(` โข ${testingName} testing setup`);
+ }
+
+ if (devTools.includes("eslint-prettier")) {
+ console.log(` โข ESLint + Prettier configuration`);
+ }
+
+ if (devTools.includes("husky")) {
+ console.log(` โข Husky git hooks`);
+ }
+
+ if (devTools.includes("commitizen")) {
+ console.log(` โข Commitizen for conventional commits`);
+ }
+
+ if (packages.length > 0) {
+ console.log(` โข Additional packages: ${packages.join(", ")}`);
+ }
+
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(" โข 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(`\n๐ Next steps:`);
+ console.log(` cd ${projectName}`);
+ console.log(` npm install`);
+ console.log(` npm run dev`);
+
+ if (testingFramework === "vitest") {
+ console.log(` npm test (run tests)`);
+ } else if (testingFramework === "jest") {
+ console.log(` npm test (run tests)`);
+ } else if (testingFramework === "cypress") {
+ console.log(` npm run test:e2e (run E2E tests)`);
+ }
+
+ if (devTools.includes("eslint-prettier")) {
+ console.log(` npm run lint (check code quality)`);
}
- 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:4173 and test install/offline features`);
diff --git a/lib/dev-tools.js b/lib/dev-tools.js
new file mode 100644
index 0000000..d26fb1d
--- /dev/null
+++ b/lib/dev-tools.js
@@ -0,0 +1,143 @@
+import { run, writeFile, readFile } from './utils.js';
+import path from 'path';
+import fs from 'fs';
+
+export const setupESLintPrettier = (projectPath) => {
+ run(`npm install -D eslint @eslint/js eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-refresh prettier eslint-config-prettier eslint-plugin-prettier`, projectPath);
+
+ // Create ESLint config
+ const eslintConfig = `import js from '@eslint/js'
+import react from 'eslint-plugin-react'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+
+export default [
+ { ignores: ['dist'] },
+ {
+ files: ['**/*.{js,jsx}'],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ parserOptions: {
+ ecmaVersion: 'latest',
+ ecmaFeatures: { jsx: true },
+ sourceType: 'module',
+ },
+ },
+ settings: { react: { version: '18.3' } },
+ plugins: {
+ react,
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...js.configs.recommended.rules,
+ ...react.configs.recommended.rules,
+ ...react.configs['jsx-runtime'].rules,
+ ...reactHooks.configs.recommended.rules,
+ 'react/jsx-no-target-blank': 'off',
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+ },
+]`;
+ writeFile(path.join(projectPath, "eslint.config.js"), eslintConfig);
+
+ // Create Prettier config
+ const prettierConfig = `{
+ "semi": true,
+ "trailingComma": "es5",
+ "singleQuote": true,
+ "printWidth": 80,
+ "tabWidth": 2,
+ "useTabs": false
+}`;
+ writeFile(path.join(projectPath, ".prettierrc"), prettierConfig);
+
+ // Create .prettierignore
+ const prettierIgnore = `dist
+node_modules
+*.log
+.DS_Store`;
+ writeFile(path.join(projectPath, ".prettierignore"), prettierIgnore);
+};
+
+export const setupHusky = (projectPath) => {
+ run(`npm install -D husky lint-staged`, projectPath);
+ run(`npx husky install`, projectPath);
+ run(`npx husky add .husky/pre-commit "npx lint-staged"`, projectPath);
+
+ // Create lint-staged config in package.json
+ const packageJsonPath = path.join(projectPath, "package.json");
+ let packageJson = JSON.parse(readFile(packageJsonPath));
+ packageJson["lint-staged"] = {
+ "*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
+ "*.{css,scss,md}": ["prettier --write"]
+ };
+ writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
+};
+
+export const setupCommitizen = (projectPath) => {
+ run(`npm install -D commitizen cz-conventional-changelog`, projectPath);
+
+ const packageJsonPath = path.join(projectPath, "package.json");
+ let packageJson = JSON.parse(readFile(packageJsonPath));
+ packageJson.config = {
+ commitizen: {
+ path: "cz-conventional-changelog"
+ }
+ };
+ writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
+};
+
+export const updatePackageScripts = (projectPath, testingFramework, devTools) => {
+ const packageJsonPath = path.join(projectPath, "package.json");
+ let packageJson = JSON.parse(readFile(packageJsonPath));
+
+ // Add testing scripts based on framework chosen
+ if (testingFramework === "vitest") {
+ packageJson.scripts.test = "vitest";
+ packageJson.scripts["test:ui"] = "vitest --ui";
+ packageJson.scripts["test:coverage"] = "vitest --coverage";
+ } else if (testingFramework === "jest") {
+ packageJson.scripts.test = "jest";
+ packageJson.scripts["test:watch"] = "jest --watch";
+ packageJson.scripts["test:coverage"] = "jest --coverage";
+ } else if (testingFramework === "cypress") {
+ packageJson.scripts["test:e2e"] = "cypress open";
+ packageJson.scripts["test:e2e:headless"] = "cypress run";
+ }
+
+ // Add linting scripts if ESLint is chosen
+ if (devTools.includes("eslint-prettier")) {
+ packageJson.scripts.lint = "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0";
+ packageJson.scripts["lint:fix"] = "eslint . --ext js,jsx --fix";
+ packageJson.scripts.format = 'prettier --write "src/**/*.{js,jsx,css,md}"';
+ }
+
+ // Add commit script if commitizen is chosen
+ if (devTools.includes("commitizen")) {
+ packageJson.scripts.commit = "cz";
+ }
+
+ writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
+};
+
+export const setupDevTools = (devTools, projectPath, testingFramework) => {
+ if (devTools.includes("eslint-prettier")) {
+ setupESLintPrettier(projectPath);
+ }
+
+ if (devTools.includes("husky")) {
+ setupHusky(projectPath);
+ }
+
+ if (devTools.includes("commitizen")) {
+ setupCommitizen(projectPath);
+ }
+
+ // Update package.json scripts
+ updatePackageScripts(projectPath, testingFramework, devTools);
+};
diff --git a/lib/testing.js b/lib/testing.js
new file mode 100644
index 0000000..3cbdb68
--- /dev/null
+++ b/lib/testing.js
@@ -0,0 +1,95 @@
+import { run, writeFile, createFolder } from './utils.js';
+import path from 'path';
+import fs from 'fs';
+
+export const setupVitest = (projectPath) => {
+ run(`npm install -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event`, projectPath);
+
+ // Update vite.config.js for Vitest
+ const viteConfigPath = path.join(projectPath, "vite.config.js");
+ let viteConfig = fs.readFileSync(viteConfigPath, "utf-8");
+
+ // Add test configuration
+ const testConfig = `
+///
+`;
+ viteConfig = testConfig + viteConfig;
+ viteConfig = viteConfig.replace(
+ /export default defineConfig\(\{/,
+ `export default defineConfig({
+ test: {
+ globals: true,
+ environment: 'jsdom',
+ setupFiles: './src/test/setup.ts',
+ },`
+ );
+ writeFile(viteConfigPath, viteConfig);
+
+ // Create test setup file
+ createFolder(path.join(projectPath, "src", "test"));
+ const setupContent = `import '@testing-library/jest-dom';`;
+ writeFile(path.join(projectPath, "src", "test", "setup.ts"), setupContent);
+
+ // Create sample test
+ const testContent = `import { render, screen } from '@testing-library/react';
+import { describe, it, expect } from 'vitest';
+import App from '../App';
+
+describe('App', () => {
+ it('renders welcome message', () => {
+ render();
+ expect(screen.getByText(/Welcome to/i)).toBeInTheDocument();
+ });
+});`;
+ writeFile(path.join(projectPath, "src", "App.test.jsx"), testContent);
+};
+
+export const setupJest = (projectPath) => {
+ run(`npm install -D jest @testing-library/react @testing-library/jest-dom @testing-library/user-event jest-environment-jsdom`, projectPath);
+
+ // Create Jest config
+ const jestConfig = `export default {
+ testEnvironment: 'jsdom',
+ setupFilesAfterEnv: ['/src/test/setup.js'],
+ moduleNameMapping: {
+ '\\\\.(css|less|scss)$': 'identity-obj-proxy',
+ },
+};`;
+ writeFile(path.join(projectPath, "jest.config.js"), jestConfig);
+
+ // Create test setup file
+ createFolder(path.join(projectPath, "src", "test"));
+ const setupContent = `import '@testing-library/jest-dom';`;
+ writeFile(path.join(projectPath, "src", "test", "setup.js"), setupContent);
+};
+
+export const setupCypress = (projectPath) => {
+ run(`npm install -D cypress`, projectPath);
+ run(`npx cypress install`, projectPath);
+
+ // Create cypress config
+ const cypressConfig = `import { defineConfig } from 'cypress'
+
+export default defineConfig({
+ e2e: {
+ baseUrl: 'http://localhost:5173',
+ setupNodeEvents(on, config) {
+ // implement node event listeners here
+ },
+ },
+})`;
+ writeFile(path.join(projectPath, "cypress.config.js"), cypressConfig);
+};
+
+export const setupTestingFramework = (testingFramework, projectPath) => {
+ const testingMap = {
+ "vitest": () => setupVitest(projectPath),
+ "jest": () => setupJest(projectPath),
+ "cypress": () => setupCypress(projectPath)
+ };
+
+ const setupFunction = testingMap[testingFramework];
+ if (setupFunction) {
+ setupFunction();
+ }
+};
diff --git a/package.json b/package.json
index a9d6996..0d1c724 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "quickstart-react",
- "version": "1.1.2",
+ "version": "1.1.3",
"description": "A CLI tool to quickly scaffold a React + Vite project with optional CSS frameworks and useful packages, ready to use out of the box.",
"main": "index.js",
"type": "module",
@@ -23,7 +23,12 @@
"mui",
"javascript",
"frontend",
- "react-app"
+ "react-app",
+ "testing",
+ "vitest",
+ "eslint",
+ "prettier",
+ "development-tools"
],
"author": "harshgupta20",
"license": "MIT",
diff --git a/readme.md b/readme.md
index 4fb9f77..e0a2199 100644
--- a/readme.md
+++ b/readme.md
@@ -5,7 +5,9 @@
## โจ Features
- **Interactive Setup** โ prompts you for project name, CSS framework, and optional packages
- **CSS Framework Support** โ Tailwind CSS, Bootstrap, or MUI (Material UI)
-- **Optional Packages** โ easily add Axios, React Icons, React Hook Form, Yup, Formik, and Moment.js
+- **Testing Framework Integration** โ Vitest, Jest, or Cypress support with pre-configured setups
+- **Development Tools** โ ESLint + Prettier, Husky git hooks, Commitizen for conventional commits
+- **Optional Packages** โ easily add Axios, React Icons, React Hook Form, Yup, Formik, Moment.js, Zustand, TanStack Query, Framer Motion, and React Helmet
- **Automatic Folder Structure** โ creates `components`, `pages`, `hooks`, `store`, `utils`, `assets` folders
- **Boilerplate Ready** โ replaces default Vite boilerplate with a clean welcome page
- **Axios Setup** โ pre-configured Axios instance if selected
@@ -21,7 +23,9 @@ npx quickstart-react
When you run `npx quickstart-react`, you will be prompted to:
1. **Enter Project Name** โ e.g., `my-app`
2. **Choose CSS Framework** โ Tailwind, Bootstrap, or MUI
-3. **Select Optional Packages** โ choose from a list of commonly used React libraries
+3. **Select Testing Framework** โ Vitest, Jest, Cypress, or None
+4. **Choose Optional Packages** โ choose from a list of commonly used React libraries
+5. **Select Development Tools** โ ESLint + Prettier, Husky, Commitizen
Example run:
```bash
@@ -32,13 +36,18 @@ npx quickstart-react
```
? Enter project name: my-portfolio
? Choose a CSS framework: Tailwind
-? Select optional packages: Axios, React Icons
+? Choose a testing framework: Vitest + React Testing Library
+? Select optional packages: Axios, React Icons, Zustand
+? Select development tools: ESLint + Prettier, Husky
```
This will:
- Create a new Vite + React project in `my-portfolio/`
- Install Tailwind CSS and configure it with Vite
-- Install Axios and React Icons
+- Set up Vitest with React Testing Library for testing
+- Install Axios, React Icons, and Zustand
+- Configure ESLint + Prettier for code quality
+- Set up Husky for git hooks
- Create standard project folders
- Add a clean welcome screen
- Set up an Axios instance at `src/utils/axiosInstance.js`
@@ -87,6 +96,23 @@ You can add these during setup:
- **Yup** โ schema validation
- **Formik** โ form management
- **Moment.js** โ date/time utilities
+- **Zustand** โ lightweight state management
+- **TanStack Query** โ data fetching and caching
+- **Framer Motion** โ animation library
+- **React Helmet** โ document head management for SEO
+
+## ๐งช Testing Framework Support
+Choose from these testing options:
+- **Vitest + React Testing Library** โ fast unit testing with Vite integration
+- **Jest + React Testing Library** โ traditional React testing setup
+- **Cypress** โ end-to-end testing framework
+- **None** โ skip testing setup
+
+## ๐ ๏ธ Development Tools
+Enhance your development workflow with:
+- **ESLint + Prettier** โ code linting and formatting
+- **Husky** โ git hooks for pre-commit checks
+- **Commitizen** โ conventional commit messages
## ๐ Quick Start
```bash