UNPKG

react-atomic-structure

Version:

A command-line tool to set up a predefined folder structure in a React project using Vite.

272 lines (234 loc) 9.26 kB
import fs from 'fs'; import path from 'path'; import { execSync } from 'child_process'; import inquirer from 'inquirer'; // Import JSON file using 'import' syntax in ES modules import { readFileSync, writeFileSync } from 'fs'; const projectPath = process.cwd(); const packageJsonPath = path.join(projectPath, 'package.json'); const postcssConfigPath = path.join(projectPath, 'postcss.config.js'); const prettierConfigPath = path.join(projectPath, '.prettierrc'); const prettierIgnorePath = path.join(projectPath, '.prettierignore'); const gitIgnorePath = path.join(projectPath, '.gitignore'); const eslintConfigPath = path.join(projectPath, '.eslintrc.json'); // Function to ensure all dependencies are installed async function installDependencies(packageManager) { const dependencies = [ 'eslint', 'postcss', 'autoprefixer', 'husky', 'prettier' ]; console.log(`Installing dependencies with ${packageManager}...`); if (packageManager === 'yarn') { execSync(`yarn add ${dependencies.join(' ')} --dev`, { stdio: 'inherit' }); } else { execSync(`npm install ${dependencies.join(' ')} --save-dev`, { stdio: 'inherit' }); } console.log('Dependencies installed!'); } // Function to add missing configuration to package.json async function addPackageJsonConfig() { // Use fs.readFileSync to read the JSON file const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); // Add lint-staged config if (!packageJson['lint-staged']) { packageJson['lint-staged'] = { "*.js": "eslint --fix", "*.ts": "eslint --fix", "*.tsx": "eslint --fix" }; } // Add husky configuration if it doesn't exist if (!packageJson.scripts) { packageJson.scripts = {}; } if (!packageJson.scripts['prepare']) { packageJson.scripts['prepare'] = "husky install"; } // Write the updated package.json writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); console.log('Updated package.json with lint-staged and husky configurations.'); } // Function to choose package manager async function choosePackageManager() { const answers = await inquirer.prompt([{ type: 'list', name: 'packageManager', message: 'Which package manager are you using?', choices: ['npm', 'yarn'], default: 'npm', }]); return answers.packageManager; } // Function to choose Vite template async function chooseTemplate() { const answers = await inquirer.prompt([{ type: 'list', name: 'template', message: 'Please choose a Vite template:', choices: ['react', 'react-ts', 'vanilla', 'vanilla-ts', 'vue', 'svelte'], default: 'react', }]); return answers.template; } // Function to ask for install settings async function askInstallSettings() { const answers = await inquirer.prompt([{ type: 'confirm', name: 'installSettings', message: 'Do you want to install ESLint, PostCSS, Husky, Autoprefixer, and Prettier?', default: true, }]); return answers.installSettings; } // Function to add "import React from 'react';" to a file if not already present function addReactImportToFile(filePath) { const content = fs.readFileSync(filePath, 'utf8'); if (!/import\s+React\s+from\s+['"]react['"]/.test(content)) { const newContent = `import React from 'react';\n${content}`; fs.writeFileSync(filePath, newContent, 'utf8'); console.log(`Updated: ${filePath}`); } } // Recursively traverse a directory and process .jsx and .tsx files function traverseAndAddReactImport(dir) { fs.readdirSync(dir, { withFileTypes: true }).forEach(entry => { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { traverseAndAddReactImport(fullPath); } else if (entry.isFile() && (fullPath.endsWith('.jsx') || fullPath.endsWith('.tsx'))) { addReactImportToFile(fullPath); } }); } async function setupProject() { const packageManager = await choosePackageManager(); if (!fs.existsSync(packageJsonPath)) { console.log('No package.json found! Initializing a new Vite project...'); const template = await chooseTemplate(); execSync(`npm create vite@latest . -- --template ${template}`, { stdio: 'inherit' }); console.log(`Vite project setup complete with ${template} template.`); } const folders = [ 'src', 'src/assets', 'src/assets/fonts', 'src/assets/icons', 'src/assets/images', 'src/components', 'src/components/atoms', 'src/components/molecules', 'src/components/organisms', 'src/hooks', 'src/models', 'src/pages', 'src/services', 'src/store', 'src/store/api', 'src/store/slices', 'src/templates', 'src/utils', 'src/styles', 'public', 'tests' ]; folders.forEach(folder => { const folderPath = path.join(projectPath, folder); if (!fs.existsSync(folderPath)) { fs.mkdirSync(folderPath, { recursive: true }); console.log(`Created: ${folder}`); } }); console.log('Folder structure successfully set up!'); await installDependencies(packageManager); const installSettings = await askInstallSettings(); if (installSettings) { console.log('Installing ESLint, PostCSS, Husky, Autoprefixer, and Prettier...'); await installDependencies(packageManager); console.log('Initializing ESLint...'); execSync(`npx eslint --init`, { stdio: 'inherit' }); // Update or create the ESLint configuration for React 19 compatibility if (!fs.existsSync(eslintConfigPath)) { const eslintConfig = { "extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:react/jsx-runtime" ], "plugins": [ "react" ], "settings": { "react": { "version": "detect" } }, "rules": { "react/react-in-jsx-scope": "off", "react/jsx-no-target-blank": "warn" } }; fs.writeFileSync(eslintConfigPath, JSON.stringify(eslintConfig, null, 2)); console.log('Created .eslintrc.json with React 19 compatible settings.'); } else { console.log("ESLint configuration (.eslintrc.json) already exists. Please ensure it includes React 19 settings."); } // Add lint-staged and husky hooks addPackageJsonConfig(); // Initialize Husky and install hooks if (!fs.existsSync(path.join(projectPath, '.git'))) { console.log('Git is not initialized. Initializing now...'); execSync('git init', { stdio: 'inherit' }); } execSync(`npx husky-init && ${packageManager} install`, { stdio: 'inherit' }); // Set up pre-commit hook to run lint-staged using the chosen package manager const lintCommand = packageManager === 'yarn' ? 'yarn lint-staged' : 'npm run lint-staged'; execSync(`npx husky add .husky/pre-commit "${lintCommand}"`, { stdio: 'inherit' }); // Add a pre-push hook to run build using the chosen package manager const buildCommand = packageManager === 'yarn' ? 'yarn build' : 'npm run build'; execSync(`npx husky add .husky/pre-push "${buildCommand}"`, { stdio: 'inherit' }); console.log('Husky hooks (pre-commit and pre-push) set up complete!'); // Setup PostCSS and Prettier console.log('Setting up PostCSS and Autoprefixer...'); if (!fs.existsSync(postcssConfigPath)) { fs.writeFileSync(postcssConfigPath, `export default {\n plugins: {\n autoprefixer: {}\n }\n};`); console.log('Created postcss.config.js for Autoprefixer.'); } console.log('Creating Prettier configuration...'); if (!fs.existsSync(prettierConfigPath)) { fs.writeFileSync(prettierConfigPath, JSON.stringify({ singleQuote: true, trailingComma: 'all', tabWidth: 3, semi: false, }, null, 2)); console.log('Created .prettierrc for Prettier.'); } console.log('Creating Prettier ignore configuration...'); if (!fs.existsSync(prettierIgnorePath)) { fs.writeFileSync(prettierIgnorePath, `node_modules dist package-lock.json yarn.lock .idea`); console.log('Created .prettierignore file.'); } console.log('Creating .gitignore configuration...'); if (!fs.existsSync(gitIgnorePath)) { fs.writeFileSync(gitIgnorePath, `# Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* # Build directories node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw?`); console.log('Created .gitignore file.'); } console.log('Configuration complete!'); // Finally, traverse the "src" folder and add React import where missing const srcPath = path.join(projectPath, 'src'); if (fs.existsSync(srcPath)) { console.log("Adding 'import React from \"react\";' to all .jsx and .tsx files in the src folder..."); traverseAndAddReactImport(srcPath); console.log("React import added to all applicable files."); } } } setupProject();