create-volo-app-temp
Version:
CLI tool to create a new Volo app with Firebase Auth, Postgres, and Cloudflare deployment
263 lines • 10.5 kB
JavaScript
import inquirer from 'inquirer';
import path from 'path';
import fs from 'fs-extra';
import { execa } from 'execa';
import chalk from 'chalk';
import ora from 'ora';
import { logger } from '../utils/logger.js';
import { cloneTemplate } from '../utils/template.js';
import { setupFirebase } from '../services/firebase.js';
import { setupCloudflare } from '../services/cloudflare.js';
import { generateConfigFiles } from '../utils/config.js';
import { validateProjectName } from '../utils/validation.js';
import { setupDatabase } from '../services/database.js';
export async function createApp(projectName, options) {
// Get project name
const name = await getProjectName(projectName);
const directory = path.resolve(process.cwd(), name);
// Validate project directory
if (await fs.pathExists(directory)) {
const { overwrite } = await inquirer.prompt([
{
type: 'confirm',
name: 'overwrite',
message: `Directory "${name}" already exists. Do you want to overwrite it?`,
default: false
}
]);
if (!overwrite) {
logger.info('Operation cancelled.');
return;
}
await fs.remove(directory);
}
logger.step(`Creating project "${name}"...`);
logger.newLine();
// Step 1: Clone template
const cloneSpinner = ora({
text: 'Cloning template...',
spinner: 'line'
}).start();
try {
await cloneTemplate(options.template, directory);
cloneSpinner.succeed('Template cloned successfully');
}
catch (error) {
cloneSpinner.fail('Failed to clone template');
throw error;
}
// Step 2: Gather configuration
logger.newLine();
console.log(chalk.cyan.bold('🔧 Setting up your app services...'));
console.log(chalk.white('Your Volo app needs three key services to work:'));
console.log(chalk.white(' • Firebase - for user authentication (login/signup)'));
console.log(chalk.white(' • Database - for storing your app data'));
console.log(chalk.white(' • Cloudflare - for hosting your app globally'));
logger.newLine();
const config = {
name,
directory,
firebase: await setupFirebaseWithRetry(),
database: await setupDatabaseWithRetry(options.databasePreference),
cloudflare: await setupCloudflare(name)
};
// Step 3: Generate configuration files
const configSpinner = ora({
text: 'Generating configuration files...',
spinner: 'line'
}).start();
try {
await generateConfigFiles(config);
configSpinner.succeed('Configuration files generated');
}
catch (error) {
configSpinner.fail('Failed to generate configuration files');
throw error;
}
// Step 4: Run post-setup
const postSetupSpinner = ora({
text: 'Running post-setup tasks (this may take 30-60 seconds).',
spinner: 'line'
}).start();
// Add animated dots effect for the long-running post-setup
let dotCount = 1;
const dotsInterval = setInterval(() => {
const dots = '.'.repeat(dotCount);
postSetupSpinner.text = `Running post-setup tasks (this may take 30-60 seconds)${dots}`;
dotCount = dotCount === 3 ? 1 : dotCount + 1;
}, 500);
try {
await execa('pnpm', ['post-setup'], {
cwd: directory,
stdio: options.verbose ? 'inherit' : 'pipe'
});
clearInterval(dotsInterval);
postSetupSpinner.succeed('Post-setup completed successfully!');
}
catch (error) {
clearInterval(dotsInterval);
postSetupSpinner.fail('Post-setup encountered issues');
logger.warning('You can run it manually later');
logger.newLine();
console.log(chalk.yellow.bold('⚡ Complete setup manually:'));
console.log(chalk.cyan(` cd ${name}`));
console.log(chalk.cyan(' pnpm post-setup'));
logger.newLine();
logger.debug(`Post-setup error: ${error}`);
}
// Step 5: Success message
logger.newLine();
logger.success('🎉 Your Volo app has been created successfully!');
logger.newLine();
console.log(chalk.cyan.bold('🚀 What you got:'));
console.log(chalk.white(' • React + TypeScript + Tailwind CSS + ShadCN frontend'));
console.log(chalk.white(' • Hono API backend for Cloudflare Workers'));
console.log(chalk.white(' • Firebase Authentication (Google Sign-In)'));
console.log(chalk.white(' • PostgreSQL database with Drizzle ORM'));
console.log(chalk.white(' • Production deployment ready'));
logger.newLine();
console.log(chalk.green.bold('▶️ Next steps:'));
console.log(chalk.cyan(` cd ${name}`));
console.log(chalk.cyan(' pnpm run dev:start'));
logger.newLine();
// Ask if user wants to start the app now
const { startNow } = await inquirer.prompt([
{
type: 'confirm',
name: 'startNow',
message: 'Would you like to start the development server now?',
default: true
}
]);
if (startNow) {
logger.newLine();
console.log(chalk.green('🚀 Starting your Volo app...'));
logger.newLine();
try {
// Change to the project directory and start the dev server
await execa('pnpm', ['run', 'dev:start'], {
cwd: directory,
stdio: 'inherit'
});
}
catch (error) {
logger.error('Failed to start the development server');
logger.info('You can start it manually by running:');
console.log(chalk.cyan(` cd ${name}`));
console.log(chalk.cyan(' pnpm run dev:start'));
}
}
else {
console.log(chalk.blue('📚 Need help? Check the README.md in your project directory'));
logger.newLine();
console.log(chalk.gray('When you\'re ready to start developing:'));
console.log(chalk.cyan(` cd ${name}`));
console.log(chalk.cyan(' pnpm run dev:start'));
}
}
async function getProjectName(provided) {
if (provided && validateProjectName(provided)) {
return provided;
}
if (provided) {
logger.warning(`"${provided}" is not a valid project name.`);
logger.info('Project names should be lowercase, contain only letters, numbers, and hyphens.');
}
const { name } = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'What is your project name?',
default: 'my-volo-app',
validate: (input) => {
if (!input.trim()) {
return 'Project name is required';
}
if (!validateProjectName(input)) {
return 'Project name should be lowercase, contain only letters, numbers, and hyphens';
}
return true;
}
}
]);
return name;
}
async function setupFirebaseWithRetry(maxRetries = 2) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await setupFirebase();
}
catch (error) {
logger.warning(`Firebase setup failed (attempt ${attempt}/${maxRetries})`);
if (attempt === maxRetries) {
logger.error('Firebase setup failed after multiple attempts');
logger.newLine();
console.log(chalk.yellow.bold('⚡ Manual Firebase setup required:'));
console.log(chalk.cyan(' 1. Visit https://console.firebase.google.com'));
console.log(chalk.cyan(' 2. Create a new project'));
console.log(chalk.cyan(' 3. Enable Google Authentication'));
console.log(chalk.cyan(' 4. Create a web app and update your config files'));
logger.newLine();
throw error;
}
const { retry } = await inquirer.prompt([
{
type: 'confirm',
name: 'retry',
message: 'Would you like to retry Firebase setup?',
default: true
}
]);
if (!retry) {
throw error;
}
logger.info('Retrying Firebase setup...');
}
}
throw new Error('Firebase setup failed');
}
async function setupDatabaseWithRetry(databasePreference, maxRetries = 2) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
switch (databasePreference) {
case 'neon':
return await setupDatabase(databasePreference);
case 'supabase':
const { setupSupabaseDatabase } = await import('../services/supabase.js');
return await setupSupabaseDatabase();
case 'other':
const { setupOtherDatabase } = await import('../services/database.js');
return await setupOtherDatabase();
default:
return await setupDatabase(databasePreference); // fallback
}
}
catch (error) {
logger.warning(`Database setup failed (attempt ${attempt}/${maxRetries})`);
if (attempt === maxRetries) {
logger.error('Database setup failed after multiple attempts');
logger.newLine();
console.log(chalk.yellow.bold('⚡ Manual database setup required:'));
console.log(chalk.cyan(' 1. Create a PostgreSQL database (Neon, Supabase, or other)'));
console.log(chalk.cyan(' 2. Update DATABASE_URL in server/.dev.vars'));
console.log(chalk.cyan(' 3. Run: cd server && pnpm run db:push'));
logger.newLine();
throw error;
}
const { retry } = await inquirer.prompt([
{
type: 'confirm',
name: 'retry',
message: 'Would you like to retry database setup?',
default: true
}
]);
if (!retry) {
throw error;
}
logger.info('Retrying database setup...');
}
}
throw new Error('Database setup failed');
}
//# sourceMappingURL=create.js.map