reactjs-component-cli
Version:
React component generator CLI
185 lines (162 loc) • 6.16 kB
JavaScript
// This is a CLI tool to generate React components, pages, context, and providers interactively.
import fs from 'fs';
import path from 'path';
import os from 'os';
import { program } from 'commander';
import inquirer from 'inquirer';
// Load config from file
function loadConfig() {
const configPaths = [
path.join(process.cwd(), '.componentclirc.json'),
path.join(os.homedir(), '.componentclirc.json'),
];
for (const configPath of configPaths) {
if (fs.existsSync(configPath)) {
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
}
}
return {};
}
// Generate files
function createFiles({
componentName,
useTypeScript,
styleExtension,
createTest,
useCSSModule,
customPath,
useCurrent,
type
}) {
const ext = useTypeScript ? 'tsx' : 'jsx';
const styleFile = useCSSModule
? `${componentName}.module.${styleExtension}`
: `${componentName}.${styleExtension}`;
const testFile = `${componentName}.test.${ext}`;
const currentDir = process.env.INIT_CWD || process.cwd();
// Determine the folder name based on the type
let folderName;
switch (type.toLowerCase()) {
case 'page':
folderName = 'pages';
break;
case 'component':
default:
folderName = 'components';
break;
}
// Determine base directory
let baseDir;
if (customPath) {
baseDir = path.join(currentDir, customPath, folderName);
} else if (useCurrent) {
baseDir = path.join(currentDir, folderName);
} else if (currentDir.includes(`${path.sep}src${path.sep}`) || currentDir.endsWith(`${path.sep}src`)) {
baseDir = path.join(currentDir, folderName);
} else {
baseDir = path.join(currentDir, 'src', folderName);
}
const targetDir = path.join(baseDir, componentName);
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
// Create component or context/provider file
let importStyle = '';
let componentContent = '';
const isVisualComponent = ['component', 'page'].includes(type.toLowerCase());
if (isVisualComponent) {
importStyle = `import './${styleFile}';\n\n`;
componentContent = `${importStyle}const ${componentName} = () => {\n return (<div className="${componentName}">${componentName} ${type}</div>);\n};\n\nexport default ${componentName};`;
}
fs.writeFileSync(path.join(targetDir, `${componentName}.${ext}`), componentContent);
// Style file (only for visual components)
if (isVisualComponent) {
fs.writeFileSync(path.join(targetDir, styleFile), `.${componentName} {\n\n}`);
}
// Test file (only for visual components)
if (createTest && isVisualComponent) {
const testContent = `import React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport ${componentName} from './${componentName}';\n\ntest('renders ${componentName}', () => {\n render(<${componentName} />);\n expect(screen.getByText('${componentName}')).toBeInTheDocument();\n});`;
fs.writeFileSync(path.join(targetDir, testFile), testContent);
}
console.log(`✅ Created ${type} "${componentName}" in ${path.relative(currentDir, targetDir)}`);
}
// Interactive prompt
async function promptUser(cliOptions = {}) {
try {
const config = loadConfig();
const { type } = await inquirer.prompt([
{
type: 'list',
name: 'type',
message: 'What do you want to generate?',
choices: ['Component', 'Page']
}
]);
const { componentName } = await inquirer.prompt([
{
type: 'input',
name: 'componentName',
message: `Enter ${type.toLowerCase()} name:`,
validate: input => !!input || 'Name is required.'
}
]);
const { useTypeScript, styleExtension, createTest, useCSSModule } = await inquirer.prompt([
{
type: 'confirm',
name: 'useTypeScript',
message: 'Use TypeScript?',
default: config.useTypeScript || false
},
{
type: 'list',
name: 'styleExtension',
message: 'Choose style extension:',
choices: ['css', 'scss'],
default: config.styleExtension || 'css',
when: () => ['Component', 'Page'].includes(type)
},
{
type: 'confirm',
name: 'createTest',
message: 'Create test file?',
default: config.createTest || false,
when: () => ['Component', 'Page'].includes(type)
},
{
type: 'confirm',
name: 'useCSSModule',
message: 'Use CSS Modules?',
default: config.useCSSModule || false,
when: () => ['Component', 'Page'].includes(type)
}
]);
const mergedOptions = {
componentName,
useTypeScript,
styleExtension,
createTest,
useCSSModule,
customPath: cliOptions.current || config.path || null,
useCurrent: cliOptions.current || false,
type
};
createFiles(mergedOptions);
} catch (error) {
if (error?.constructor?.name === 'ExitPromptError') {
console.log('Prompt cancelled by user.');
} else {
console.error('An unexpected error occurred:', error);
}
}
}
// CLI entry point
program
.name('create-component')
.description('Interactive generator for React components, pages, context, and providers')
.option('-p, --path <path>', 'Custom path to place the generated file(s)')
.option('-c, --current', 'Use current directory (e.g., inside /src/components)')
.action((options) => {
promptUser(options);
});
program.parse();