create-kutty-react
Version:
CLI tool to create a basic React app
271 lines (232 loc) • 7.19 kB
JavaScript
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();