@reuvenorg/react-native-boilerplate-ultimate
Version:
A powerful CLI tool for creating React Native projects with modular architecture. Generate, manage, and scaffold React Native applications with pre-built modules and best practices.
248 lines ⢠10.3 kB
JavaScript
import { Command } from 'commander';
import fs from 'fs-extra';
import * as path from 'path';
import ora from 'ora';
import { resolveProjectPath, getProjectInfo, getErrorMessage, readPackageJson, logSuccess, logError, logWarning, logGray, logHeader, successMessage, failMessage, } from '../utils/index.js';
const DEFAULT_COLORS = {
primary: '#1976D2',
background: '#FFFFFF',
};
// Android icon sizes
const ANDROID_ICONS = {
'mipmap-mdpi': 48,
'mipmap-hdpi': 72,
'mipmap-xhdpi': 96,
'mipmap-xxhdpi': 144,
'mipmap-xxxhdpi': 192,
};
// iOS icon sizes
const IOS_ICONS = {
'Icon-20@2x.png': 40,
'Icon-20@3x.png': 60,
'Icon-29@2x.png': 58,
'Icon-29@3x.png': 87,
'Icon-40@2x.png': 80,
'Icon-40@3x.png': 120,
'Icon-60@2x.png': 120,
'Icon-60@3x.png': 180,
'Icon-76@2x.png': 152,
'Icon-83.5@2x.png': 167,
'Icon-1024.png': 1024,
};
function isCanvasModule(module) {
if (typeof module !== 'object' || module === null) {
return false;
}
if (!('createCanvas' in module)) {
return false;
}
// Use Record type to safely access properties
const moduleRecord = module;
return typeof moduleRecord.createCanvas === 'function';
}
function ensureDirectoryExists(dirPath) {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}
function validateCanvasInstallation(projectRoot) {
try {
const packageJson = readPackageJson(projectRoot);
return !!((packageJson.dependencies && packageJson.dependencies['canvas']) ||
(packageJson.devDependencies && packageJson.devDependencies['canvas']));
}
catch {
return false;
}
}
async function generateIcon(size, backgroundColor, foregroundColor) {
try {
// Dynamic import with string to avoid TypeScript resolution at compile time
const moduleName = 'canvas';
const canvasModule = await import(moduleName).catch(() => {
throw new Error('Canvas package not found');
});
if (!isCanvasModule(canvasModule)) {
throw new Error('Canvas module does not have expected interface');
}
const canvas = canvasModule.createCanvas(size, size);
const ctx = canvas.getContext('2d');
// Background
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, size, size);
// Foreground - simple circle
const centerX = size / 2;
const centerY = size / 2;
const radius = size * 0.3;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.fillStyle = foregroundColor;
ctx.fill();
// Add text
ctx.fillStyle = '#FFFFFF';
ctx.font = `${size * 0.15}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('App', centerX, centerY);
return canvas.toBuffer('image/png');
}
catch {
throw new Error('Canvas package not found. Please install it in your project: npm install canvas --save-dev --legacy-peer-deps');
}
}
async function generateSplashLogo(size, colors) {
try {
// Dynamic import with string to avoid TypeScript resolution at compile time
const moduleName = 'canvas';
const canvasModule = await import(moduleName).catch(() => {
throw new Error('Canvas package not found');
});
if (!isCanvasModule(canvasModule)) {
throw new Error('Canvas module does not have expected interface');
}
const canvas = canvasModule.createCanvas(size, size);
const ctx = canvas.getContext('2d');
// Logo - circle
const centerX = size / 2;
const centerY = size / 2;
const radius = size * 0.4;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.fillStyle = colors.primary;
ctx.fill();
// Inner text
ctx.fillStyle = '#FFFFFF';
ctx.font = `${size * 0.2}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Logo', centerX, centerY);
return canvas.toBuffer('image/png');
}
catch {
throw new Error('Canvas package not found. Please install it in your project: npm install canvas --save-dev --legacy-peer-deps');
}
}
async function generateAndroidIcons(projectRoot, colors) {
const spinner = ora('Generating Android icons...').start();
try {
for (const [folder, size] of Object.entries(ANDROID_ICONS)) {
const dirPath = path.join(projectRoot, 'android', 'app', 'src', 'main', 'res', folder);
ensureDirectoryExists(dirPath);
const iconBuffer = await generateIcon(size, colors.background, colors.primary);
const filePath = path.join(dirPath, 'ic_launcher.png');
fs.writeFileSync(filePath, iconBuffer);
// Also create round icon
const roundFilePath = path.join(dirPath, 'ic_launcher_round.png');
fs.writeFileSync(roundFilePath, iconBuffer);
}
spinner.succeed(successMessage('Android icons generated successfully'));
}
catch (error) {
spinner.fail(failMessage(`Failed to generate Android icons: ${getErrorMessage(error)}`));
throw error;
}
}
async function generateIOSIcons(projectRoot, colors) {
const spinner = ora('Generating iOS icons...').start();
try {
const projectInfo = getProjectInfo(projectRoot);
const iosIconPath = path.join(projectRoot, 'ios', projectInfo.iosScheme, 'Images.xcassets', 'AppIcon.appiconset');
ensureDirectoryExists(iosIconPath);
for (const [filename, size] of Object.entries(IOS_ICONS)) {
const iconBuffer = await generateIcon(size, colors.background, colors.primary);
const filePath = path.join(iosIconPath, filename);
fs.writeFileSync(filePath, iconBuffer);
}
spinner.succeed(successMessage('iOS icons generated successfully'));
}
catch (error) {
spinner.fail(failMessage(`Failed to generate iOS icons: ${getErrorMessage(error)}`));
throw error;
}
}
async function generateSplashScreens(projectRoot, colors) {
const spinner = ora('Generating splash screens...').start();
try {
// Android splash screens
const androidSplashSizes = [48, 72, 96, 144, 192];
for (const size of androidSplashSizes) {
const densityFolder = Object.entries(ANDROID_ICONS).find(([, iconSize]) => iconSize === size)?.[0];
if (!densityFolder)
continue;
const dirPath = path.join(projectRoot, 'android', 'app', 'src', 'main', 'res', densityFolder.replace('mipmap', 'drawable'));
ensureDirectoryExists(dirPath);
const logoBuffer = await generateSplashLogo(size, colors);
const filePath = path.join(dirPath, 'splashscreen_logo.png');
fs.writeFileSync(filePath, logoBuffer);
}
// iOS splash screen
const projectInfo = getProjectInfo(projectRoot);
const iosPath = path.join(projectRoot, 'ios', projectInfo.iosScheme, 'Images.xcassets', 'SplashScreenLogo.imageset');
ensureDirectoryExists(iosPath);
fs.writeFileSync(path.join(iosPath, 'splash-logo.png'), await generateSplashLogo(100, colors));
fs.writeFileSync(path.join(iosPath, 'splash-logo@2x.png'), await generateSplashLogo(200, colors));
fs.writeFileSync(path.join(iosPath, 'splash-logo@3x.png'), await generateSplashLogo(300, colors));
spinner.succeed(successMessage('Splash screens generated successfully'));
}
catch (error) {
spinner.fail(failMessage(`Failed to generate splash screens: ${getErrorMessage(error)}`));
throw error;
}
}
async function iconsAction(options) {
try {
const projectRoot = resolveProjectPath(options);
const projectInfo = getProjectInfo(projectRoot);
const colors = {
primary: typeof options.primary === 'string'
? options.primary
: DEFAULT_COLORS.primary,
background: typeof options.background === 'string'
? options.background
: DEFAULT_COLORS.background,
};
logHeader(`đ¨ Icon Generator for "${projectInfo.displayName}"`);
logGray(`Primary color: ${colors.primary}`);
logGray(`Background color: ${colors.background}`);
// Check if canvas is installed
if (!validateCanvasInstallation(projectRoot)) {
logWarning('\nâ ď¸ Canvas package not found in project dependencies.');
console.log('Please install it first: npm install canvas --save-dev --legacy-peer-deps');
logGray('Note: Canvas package is required for icon generation.');
return;
}
const isAndroidOnly = Boolean(options.android);
const isIOSOnly = Boolean(options.ios);
const isSplashOnly = Boolean(options.splash);
if (isSplashOnly) {
await generateSplashScreens(projectRoot, colors);
}
else if (isAndroidOnly) {
await generateAndroidIcons(projectRoot, colors);
}
else if (isIOSOnly) {
await generateIOSIcons(projectRoot, colors);
}
else {
// Generate all
await generateAndroidIcons(projectRoot, colors);
await generateIOSIcons(projectRoot, colors);
await generateSplashScreens(projectRoot, colors);
}
logSuccess('\nđ Icon generation completed successfully!');
}
catch (error) {
logError(`â ${getErrorMessage(error)}`);
process.exit(1);
}
}
export const iconsCommand = new Command('icons')
.description('Generate app icons and splash screens')
.option('--android', 'Generate Android icons only')
.option('--ios', 'Generate iOS icons only')
.option('--splash', 'Generate splash screens only')
.option('--primary <color>', 'Primary color', DEFAULT_COLORS.primary)
.option('--background <color>', 'Background color', DEFAULT_COLORS.background)
.option('--destination <path>', 'Project directory path')
.action(iconsAction);
//# sourceMappingURL=icons.js.map