@nhangen/universal-dev-env
Version:
Universal development environment setup for modern web projects
1,474 lines (1,261 loc) ⢠84.1 kB
JavaScript
#!/usr/bin/env node
const { program } = require('commander');
const chalk = require('chalk');
const inquirer = require('inquirer');
const ora = require('ora');
const fs = require('fs');
const path = require('path');
const { execSync, exec } = require('child_process');
const os = require('os');
// Cross-platform utilities
function isWindows() {
return os.platform() === 'win32';
}
function isMacOS() {
return os.platform() === 'darwin';
}
function isLinux() {
return os.platform() === 'linux';
}
function getShellCommand(command) {
if (isWindows()) {
// Use PowerShell on Windows
return `powershell -Command "${command}"`;
}
return command;
}
function getPackageManager() {
if (isWindows()) {
return 'choco';
} else if (isMacOS()) {
return 'brew';
} else {
// Detect Linux package manager
try {
execSync('which apt-get', { stdio: 'ignore' });
return 'apt';
} catch {
try {
execSync('which yum', { stdio: 'ignore' });
return 'yum';
} catch {
try {
execSync('which apk', { stdio: 'ignore' });
return 'apk';
} catch {
return 'unknown';
}
}
}
}
}
const packageJson = require('../package.json');
// Cache configuration
const CACHE_DIR = path.join(os.homedir(), '.universal-dev-env', 'cache');
const CACHE_EXPIRY = 30 * 24 * 60 * 60 * 1000; // 30 days in milliseconds
program
.name('universal-dev-setup')
.description('Universal development environment setup tool')
.version(packageJson.version);
program
.command('init')
.description('Initialize a new project with universal development environment')
.option('-t, --type <type>', 'Project type (react, node, python, full-stack)')
.option('-n, --name <name>', 'Project name')
.option('--here', 'Initialize in current directory instead of creating subdirectory')
.option('--ml', 'Include ML libraries for Python projects')
.option('--backend <backend>', 'Backend for React projects (none, express, nextjs, firebase, serverless)')
.option('--ai-context', 'Generate AI context file for Claude/Gemini/Copilot shared memory')
.option('--skip-prompts', 'Skip interactive prompts')
.option('--cache', 'Enable caching for faster setup (default: true)')
.option('--no-cache', 'Disable caching and download fresh copies')
.action(async (options) => {
console.log(chalk.blue.bold('š Universal Development Environment Setup'));
console.log(chalk.gray('='.repeat(50)));
let config = {};
if (!options.skipPrompts) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'projectName',
message: 'Project name:',
default: options.name || path.basename(process.cwd()),
validate: (input) => input.length > 0
},
{
type: 'list',
name: 'projectType',
message: 'Select project type:',
choices: [
{ name: 'āļø React Frontend', value: 'react' },
{ name: 'š¢ Node.js Backend', value: 'node' },
{ name: 'š Python Application', value: 'python' },
{ name: 'š Full-Stack (React + Node)', value: 'full-stack' },
{ name: 'āļø Custom/Other', value: 'custom' }
],
default: options.type || 'react'
},
{
type: 'checkbox',
name: 'features',
message: 'Select additional features:',
choices: [
{ name: 'š¤ AI CLI Tools (Claude, Gemini)', value: 'ai-cli', checked: true },
{ name: 'āļø Google Cloud CLI', value: 'gcloud', checked: true },
{ name: 'š¦ GitHub CLI', value: 'github-cli', checked: true },
{ name: 'š Playwright (Web Automation)', value: 'playwright' },
{ name: 'š³ Docker Multi-stage Build', value: 'docker-multi-stage' },
{ name: 'š§ VS Code Extensions Pack', value: 'vscode-extensions', checked: true }
]
},
{
type: 'list',
name: 'baseImage',
message: 'Select base Docker image:',
choices: [
{ name: 'š§ Alpine Linux (Lightweight)', value: 'alpine' },
{ name: 'š„ļø Debian/Ubuntu (Compatible)', value: 'debian' }
],
default: 'debian'
},
{
type: 'confirm',
name: 'aiContext',
message: 'š§ Generate AI context file for shared Claude/Gemini/Copilot memory?',
default: true
}
]);
// If Python project is selected, ask about ML libraries
if (answers.projectType === 'python') {
const mlPrompt = await inquirer.prompt([
{
type: 'confirm',
name: 'includeMl',
message: 'š¤ Install Machine Learning libraries? (NumPy, Pandas, Scikit-learn, etc.)',
default: false
}
]);
answers.includeMl = mlPrompt.includeMl;
}
// If React project is selected, ask about backend
if (answers.projectType === 'react') {
const backendPrompt = await inquirer.prompt([
{
type: 'list',
name: 'backend',
message: 'š§ Select backend for your React app:',
choices: [
{ name: 'š¦ Frontend Only (No backend)', value: 'none' },
{ name: 'š Express.js API', value: 'express' },
{ name: 'ā” Next.js (Full-stack)', value: 'nextjs' },
{ name: 'š„ Firebase Functions', value: 'firebase' },
{ name: 'āļø Serverless (Vercel/Netlify)', value: 'serverless' }
],
default: 'none'
}
]);
answers.backend = backendPrompt.backend;
}
config = { ...options, ...answers };
} else {
config = {
projectName: options.name || path.basename(process.cwd()),
projectType: options.type || 'react',
features: ['ai-cli', 'gcloud', 'github-cli', 'vscode-extensions'],
baseImage: 'debian',
cache: options.cache,
here: options.here,
includeMl: options.ml || false, // Use --ml flag or default to false
backend: options.backend || 'none', // Use --backend flag or default to none
aiContext: options.aiContext || false // Use --ai-context flag or default to false
};
}
await setupProject(config);
});
program
.command('install')
.description('Install development tools in current environment')
.option('--dry-run', 'Show what would be installed without executing')
.option('--cache', 'Enable caching for faster installation (default: true)')
.option('--no-cache', 'Disable caching and download fresh copies')
.action(async (options) => {
const spinner = ora('Installing development environment...').start();
try {
const scriptPath = path.join(__dirname, '..', 'universal-setup.sh');
if (options.dryRun) {
spinner.info('Dry run mode - would execute: ' + scriptPath);
return;
}
execSync(`chmod +x "${scriptPath}"`, { stdio: 'inherit' });
execSync(`"${scriptPath}"`, { stdio: 'inherit' });
spinner.succeed('Development environment installed successfully!');
} catch (error) {
spinner.fail('Installation failed: ' + error.message);
process.exit(1);
}
});
program
.command('setup')
.description('Setup development environment in existing project')
.option('--dry-run', 'Show what would be installed without executing')
.option('--cache', 'Enable caching for faster setup (default: true)')
.option('--no-cache', 'Disable caching and download fresh copies')
.action(async (options) => {
const spinner = ora('Setting up development environment...').start();
try {
// First, download the latest universal-setup.sh script
const scriptUrl = 'https://raw.githubusercontent.com/nhangen/universal-dev-env/main/universal-setup.sh';
const useCache = options.cache !== false;
spinner.text = 'Downloading latest setup script...';
const scriptContent = await downloadWithCache(scriptUrl, 'universal-setup.sh', useCache);
// Write the script to current directory
const scriptPath = path.join(process.cwd(), 'universal-setup.sh');
fs.writeFileSync(scriptPath, scriptContent);
fs.chmodSync(scriptPath, 0o755);
if (options.dryRun) {
spinner.info('Dry run mode - would execute: ' + scriptPath);
return;
}
spinner.text = 'Running setup script...';
console.log(chalk.blue('\nš Executing setup script...\n'));
try {
execSync(`bash "${scriptPath}"`, { stdio: 'inherit' });
spinner.succeed('Development environment setup complete!');
console.log(chalk.green('ā
All tools installed and configured'));
} catch (scriptError) {
spinner.fail('Setup script execution failed');
console.log(chalk.red('ā Script failed with error:', scriptError.message));
console.log(chalk.yellow('š” Try running manually: ./universal-setup.sh'));
throw scriptError;
}
} catch (error) {
spinner.fail('Setup failed: ' + error.message);
console.log(chalk.yellow('š” Try running manually:'));
console.log(chalk.gray(' curl -fsSL https://raw.githubusercontent.com/nhangen/universal-dev-env/main/universal-setup.sh -o universal-setup.sh'));
console.log(chalk.gray(' chmod +x universal-setup.sh'));
console.log(chalk.gray(' ./universal-setup.sh'));
}
});
program
.command('template <name>')
.description('Create a new project from template')
.option('-d, --directory <dir>', 'Target directory', '.')
.action(async (templateName, options) => {
const templates = ['react', 'node', 'python', 'full-stack'];
if (!templates.includes(templateName)) {
console.error(chalk.red(`ā Template "${templateName}" not found.`));
console.log(chalk.yellow('Available templates:', templates.join(', ')));
process.exit(1);
}
const spinner = ora(`Creating ${templateName} project template...`).start();
try {
await createTemplate(templateName, options.directory);
spinner.succeed(`${templateName} template created successfully!`);
} catch (error) {
spinner.fail('Template creation failed: ' + error.message);
process.exit(1);
}
});
program
.command('update')
.alias('upgrade')
.description('Update universal-dev-env to latest version')
.action(async () => {
const spinner = ora('Updating universal-dev-env...').start();
try {
execSync('npm install -g @nhangen/universal-dev-env@latest', { stdio: 'inherit' });
spinner.succeed('Updated to latest version!');
} catch (error) {
spinner.fail('Update failed: ' + error.message);
}
});
// Configuration strategy selector
function selectConfigurationStrategy(config) {
const strategy = {
containerStrategy: 'devcontainer', // devcontainer, docker, docker-compose
configFormat: 'json', // json, yaml
deploymentStrategy: 'static', // static, containerized, serverless, hybrid
environmentConfigs: ['development'], // development, staging, production
additionalConfigs: []
};
// Determine container strategy based on project complexity
if (config.projectType === 'react' && config.backend === 'express') {
strategy.containerStrategy = 'docker-compose'; // Multi-service
strategy.deploymentStrategy = 'containerized';
strategy.environmentConfigs = ['development', 'staging', 'production'];
strategy.installLocation = 'container'; // Install tools in container
} else if (config.projectType === 'full-stack') {
strategy.containerStrategy = 'docker-compose';
strategy.deploymentStrategy = 'containerized';
strategy.environmentConfigs = ['development', 'staging', 'production'];
strategy.installLocation = 'container';
} else if (config.projectType === 'react' && ['firebase', 'serverless'].includes(config.backend)) {
strategy.containerStrategy = 'devcontainer'; // Development only
strategy.deploymentStrategy = 'serverless';
strategy.configFormat = 'yaml'; // Serverless often uses YAML
strategy.installLocation = 'host'; // CLI tools needed on host for deployment
} else if (config.projectType === 'react' && config.backend === 'nextjs') {
strategy.containerStrategy = 'docker'; // Single service
strategy.deploymentStrategy = 'hybrid'; // Can be static or containerized
strategy.installLocation = 'container';
} else if (config.projectType === 'python') {
strategy.containerStrategy = 'docker';
strategy.deploymentStrategy = 'containerized';
strategy.configFormat = 'yaml'; // Python ecosystem often uses YAML
strategy.installLocation = 'container';
} else {
strategy.containerStrategy = 'devcontainer';
strategy.deploymentStrategy = 'static';
strategy.installLocation = 'host'; // Simple projects use host tools
}
// Determine which tools should be included based on strategy
strategy.includeTools = {
essentials: true, // Always include: Node.js, Python, Git, Docker
aiClis: strategy.installLocation === 'host', // Claude, Gemini - only on host
cloudClis: strategy.deploymentStrategy === 'serverless' || strategy.installLocation === 'host', // gcloud, gh
heavyTools: strategy.installLocation === 'host' // Playwright, browsers
};
// Add additional configs based on features
if (config.features && config.features.includes('playwright')) {
strategy.additionalConfigs.push('playwright');
}
if (config.includeMl) {
strategy.additionalConfigs.push('jupyter', 'conda');
}
return strategy;
}
// Create container configurations based on selected strategy
async function createContainerConfigurations(config) {
const strategy = config.strategy;
switch (strategy.containerStrategy) {
case 'devcontainer':
await createDevcontainerConfig(config);
break;
case 'docker':
await createDockerConfig(config);
await createDevcontainerConfig(config); // Still create devcontainer for development
break;
case 'docker-compose':
await createDockerComposeConfig(config);
await createDevcontainerConfig(config); // DevContainer can use docker-compose
break;
}
// Create environment-specific configurations
await createEnvironmentConfigs(config);
}
async function createDevcontainerConfig(config) {
if (!fs.existsSync('.devcontainer')) {
fs.mkdirSync('.devcontainer');
}
const devcontainerConfig = generateDevcontainerConfig(config);
const filename = config.strategy.configFormat === 'yaml' ? 'devcontainer.yml' : 'devcontainer.json';
if (config.strategy.configFormat === 'yaml') {
// Convert to YAML format (for demonstration, keeping JSON for now)
fs.writeFileSync(`.devcontainer/${filename}`, JSON.stringify(devcontainerConfig, null, 2));
} else {
fs.writeFileSync(`.devcontainer/${filename}`, JSON.stringify(devcontainerConfig, null, 2));
}
}
async function createDockerConfig(config) {
const dockerfileContent = generateDockerfile(config);
fs.writeFileSync('Dockerfile', dockerfileContent);
// Create .dockerignore
const dockerignoreContent = generateDockerignore(config);
fs.writeFileSync('.dockerignore', dockerignoreContent);
}
async function createDockerComposeConfig(config) {
const dockerComposeContent = generateDockerCompose(config);
const filename = config.strategy.configFormat === 'yaml' ? 'docker-compose.yml' : 'docker-compose.json';
if (config.strategy.configFormat === 'yaml') {
fs.writeFileSync(filename, dockerComposeContent);
} else {
// Convert YAML to JSON format if needed
fs.writeFileSync(filename, dockerComposeContent);
}
// Also create individual Dockerfiles for services
await createDockerConfig(config);
}
async function createEnvironmentConfigs(config) {
const strategy = config.strategy;
for (const env of strategy.environmentConfigs) {
const envConfig = generateEnvironmentConfig(config, env);
const filename = `.env.${env}`;
fs.writeFileSync(filename, envConfig);
}
// Create environment-specific deployment configs
if (strategy.deploymentStrategy === 'containerized') {
await createKubernetesConfigs(config);
}
}
function generateDockerfile(config) {
const backend = config.backend || 'none';
const strategy = config.strategy || {};
const includeTools = strategy.includeTools || {};
if (config.projectType === 'react' && backend === 'nextjs') {
return `# Next.js Production Dockerfile
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci --only=production
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
`;
} else if (config.projectType === 'python') {
// Python containers are typically lightweight for production
const mlPackages = config.includeMl && includeTools.heavyTools ? `
# ML libraries (only if needed and heavy tools allowed)
RUN pip install --no-cache-dir numpy pandas scikit-learn matplotlib seaborn jupyter` : '';
return `# Python Application Dockerfile
FROM python:3.11-slim
WORKDIR /app
# Install only essential system dependencies
RUN apt-get update && apt-get install -y \\
build-essential \\
curl \\
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt${mlPackages}
# Copy application code
COPY . .
# Create non-root user
RUN useradd --create-home --shell /bin/bash app
RUN chown -R app:app /app
USER app
EXPOSE 8000
CMD ["python", "main.py"]
`;
} else {
// Generic Node.js Dockerfile - lightweight for containers
return `# Node.js Application Dockerfile
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy application code
COPY . .
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S app -u 1001
RUN chown -R app:nodejs /app
USER app
EXPOSE 3000
CMD ["npm", "start"]
`;
}
}
function generateDockerignore(config) {
return `node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.DS_Store
.vscode
.devcontainer
Dockerfile
.dockerignore
${config.projectType === 'python' ? '__pycache__\n*.pyc\nvenv/\n.pytest_cache/' : ''}
`;
}
function generateDockerCompose(config) {
const backend = config.backend || 'none';
if (config.projectType === 'react' && backend === 'express') {
return `version: '3.8'
services:
client:
build:
context: ./client
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- REACT_APP_API_URL=http://localhost:3001
volumes:
- ./client:/app
- /app/node_modules
depends_on:
- server
server:
build:
context: ./server
dockerfile: Dockerfile
ports:
- "3001:3001"
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://user:password@db:5432/myapp
volumes:
- ./server:/app
- /app/node_modules
depends_on:
- db
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
`;
} else if (config.projectType === 'full-stack') {
return `version: '3.8'
services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=development
volumes:
- ./frontend:/app
- /app/node_modules
backend:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "3001:3001"
environment:
- NODE_ENV=development
volumes:
- ./backend:/app
- /app/node_modules
redis:
image: redis:7-alpine
ports:
- "6379:6379"
`;
} else {
return `version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=development
volumes:
- .:/app
- /app/node_modules
`;
}
}
function generateEnvironmentConfig(config, environment) {
const baseConfig = `# ${environment.toUpperCase()} Environment Configuration
NODE_ENV=${environment}
`;
const backend = config.backend || 'none';
if (config.projectType === 'react' && backend === 'express') {
return baseConfig + `
# API Configuration
API_URL=${environment === 'production' ? 'https://your-api.com' : 'http://localhost:3001'}
REACT_APP_API_URL=${environment === 'production' ? 'https://your-api.com' : 'http://localhost:3001'}
# Database Configuration
DATABASE_URL=${environment === 'production' ? 'postgresql://user:password@prod-db:5432/myapp' : 'postgresql://user:password@localhost:5432/myapp'}
# Security
JWT_SECRET=${environment === 'production' ? 'your-production-secret' : 'your-development-secret'}
`;
} else if (config.projectType === 'python') {
return baseConfig + `
# Python Configuration
DEBUG=${environment === 'development' ? 'True' : 'False'}
SECRET_KEY=${environment === 'production' ? 'your-production-secret-key' : 'your-development-secret-key'}
# Database
DATABASE_URL=${environment === 'production' ? 'postgresql://user:password@prod-db:5432/myapp' : 'sqlite:///./dev.db'}
`;
} else {
return baseConfig + `
# Application Configuration
PORT=${environment === 'production' ? '80' : '3000'}
`;
}
}
async function createKubernetesConfigs(config) {
if (!fs.existsSync('k8s')) {
fs.mkdirSync('k8s');
}
// Create basic Kubernetes deployment
const k8sDeployment = generateKubernetesDeployment(config);
fs.writeFileSync('k8s/deployment.yaml', k8sDeployment);
// Create service
const k8sService = generateKubernetesService(config);
fs.writeFileSync('k8s/service.yaml', k8sService);
}
function generateKubernetesDeployment(config) {
return `apiVersion: apps/v1
kind: Deployment
metadata:
name: ${config.projectName.toLowerCase().replace(/\s+/g, '-')}
labels:
app: ${config.projectName.toLowerCase().replace(/\s+/g, '-')}
spec:
replicas: 3
selector:
matchLabels:
app: ${config.projectName.toLowerCase().replace(/\s+/g, '-')}
template:
metadata:
labels:
app: ${config.projectName.toLowerCase().replace(/\s+/g, '-')}
spec:
containers:
- name: app
image: ${config.projectName.toLowerCase().replace(/\s+/g, '-')}:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
`;
}
function generateKubernetesService(config) {
return `apiVersion: v1
kind: Service
metadata:
name: ${config.projectName.toLowerCase().replace(/\s+/g, '-')}-service
spec:
selector:
app: ${config.projectName.toLowerCase().replace(/\s+/g, '-')}
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
`;
}
async function setupProject(config) {
const spinner = ora('Setting up project...').start();
const useCache = config.cache !== false; // Default to true unless explicitly disabled
// Select configuration strategy
const strategy = selectConfigurationStrategy(config);
config.strategy = strategy;
if (useCache) {
spinner.text = `Setting up project (cache enabled, ${strategy.containerStrategy} strategy)...`;
}
try {
// Create basic project structure or use current directory
if (config.here) {
// Use current directory
config.projectName = path.basename(process.cwd());
spinner.text = `Setting up project in current directory (${config.projectName})...`;
} else {
// Create subdirectory
if (!fs.existsSync(config.projectName)) {
fs.mkdirSync(config.projectName, { recursive: true });
}
process.chdir(config.projectName);
}
// Download latest universal setup files
const universalFiles = [
{ name: 'universal-setup.sh', url: 'https://raw.githubusercontent.com/nhangen/universal-dev-env/main/universal-setup.sh' },
{ name: 'Dockerfile.universal', url: 'https://raw.githubusercontent.com/nhangen/universal-dev-env/main/Dockerfile.universal' },
{ name: 'devcontainer.universal.json', url: 'https://raw.githubusercontent.com/nhangen/universal-dev-env/main/devcontainer.universal.json' }
];
for (const file of universalFiles) {
try {
spinner.text = `Downloading ${file.name}...`;
const content = await downloadWithCache(file.url, file.name, useCache);
const destPath = path.join(process.cwd(), file.name);
fs.writeFileSync(destPath, content);
if (file.name.endsWith('.sh')) {
fs.chmodSync(destPath, 0o755);
}
} catch (error) {
// Fallback to local copy if available
const srcPath = path.join(__dirname, '..', file.name);
const destPath = path.join(process.cwd(), file.name);
if (fs.existsSync(srcPath)) {
fs.copyFileSync(srcPath, destPath);
if (file.name.endsWith('.sh')) {
fs.chmodSync(destPath, 0o755);
}
}
}
}
// Create container configurations based on strategy
await createContainerConfigurations(config);
// Create project-specific files based on type
await createProjectFiles(config);
// Create package.json if it doesn't exist
if (!fs.existsSync('package.json')) {
const packageJson = generatePackageJson(config);
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2));
}
// Create README
const readme = generateReadme(config);
fs.writeFileSync('README.md', readme);
// Create AI context file if requested
if (config.aiContext) {
if (!fs.existsSync('.ai')) {
fs.mkdirSync('.ai');
}
const aiContext = generateAIContext(config);
fs.writeFileSync('.ai/context.md', aiContext);
// Create additional AI context files
const recentWork = generateRecentWork(config);
fs.writeFileSync('.ai/recent-work.md', recentWork);
const preferences = generatePreferences(config);
fs.writeFileSync('.ai/preferences.md', preferences);
}
spinner.succeed(`Project "${config.projectName}" created successfully!`);
console.log(chalk.green.bold('\\nš Setup Complete!'));
console.log(chalk.yellow('Next steps:'));
console.log(chalk.gray(` 1. cd ${config.projectName}`));
console.log(chalk.gray(' 2. Open in VS Code with Dev Containers extension'));
console.log(chalk.gray(' 3. Run ./universal-setup.sh to install tools'));
console.log(chalk.gray(' 4. Start coding! š'));
} catch (error) {
spinner.fail('Setup failed: ' + error.message);
throw error;
}
}
function generateDevcontainerConfig(config) {
const base = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'devcontainer.universal.json'), 'utf8'));
const strategy = config.strategy;
const includeTools = strategy.includeTools || {};
// Customize based on project type and strategy
base.name = `${config.projectName} Dev Environment`;
// Configure container setup based on strategy
if (strategy.containerStrategy === 'docker-compose') {
base.dockerComposeFile = 'docker-compose.yml';
base.service = 'app'; // Primary service for development
base.workspaceFolder = '/app';
delete base.build; // Remove build config when using docker-compose
} else if (strategy.containerStrategy === 'docker') {
base.build = {
dockerfile: 'Dockerfile',
context: '.'
};
}
// Only include heavy features in DevContainer (not production containers)
if (strategy.containerStrategy === 'devcontainer') {
// DevContainer gets all tools since it's development-only
base.features = base.features || {};
if (includeTools.cloudClis) {
base.features["ghcr.io/devcontainers/features/github-cli:1"] = {};
base.features["ghcr.io/devcontainers/features/google-cloud-cli:1"] = {};
}
if (includeTools.heavyTools && config.features && config.features.includes('playwright')) {
base.features["ghcr.io/devcontainers/features/playwright:1"] = {};
}
} else {
// Production containers: remove heavy features
base.features = {
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/devcontainers/features/git:1": {}
};
if (config.projectType === 'python') {
base.features["ghcr.io/devcontainers/features/python:1"] = {};
}
}
if (config.projectType === 'react') {
const backend = config.backend || 'none';
// Update port forwarding based on backend
if (backend === 'express') {
base.forwardPorts = [3000, 3001, 5432]; // Client, server, and database ports
base.portsAttributes = {
"3000": { "label": "React Client" },
"3001": { "label": "Express Server" },
"5432": { "label": "PostgreSQL Database" }
};
if (strategy.containerStrategy === 'docker-compose') {
base.service = 'client'; // Use client service for development
}
} else if (backend === 'nextjs') {
base.forwardPorts = [3000];
base.portsAttributes = {
"3000": { "label": "Next.js App" }
};
} else if (backend === 'firebase') {
base.forwardPorts = [3000, 5001, 9099]; // React, Firebase emulator ports
base.portsAttributes = {
"3000": { "label": "React App" },
"5001": { "label": "Firebase Functions" },
"9099": { "label": "Firebase Auth" }
};
} else {
base.forwardPorts = [3000];
base.portsAttributes = {
"3000": { "label": "React App" }
};
}
// Add React-specific extensions
base.customizations.vscode.extensions.push(
'ES7+ React/Redux/React-Native snippets',
'Auto Rename Tag',
'Bracket Pair Colorizer'
);
}
if (config.projectType === 'python') {
base.customizations.vscode.extensions.push(
'ms-python.python',
'ms-python.pylint',
'ms-python.black-formatter'
);
if (config.includeMl) {
base.customizations.vscode.extensions.push(
'ms-toolsai.jupyter',
'ms-python.vscode-pylance'
);
}
}
if (config.features && config.features.includes('playwright')) {
base.containerEnv.PLAYWRIGHT_BROWSERS_PATH = '/usr/bin';
base.containerEnv.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = '/usr/bin/chromium';
}
// Add environment-specific settings
if (strategy.environmentConfigs.length > 1) {
base.containerEnv.NODE_ENV = 'development';
}
// Add AI context persistence if enabled
if (config.aiContext) {
// Add volume mounts for AI tool persistence
base.mounts = base.mounts || [];
base.mounts.push(
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.config/claude-code,target=/home/vscode/.config/claude-code,type=bind",
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.config/gemini,target=/home/vscode/.config/gemini,type=bind"
);
// Add container environment variables for AI context
base.containerEnv = base.containerEnv || {};
base.containerEnv.AI_CONTEXT_DIR = "/workspace/.ai";
base.containerEnv.AI_CONTEXT_FILE = "/workspace/.ai/context.md";
// Add post-create command to display AI context info
base.postCreateCommand = base.postCreateCommand || "";
base.postCreateCommand += " && echo 'š§ AI Context created: .ai/ folder with context.md' && echo 'š Claude/Gemini settings will persist across container rebuilds'";
}
return base;
}
function generatePackageJson(config) {
const base = {
name: config.projectName.toLowerCase().replace(/\s+/g, '-'),
version: '1.0.0',
private: true,
scripts: {}
};
switch (config.projectType) {
case 'react':
const backend = config.backend || 'none';
if (backend === 'nextjs') {
base.scripts = {
dev: 'next dev',
build: 'next build',
start: 'next start',
lint: 'next lint'
};
base.dependencies = {
next: '^14.0.0',
react: '^18.2.0',
'react-dom': '^18.2.0'
};
base.devDependencies = {
eslint: '^8.0.0',
'eslint-config-next': '^14.0.0'
};
} else if (backend === 'express') {
base.scripts = {
dev: 'concurrently "npm run server" "npm run client"',
server: 'cd server && nodemon index.js',
client: 'cd client && npm start',
build: 'cd client && npm run build',
start: 'cd server && node index.js',
'install-deps': 'npm install && cd client && npm install && cd ../server && npm install'
};
base.dependencies = {
concurrently: '^8.0.0'
};
base.devDependencies = {
nodemon: '^3.0.0'
};
} else if (backend === 'firebase') {
base.scripts = {
start: 'react-scripts start',
build: 'react-scripts build',
test: 'react-scripts test',
dev: 'npm start',
'firebase:serve': 'firebase emulators:start',
'firebase:deploy': 'npm run build && firebase deploy'
};
base.dependencies = {
react: '^18.2.0',
'react-dom': '^18.2.0',
'react-scripts': '5.0.1',
firebase: '^10.0.0'
};
base.devDependencies = {
'firebase-tools': '^12.0.0'
};
} else if (backend === 'serverless') {
base.scripts = {
start: 'react-scripts start',
build: 'react-scripts build',
test: 'react-scripts test',
dev: 'npm start',
'vercel:dev': 'vercel dev',
'vercel:deploy': 'npm run build && vercel --prod'
};
base.dependencies = {
react: '^18.2.0',
'react-dom': '^18.2.0',
'react-scripts': '5.0.1'
};
base.devDependencies = {
vercel: '^32.0.0'
};
} else {
// Frontend-only React
base.scripts = {
start: 'react-scripts start',
build: 'react-scripts build',
test: 'react-scripts test',
dev: 'npm start'
};
base.dependencies = {
react: '^18.2.0',
'react-dom': '^18.2.0',
'react-scripts': '5.0.1'
};
}
break;
case 'node':
base.scripts = {
start: 'node server.js',
dev: 'nodemon server.js',
test: 'jest'
};
base.dependencies = {
express: '^4.18.0'
};
base.devDependencies = {
nodemon: '^3.0.0'
};
break;
case 'full-stack':
base.scripts = {
start: 'npm run server',
dev: 'concurrently "npm run server" "npm run client"',
server: 'node backend/server.js',
client: 'cd frontend && npm start',
build: 'cd frontend && npm run build'
};
base.dependencies = {
express: '^4.18.0',
concurrently: '^8.0.0'
};
break;
}
return base;
}
function generateReadme(config) {
const strategy = config.strategy || {};
const backend = config.backend || 'none';
let containerSection = '';
let deploymentSection = '';
let projectStructure = '';
// Generate container-specific documentation
if (strategy.containerStrategy === 'docker-compose') {
containerSection = `### Docker Compose (Multi-Service)
1. **Development**: \`docker-compose up\`
2. **VS Code**: Open in DevContainer (uses docker-compose)
3. **Production**: \`docker-compose -f docker-compose.prod.yml up\`
Services:
- **Client**: React frontend (port 3000)
- **Server**: Express API (port 3001)
- **Database**: PostgreSQL (port 5432)`;
projectStructure = `\`\`\`
${config.projectName}/
āāā .devcontainer/ # VS Code dev container configuration
āāā client/ # React frontend
ā āāā src/
ā āāā package.json
ā āāā Dockerfile
āāā server/ # Express backend
ā āāā index.js
ā āāā package.json
ā āāā Dockerfile
āāā docker-compose.yml # Multi-service container config
āāā .env.development # Development environment variables
āāā .env.staging # Staging environment variables
āāā .env.production # Production environment variables
āāā k8s/ # Kubernetes deployment configs
\`\`\``;
} else if (strategy.containerStrategy === 'docker') {
containerSection = `### Docker (Single Service)
1. **Development**: \`docker build -t ${config.projectName.toLowerCase()} . && docker run -p 3000:3000 ${config.projectName.toLowerCase()}\`
2. **VS Code**: Open in DevContainer
3. **Production**: Uses optimized multi-stage build`;
projectStructure = `\`\`\`
${config.projectName}/
āāā .devcontainer/ # VS Code dev container configuration
āāā src/ # Application source code
āāā Dockerfile # Production-ready container config
āāā .dockerignore # Docker build exclusions
āāā .env.development # Development environment variables
āāā k8s/ # Kubernetes deployment configs
\`\`\``;
} else {
containerSection = `### DevContainer Only
1. **VS Code**: Open in DevContainer (recommended)
2. **Local**: Run \`npm install && npm run dev\`
3. **Deploy**: Platform-specific (${strategy.deploymentStrategy})`;
projectStructure = `\`\`\`
${config.projectName}/
āāā .devcontainer/ # VS Code dev container configuration
āāā src/ # Application source code
āāā universal-setup.sh # Environment setup script
āāā ${backend === 'firebase' ? 'firebase.json' : backend === 'serverless' ? 'vercel.json' : 'package.json'}
\`\`\``;
}
// Generate deployment-specific documentation
if (strategy.deploymentStrategy === 'containerized') {
deploymentSection = `
## š Deployment (Containerized)
### Kubernetes
\`\`\`bash
kubectl apply -f k8s/
\`\`\`
### Docker
\`\`\`bash
docker build -t ${config.projectName.toLowerCase()} .
docker run -p 3000:3000 ${config.projectName.toLowerCase()}
\`\`\``;
} else if (strategy.deploymentStrategy === 'serverless') {
deploymentSection = `
## š Deployment (Serverless)
### ${backend === 'firebase' ? 'Firebase' : 'Vercel/Netlify'}
\`\`\`bash
${backend === 'firebase' ? 'npm run firebase:deploy' : 'npm run vercel:deploy'}
\`\`\``;
} else if (strategy.deploymentStrategy === 'static') {
deploymentSection = `
## š Deployment (Static)
### Build and Deploy
\`\`\`bash
npm run build
# Deploy build/ folder to your hosting provider
\`\`\``;
}
return `# ${config.projectName}
${config.projectType.charAt(0).toUpperCase() + config.projectType.slice(1)} project with universal development environment.
${backend !== 'none' ? `**Backend**: ${backend.charAt(0).toUpperCase() + backend.slice(1)}` : ''}
**Container Strategy**: ${strategy.containerStrategy || 'devcontainer'}
**Deployment Strategy**: ${strategy.deploymentStrategy || 'static'}
**Tool Installation**: ${strategy.installLocation || 'host'} (${strategy.includeTools?.aiClis ? 'includes AI/Cloud CLIs' : 'lightweight setup'})
## š Quick Start
${containerSection}
### Manual Setup
1. Run the setup script:
\`\`\`bash
./universal-setup.sh
\`\`\`
2. Install dependencies:
\`\`\`bash
${strategy.containerStrategy === 'docker-compose' ? 'npm run install-deps' : 'npm install'}
\`\`\`
3. Start development:
\`\`\`bash
npm run dev
\`\`\`
## š ļø Available Tools
### Essential Tools (Always Included)
- **Node.js & npm**: JavaScript runtime and package manager
- **Git**: Version control system
- **Docker**: Container platform (when using containers)
${config.projectType === 'python' ? '- **Python**: Python runtime and pip package manager' : ''}
### Context-Aware Tool Installation
The system intelligently includes tools based on your project setup:
${strategy.includeTools?.aiClis ? `
#### AI & Cloud CLI Tools (Host Installation)
- **Claude CLI**: \`claude\` - AI-powered development assistant
- **Gemini CLI**: \`gemini\` - Google's AI CLI tool` : ''}
${strategy.includeTools?.cloudClis ? `
#### Cloud & Deployment Tools
- **GitHub CLI**: \`gh\` - GitHub command line interface
- **Google Cloud CLI**: \`gcloud\` - Cloud deployment and management` : ''}
${strategy.includeTools?.heavyTools ? `
#### Development Tools (Development Only)
- **Playwright**: Web automation and testing
- **Browser runtimes**: Chromium, Firefox` : ''}
${!strategy.includeTools?.aiClis ? `
#### Lightweight Container Setup
This project uses a **lightweight container configuration** that excludes heavy CLI tools to optimize:
- **Container size**: Faster builds and deployments
- **Security**: Minimal attack surface
- **Performance**: Reduced memory and CPU usage
*Heavy tools like Claude CLI, Google Cloud CLI are installed on the host system instead.*` : ''}
## š¦ Project Structure
${projectStructure}
## š Environment Configuration
This project uses environment-specific configurations:
${strategy.environmentConfigs && strategy.environmentConfigs.length > 1 ?
strategy.environmentConfigs.map(env => `- **${env}**: \`.env.${env}\``).join('\n') :
'- **development**: Local development settings'
}
${deploymentSection}
## š§ Container Configuration
Edit container settings:
- **DevContainer**: \`.devcontainer/devcontainer.json\`
${strategy.containerStrategy === 'docker-compose' ? '- **Docker Compose**: `docker-compose.yml`' : ''}
${strategy.containerStrategy === 'docker' ? '- **Dockerfile**: `Dockerfile`' : ''}
## š Documentation
For more information, see the [Universal Dev Environment documentation](https://github.com/nhangen/universal-dev-env).
---
Generated with Universal Dev Environment v${packageJson.version}
**Configuration**: ${JSON.stringify(strategy, null, 2)}
`;
}
function generateAIContext(config) {
const strategy = config.strategy || {};
const backend = config.backend || 'none';
const timestamp = new Date().toISOString().split('T')[0];
return `# AI Context for ${config.projectName}
> **š¤ AI Assistant Instructions**: Please read this entire file before responding to any questions about this project. This provides essential context for maintaining consistency across Claude, Gemini, and GitHub Copilot.
>
> **Additional Context Files**:
> - \`.ai/recent-work.md\` - Latest development sessions and changes
> - \`.ai/preferences.md\` - Coding patterns and project preferences
## š Project Overview
**Project**: ${config.projectName}
**Type**: ${config.projectType.charAt(0).toUpperCase() + config.projectType.slice(1)}
${backend !== 'none' ? `**Backend**: ${backend.charAt(0).toUpperCase() + backend.slice(1)}` : ''}
**Container Strategy**: ${strategy.containerStrategy || 'devcontainer'}
**Deployment Strategy**: ${strategy.deploymentStrategy || 'static'}
**Tool Installation**: ${strategy.installLocation || 'host'} (${strategy.includeTools?.aiClis ? 'includes AI/Cloud CLIs' : 'lightweight setup'})
**Created**: ${timestamp}
## š Quick Start Prompt
**Use this when starting new conversations:**
"This is a ${config.projectType}${backend !== 'none' ? ` + ${backend}` : ''} project using ${strategy.containerStrategy || 'devcontainer'} strategy. ${strategy.includeTools?.aiClis ? 'Full AI/Cloud tooling available.' : 'Lightweight container setup excludes heavy CLI tools.'} Current focus: ${backend === 'express' ? 'Full-stack development with React frontend and Express backend.' : backend === 'nextjs' ? 'Next.js full-stack development.' : backend === 'firebase' ? 'Serverless development with Firebase Functions.' : 'Frontend development.'}"
## šļø Architecture & Setup
### Container Configuration
- **Strategy**: ${strategy.containerStrategy || 'devcontainer'}
- **Environment Configs**: ${strategy.environmentConfigs ? strategy.environmentConfigs.join(', ') : 'development'}
- **Port Forwarding**: ${backend === 'express' ? 'Client (3000), Server (3001), Database (5432)' : backend === 'nextjs' ? 'App (3000)' : backend === 'firebase' ? 'App (3000), Functions (5001), Auth (9099)' : 'App (3000)'}
### Development Tools
${strategy.includeTools?.aiClis ? `
#### AI & Cloud Tools (Host Installation)
- Claude CLI, Gemini CLI available on host
- GitHub CLI, Google Cloud CLI for deployment` : ''}
${strategy.includeTools?.heavyTools ? `
#### Heavy Development Tools
- Playwright for web automation
- Browser runtimes included` : ''}
${!strategy.includeTools?.aiClis ? `
#### Lightweight Container Setup
**Note**: This project uses lightweight containers that exclude heavy CLI tools to optimize:
- Container size (50-80% reduction)
- Build/deployment speed
- Security (minimal attack surface)
*Heavy tools like Claude CLI, Google Cloud CLI are on the host system, not in containers.*` : ''}
## š§ Technical Stack
**Frontend**: ${config.projectType === 'react' ? 'React 18.2.0' : config.projectType === 'node' ? 'Node.js Server' : config.projectType === 'python' ? 'Python 3.11' : 'Web Application'}
${backend === 'express' ? '**Backend**: Express.js 4.18.0 with PostgreSQL database' : ''}
${backend === 'nextjs' ? '**Framework**: Next.js 14.0.0 (full-stack)' : ''}
${backend === 'firebase' ? '**Backend**: Firebase Functions with Firestore' : ''}
${backend === 'serverless' ? '**Functions**: Vercel/Netlify Edge Functions' : ''}
${config.includeMl ? '**ML Libraries**: NumPy, Pandas, Scikit-learn, Jupyter' : ''}
### File Structure
\`\`\`
${config.projectName}/
${backend === 'express' ? `āāā client/ # React frontend
ā āāā src/
ā āāā package.json
ā āāā Dockerfile # (if using docker-compose)
āāā server/ # Express backend
ā āāā index.js
ā āāā package.json
ā āāā Dockerfile # (if using docker-compose)
āāā docker-compose.yml # Multi-service setup` :
backend === 'nextjs' ? `āāā pages/ # Next.js pages
ā āāā index.js
ā āāā api/ # API routes
āāā Dockerfile # Production build` :
backend === 'firebase' ? `āāā src/ # React frontend
āāā functions/ # Firebase Functions
ā āāā index.js
ā āāā package.json
āāā firebase.json # Firebase config` : `āāā src/ # Application code`}
āāā .devcontainer/ # VS Code dev container config
${strategy.environmentConfigs && strategy.environmentConfigs.length > 1 ?
strategy.environmentConfigs.map(env => `āāā .env.${env} # ${env.charAt(0).toUpperCase() + env.slice(1)} environment`).join('\n') :
'āāā .env # Environment variables'
}
${strategy.deploymentStrategy === 'containerized' ? 'āāā k8s/ # Kubernetes configs' : ''}
\`\`\`
## š» Development Commands
**Start Development:**
\`\`\`bash
${strategy.containerStrategy === 'docker-compose' ? 'docker-compose up' :
strategy.containerStrategy === 'docker' ? 'docker build -t ' + config.projectName.toLowerCase() + ' . && docker run -p 3000:3000 ' + config.projectName.toLowerCase() :
'npm run dev'}
\`\`\`
**VS Code DevContainer:**
1. Open project in VS Code
2. Install "Dev Containers" extension
3. Click "Reopen in Container"
4. Container will auto-configure
**Installation:**
\`\`\`bash
${backend === 'express' ? 'npm run install-deps # Installs client, server, and root dependencies' : 'npm install'}
\`\`\`
## š§ AI Tool Persistence Instructions
### Claude CLI Persistence
**Settings Location**: \`~/.config/claude-code/settings.local.json\`
**Container Mount**: Ensure this path is mounted as volume in DevContainer
**Authentication**: Re-authenticate if settings don't persist: \`claude auth login\`
### Gemini CLI Persistence
**Settings Location**: \`~/.config/gemini/settings.json\`
**State File**: \`~/.config/gemini/state_snapshot.xml\`
**Container Mount**: Both files need volume mounting for persistence
**Authentication**: Check \`gemini auth status\` if login is lost
### GitHub Copilot Persistence
**VS Code Settings**: Automatically persists via VS Code settings sync
**Authentication**: Uses VS Code's GitHub authentication (should persist)
### DevContainer Volume Mounts
**Add to .devcontainer/devcontainer.json:**
\`\`\`json
{
"mounts": [
"source=\${localEnv:HOME}\${localEnv:USERPROFILE}/.config/claude-code,target=/home/vscode/.config/claude-code,type=bind",
"source=\${localEnv:HOME}\${localEnv:USERPROFILE}/.config/gemini,target=/home/vscode/.config/gemini,type=bind"
]
}
\`\`\`
## š Recent Decisions & Context
### Container Strategy Decision
- **Chosen**: ${strategy.containerStrategy || 'devcontainer'} for ${strategy.containerStrategy === 'docker-compose' ? 'multi-service architecture' : strategy.containerStrategy === 'docker' ? 'single-service optimization' : 'development simplicity'}
- **Reasoning**: ${strategy.containerStrategy === 'docker-compose' ? 'Complex multi-service setup requires orchestration' : strategy.containerStrategy === 'docker' ? 'Single service benefits from optimized production builds' : 'Simple pro