rgen-cli
Version:
A developer CLI for initializing React projects, managing utilities, and scaffolding components, hooks, pages, layouts, routes, and contexts quickly.
227 lines (226 loc) • 8.84 kB
JavaScript
import chalk from 'chalk';
import dotenv from 'dotenv';
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import readline from 'node:readline';
import { defaults } from './defaults.js';
export default class Build {
baseDir = '';
cmd;
defaults = {
base: 'src/',
debug: false,
model: 'gemini-2.5-flash',
useAI: false,
};
deps = {};
flags;
geminiApiKey = '';
name = '';
rootDir = '';
type;
typescript = true;
uname = '';
constructor(cmd, name, type, flags) {
this.cmd = cmd;
this.name = name.trim().toLowerCase();
if (!/^[a-z][a-z0-9\-.]*$/.test(this.name)) {
cmd.error("Invalid name: must start with a letter and can only contain letters, numbers, '-' and '.'");
}
this.type = type;
this.flags = flags;
}
cout(str) {
if (this.defaults.debug) {
this.cmd.log(str);
}
}
async init() {
// *
this.manageDotEnv();
this.manageDefaults();
// * Extract package.json
this.extractPackageJson();
// * auto install packages
this.autoInstall();
if (!this.name) {
throw new Error('name must be provided');
}
this.uname = this.name.charAt(0).toUpperCase() + this.name.slice(1);
// * this.rootDir
switch (this.type) {
case 'forms': {
if (!this.flags.page) {
throw new Error('page name must be provided');
}
this.rootDir = path.join(process.cwd(), this.defaults.base, 'pages', this.flags.page, this.type);
break;
}
default: {
this.rootDir = path.join(process.cwd(), this.defaults.base, this.type);
}
}
this.baseDir = path.join(this.rootDir, this.name.toLowerCase());
// * Aplly Rules
this.applyRules();
if (this.type === 'store') {
fs.mkdirSync(path.join(this.rootDir, 'state', this.name.toLowerCase()), { recursive: true });
}
else {
fs.mkdirSync(this.baseDir, { recursive: true });
}
}
applyRules() {
// * '.' Rule
if (this.name.includes('.')) {
// uname
this.uname = this.uname
.split('.')
.map((e) => e.charAt(0).toUpperCase() + e.slice(1))
.join('');
// baseDir
this.baseDir = path.join(process.cwd(), this.defaults.base, this.type, `${this.name.split('.').join('/').toLowerCase()}`);
}
// * '-' Rule
if (this.name.includes('-')) {
// uname
this.uname = this.uname
.split('-')
.map((e) => e.charAt(0).toUpperCase() + e.slice(1))
.join('');
// baseDir
this.baseDir = path.join(this.rootDir, this.name);
}
}
async autoInstall() {
switch (this.type) {
case 'routes': {
if (!this.deps['react-router']) {
/* const install = await this.promptInstall('react-router')
if (!install) {
this.cmd.error('react-router is required.')
} */
this.cmd.log(chalk.blue('[+] Installing react-router...'));
try {
execSync('npm install react-router', { stdio: 'inherit' });
this.cmd.log(`${chalk.green('[+] Installed react-router successfully.')} `);
}
catch {
this.cmd.error('Failed to install react-router.');
}
this.cmd.log(chalk.green('[+] Installed react-router successfully.'));
}
break;
}
case 'store': {
if (!this.deps['react-redux'] && !this.deps['@reduxjs/toolkit']) {
/* const install = await this.promptInstall('react-redux')
if (!install) {
this.cmd.error('react-redux is required.')
} */
this.cmd.log(chalk.blue('[+] Installing react-redux @reduxjs/toolkit...'));
try {
execSync('npm install react-redux @reduxjs/toolkit', { stdio: 'inherit' });
this.cmd.log(`${chalk.green('[+] Installed react-redux @reduxjs/toolkit successfully.')} `);
}
catch {
this.cmd.error('Failed to install react-redux @reduxjs/toolkit.');
}
}
break;
}
case 'forms': {
if (!this.deps['@hookform/resolvers'] && !this.deps['react-hook-form'] && !this.deps.zod) {
/* const install = await this.promptInstall('')
if (!install) {
this.cmd.error(' is required.')
} */
this.cmd.log(chalk.blue('[+] Installing react-hook-form zod @hookform/resolvers...'));
try {
execSync('npm install react-hook-form zod @hookform/resolvers', { stdio: 'inherit' });
this.cmd.log(`${chalk.green('[+] Installed react-hook-form zod @hookform/resolvers successfully.')} `);
}
catch {
this.cmd.error('Failed to install react-hook-form zod @hookform/resolvers.');
}
}
}
}
}
async extractPackageJson() {
const pkgPath = path.join(process.cwd(), 'package.json');
if (!fs.existsSync(pkgPath)) {
throw new Error('No package.json found. Please make sure you are in the root of a React project.');
}
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
// * Check dependencies or devDependencies for react
this.deps = {
...pkg.dependencies,
...pkg.devDependencies,
};
if (!this.deps.react) {
throw new Error('No "react" dependency found. Are you in a React project?');
}
// * Check if typescipt is installed
if (!this.deps.typescript) {
this.typescript = false;
}
}
manageDefaults() {
const reactUtilsDefaults = defaults();
/*
* Overriding Default debug
*/
if (reactUtilsDefaults.debug) {
this.cout(`${chalk.blue('[+]')} Overriding default debug → "${chalk.blue(reactUtilsDefaults.debug)}"`);
this.defaults.debug = reactUtilsDefaults.debug;
}
/*
* Overriding Default base
*/
if (reactUtilsDefaults.base) {
this.cout(`${chalk.blue('[+]')} Overriding default base directory → "${chalk.blue(reactUtilsDefaults.base)}"`);
this.defaults.base = reactUtilsDefaults.base;
}
/*
* Overriding Default useAI
*/
if (reactUtilsDefaults.useAI) {
this.cout(`${chalk.blue('[+]')} Overriding useAI → "${chalk.blue(reactUtilsDefaults.useAI)}"`);
this.defaults.useAI = reactUtilsDefaults.useAI;
}
/*
* Overriding Default model
*/
if (reactUtilsDefaults.model) {
this.cout(`${chalk.blue('[+]')} Overriding model → "${chalk.blue(reactUtilsDefaults.model)}"`);
this.defaults.model = reactUtilsDefaults.model;
}
}
manageDotEnv() {
const envPath = path.resolve(process.cwd(), '.env');
if (!fs.existsSync(envPath)) {
this.cout(`${chalk.red('[x] .env not found at:')} ${chalk.blue(envPath)}`);
return;
}
this.cout(`${chalk.green('[+] .env found at:')} ${chalk.blue(envPath)}`);
dotenv.config({ path: envPath, quiet: true });
this.geminiApiKey = process.env.GEMINI_API_KEY ?? '';
if (this.geminiApiKey) {
this.cout(`${chalk.green('[+] GEMINI_API_KEY loaded:')} ${chalk.yellow(this.geminiApiKey)}`);
}
else {
this.cout(`${chalk.red('[x] GEMINI_API_KEY not found in .env at:')} ${chalk.blue(envPath)}`);
}
}
async promptInstall(packageName) {
return new Promise((resolve) => {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
rl.question(chalk.blue(packageName) + ` is not installed. Do you want to install it? (Y/n) `, (answer) => {
rl.close();
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === '');
});
});
}
}