UNPKG

create-kutty-react

Version:

CLI tool to create a basic React app

271 lines (232 loc) 7.19 kB
#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const { execSync, spawn } = require('child_process'); /** * Ensures global dependencies are installed and linked locally. */ const installGlobalDependencies = () => { const dependencies = ['react', 'react-dom', 'react-scripts']; const missingDeps = []; dependencies.forEach((dep) => { try { const globalPath = execSync('npm root -g').toString().trim(); const depPath = path.join(globalPath, dep); if (!fs.existsSync(depPath)) { missingDeps.push(dep); } else { const projectNodeModules = path.join(process.cwd(), 'node_modules'); fs.mkdirSync(projectNodeModules, { recursive: true }); const projectDepPath = path.join(projectNodeModules, dep); // Attempt to create symlink; fallback to copying on failure try { if (!fs.existsSync(projectDepPath)) { fs.symlinkSync(depPath, projectDepPath, 'junction'); } } catch (symlinkError) { console.warn(`Symlink failed for ${dep}, falling back to copying...`); copyTemplateFiles(depPath, projectDepPath); } } } catch (err) { console.error(`Error processing dependency ${dep}: ${err.message}`); missingDeps.push(dep); } }); if (missingDeps.length > 0) { console.log( `Installing missing global dependencies: ${missingDeps.join(', ')}` ); execSync(`npm install -g ${missingDeps.join(' ')}`, { stdio: 'inherit' }); } else { console.log('All global dependencies are already installed and linked.'); } }; /** * Recursively copies files from source to destination. */ const copyTemplateFiles = (src, dest) => { if (!fs.existsSync(src)) { throw new Error(`Source path does not exist: ${src}`); } const items = fs.readdirSync(src, { withFileTypes: true }); fs.mkdirSync(dest, { recursive: true }); items.forEach((item) => { const srcPath = path.join(src, item.name); const destPath = path.join(dest, item.name); if (item.isDirectory()) { copyTemplateFiles(srcPath, destPath); } else { fs.copyFileSync(srcPath, destPath); } }); }; /** * Creates a new React project. */ const createProject = (projectName) => { let finalProjectName = projectName; // If no project name provided, default to "kutty-project" if (!finalProjectName || finalProjectName.trim() === '') { finalProjectName = 'kutty-project'; console.log('No project name provided, defaulting to "kutty-project".'); } const projectPath = path.resolve(process.cwd(), finalProjectName); if (!/^[a-zA-Z0-9_-]+$/.test(finalProjectName)) { console.error( 'Error: Invalid project name. Use only letters, numbers, underscores, or hyphens.' ); process.exit(1); } if (fs.existsSync(projectPath)) { console.error('Error: Directory already exists.'); process.exit(1); } fs.mkdirSync(projectPath); // Create public folder and index.html const publicPath = path.join(projectPath, 'public'); fs.mkdirSync(publicPath, { recursive: true }); const htmlContent = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>React App</title> <link rel="stylesheet" href="styles.css" /> </head> <body> <div id="root"></div> <div class="footer">Crafted by Nithya Ganesh</div> </body> </html>`; fs.writeFileSync(path.join(publicPath, 'index.html'), htmlContent, 'utf-8'); // Create src folder and initial files const srcPath = path.join(projectPath, 'src'); fs.mkdirSync(srcPath, { recursive: true }); const appJsContent = ` import React from 'react'; import './App.css'; const App = () => { return ( <div className="App"> <h1>Welcome to React!</h1> <p>Start building your project here.</p> </div> ); }; export default App;`; fs.writeFileSync(path.join(srcPath, 'App.js'), appJsContent, 'utf-8'); const indexJsContent = ` import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import './index.css'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <App /> </React.StrictMode> );`; fs.writeFileSync(path.join(srcPath, 'index.js'), indexJsContent, 'utf-8'); const indexCssContent = ` body { font-family: Arial, sans-serif; margin: 0; padding: 0; box-sizing: border-box; background-color: #000000; color: white; } h1 { color: #007bff; text-align: center; } footer { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); font-size: 1.2rem; color: #999; }`; fs.writeFileSync(path.join(srcPath, 'index.css'), indexCssContent, 'utf-8'); // Create App.css for additional styles const appCssContent = ` .App { text-align: center; }`; fs.writeFileSync(path.join(srcPath, 'App.css'), appCssContent, 'utf-8'); // Create .gitignore const gitignoreContent = ` node_modules build .env`; fs.writeFileSync( path.join(projectPath, '.gitignore'), gitignoreContent, 'utf-8' ); // Create package.json const packageJsonPath = path.join(projectPath, 'package.json'); const packageJson = { name: finalProjectName, version: '1.0.0', main: 'index.js', scripts: { start: 'react-scripts start', build: 'react-scripts build', test: 'react-scripts test', eject: 'react-scripts eject', }, dependencies: { react: '^18.0.0', 'react-dom': '^18.0.0', 'react-scripts': '^5.0.0', }, license: 'ISC', }; fs.writeFileSync( packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf-8' ); console.log('Project structure created successfully!'); // Run `npm install` and `npm start` for the user automatically const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; const install = spawn(npmCommand, ['install'], { cwd: projectPath, stdio: 'inherit', }); install.on('close', (installCode) => { if (installCode === 0) { console.log('Running `npm start` for you...'); const start = spawn(npmCommand, ['start'], { cwd: projectPath, stdio: 'inherit', }); start.on('close', (startCode) => { if (startCode !== 0) { console.error('Error running `npm start`.'); } }); } else { console.error('Error during `npm install`.'); } }); }; /** * Main function. */ const main = () => { const args = process.argv.slice(2); if (args.length < 1) { console.error('Usage: create-react-project <project-name>'); process.exit(1); } console.log('Checking and installing global dependencies if necessary...'); installGlobalDependencies(); const projectName = args[0]; createProject(projectName); }; main();