UNPKG

@guyycodes/plugin-sdk

Version:

AI-powered plugin scaffolding tool - Create full-stack applications with 7+ AI models, 50+ business integrations, and production-ready infrastructure

353 lines (297 loc) • 12.5 kB
#!/usr/bin/env node // finalCli.js // This is the CLI for scaffolding new plugin projects with the Plugin SDK. const { Command } = require('commander'); const inquirer = require('inquirer'); const fs = require('fs-extra'); const path = require('path'); const { execSync } = require('child_process'); const chalk = require('chalk'); const ora = require('ora'); // Import abstracted modules const { createBackendFiles } = require('./createBackend'); const { createGitHubWorkflow } = require('./githubWorkflow'); const { scaffoldViteClient } = require('./viteClient'); const { createAppConfigNode, createAppConfigPython, createDockerIgnore, createGitIgnore, createReadmeMd } = require('./appConfig'); const program = new Command(); // CLI setup program .name('plugin-sdk') .description('Plugin SDK - Create new plugin projects') .version('1.0.0'); // ============================================================================ // INIT COMMAND - Create new plugin project // ============================================================================ program .command('init') .argument('[project-name]', 'Plugin project name') .option('--project-name <name>', 'Plugin project name') .option('--backend <type>', 'Backend type (nodejs|python)', 'nodejs') .option('--frontend <type>', 'Frontend type (typescript|javascript)', 'typescript') .option('--integration <service>', 'Integration service (quickbooks|calendar|stripe|mailchimp|custom)', 'custom') .description('Initialize a new plugin project') .action(async (projectNameArg, options) => { const projectName = projectNameArg || options.projectName; if (!projectName) { const answers = await inquirer.prompt([ { type: 'input', name: 'projectName', message: 'What is your plugin project name?', validate: (input) => { if (!input.trim()) return 'Project name is required'; if (!/^[a-zA-Z][a-zA-Z0-9-]*$/.test(input)) { return 'Project name must start with a letter and contain only letters, numbers, and hyphens'; } return true; } }, { type: 'list', name: 'backendType', message: 'Choose your backend technology:', choices: [ { name: 'Node.js (Express- API ONLY)', value: 'nodejs' }, { name: 'Python (FastAPI- API + ML/LLM)', value: 'python' } ] }, { type: 'list', name: 'frontendType', message: 'Choose your frontend type:', choices: [ { name: 'TypeScript (Recommended)', value: 'typescript' }, { name: 'JavaScript', value: 'javascript' } ] }, { type: 'list', name: 'integration', message: 'What type of plugin are you creating?', choices: [ { name: 'Custom Plugin (Blank template for your own integration)', value: 'custom' }, // { name: 'QuickBooks (Pre-built accounting integration)', value: 'quickbooks' }, // { name: 'Google Calendar (Pre-built scheduling integration)', value: 'calendar' }, // { name: 'Stripe (Pre-built payments integration)', value: 'stripe' }, // { name: 'Mailchimp (Pre-built email marketing integration)', value: 'mailchimp' } ] } ]); // Use answers when no project name provided await initProject(answers.projectName, answers.backendType, answers.frontendType, answers.integration); } else { // Use the provided project name and options await initProject(projectName, options.backend, options.frontend, options.integration); } }); // ============================================================================ // IMPLEMENTATION FUNCTIONS // ============================================================================ async function initProject(projectName, backendType, frontendType, integration) { console.log(chalk.blue(`šŸš€ Creating plugin project: ${projectName}`)); console.log(chalk.gray(` Backend: ${backendType}`)); console.log(chalk.gray(` Frontend: ${frontendType}`)); console.log(chalk.gray(` Integration: ${integration}`)); const projectPath = path.resolve(process.cwd(), projectName); if (fs.existsSync(projectPath)) { console.error(chalk.red(`Error: Directory ${projectName} already exists`)); process.exit(1); } const spinner = ora('Setting up project structure...').start(); try { // Create project directory and navigate fs.ensureDirSync(projectPath); process.chdir(projectPath); // Create directory structure await createDirectoryStructure(projectPath); spinner.text = 'Creating directory structure...'; // Scaffold Vite client with Module Federation spinner.text = 'Scaffolding Vite client...'; await scaffoldViteClient(projectPath, projectName, frontendType, integration, backendType); // Create backend files spinner.text = `Creating ${backendType} backend...this might take a second or two...`; await createBackendFiles(projectPath, backendType, integration, projectName); // Create configuration files spinner.text = 'Creating configuration files...'; await createConfigFiles(projectPath, projectName, backendType, frontendType, integration); // Create appConfig.json spinner.text = 'Creating appConfig.json...'; if (backendType === 'nodejs') { await createAppConfigNode(projectPath, projectName, backendType, integration); } else { await createAppConfigPython(projectPath, projectName, backendType, integration); } await createDockerIgnore(projectPath, projectName); await createGitIgnore(projectPath, projectName); await createReadmeMd(projectPath, projectName); // Create GitHub Actions workflow spinner.text = 'Creating CI/CD workflow...'; await createGitHubWorkflow(projectPath, projectName, backendType); // Initialize git repository spinner.text = 'Initializing git repository...'; await initializeGit(projectPath); // Install dependencies spinner.text = 'Installing dependencies...'; await installDependencies(projectPath, backendType); spinner.succeed(chalk.green(`āœ… Successfully created ${projectName}!`)); // Show next steps console.log(chalk.yellow('\nšŸ“ Next steps:')); console.log(` 1. ${chalk.cyan(`cd ${projectName}`)}`); console.log(` 2. ${chalk.cyan('Setup Env Variables')} # See README.md for required variables`); console.log(` 3. ${chalk.cyan('npm install')} # Install dependencies in root - control servers from root`); console.log(` 4. ${chalk.cyan('npm run install:server')} # Install dependencies in backend server`); console.log(` 5. ${chalk.cyan('npm run install:client')} # Install dependencies in frontend server`); console.log(` 6. ${chalk.cyan('npm run dev')} # Start both servers (w/concurrently from root)`); console.log(` 7. ${chalk.cyan('npm run dev:server')} # Start backend server`); console.log(` 8. ${chalk.cyan('npm run dev:client')} # Start frontend server`); console.log(` 9. ${chalk.cyan('Api keys included in /config/auth files')}`); console.log(chalk.yellow('\nšŸ”§ Configuration:')); console.log(` • Customize ${chalk.cyan('src/client/src/PluginApp')} for your UI`); console.log(` • Backend runs on ${chalk.cyan('http://localhost:3000')}, Frontend on ${chalk.cyan('http://localhost:5173')}`); console.log(` • API calls use ${chalk.cyan('/api')} prefix (auto-routed in development)`); console.log(` • Update ${chalk.cyan('manifest.json')} with deployment URLs after deploy`); console.log(` • See ${chalk.cyan('Python implementation has live thought streaming + full ML/LLM Templates')}`); console.log(` • See ${chalk.cyan('Typescript implementation is limited to API only (no ML/LLM Templates)')}`); console.log(chalk.yellow('\n🌐 API Routing:')); console.log(` • Development: ${chalk.cyan('/api/health')} → Vite proxy → ${chalk.cyan('localhost:3000/health')}`); console.log(` • Production: ${chalk.cyan('/api/integrations/${projectName}/health')} → Main app → Plugin backend`); } catch (error) { spinner.fail('Failed to create project'); console.error(chalk.red('Error:'), error.message); if (fs.existsSync(projectPath)) { fs.removeSync(projectPath); } process.exit(1); } } // ============================================================================ // HELPER FUNCTIONS // ============================================================================ async function createDirectoryStructure(projectPath) { const dirs = [ 'src/client', 'src/server', '.github/workflows', ]; dirs.forEach(dir => { fs.ensureDirSync(path.join(projectPath, dir)); }); } async function createConfigFiles(projectPath, projectName, backendType, frontendType, integration) { // Root package.json const rootPackageJson = { "name": projectName, "version": "1.0.0", "description": integration === 'custom' ? `Custom plugin` : `${integration} integration plugin`, "scripts": { "dev:client": "cd src/client && npm run dev", "dev:server": backendType === 'nodejs' ? "cd src/server && npm run dev" : "cd src/server && source venv/bin/activate && python main.py", "build:client": "cd src/client && npm run build", "venv": backendType === 'nodejs' ? "echo 'Node.js backend, no venv needed'" : "cd src/server && python3.11 -m venv venv && source venv/bin/activate", "install-uv": backendType === 'nodejs' ? "echo 'Node.js backend, no venv needed'" : "curl -LsSf https://astral.sh/uv/install.sh | sh", "install:client": "cd src/client && npm install", "install:server": backendType === 'nodejs' ? "cd src/server && npm install" : "npm run venv && cd src/server && source venv/bin/activate && pip install -r requirements.txt", "build:server": backendType === 'nodejs' ? "cd src/server && npm run build" : "echo 'Python server ready'", "dev": backendType === 'nodejs' ? "concurrently \"cd src/server && npm run dev\" \"cd src/client && npm run dev\"" : "concurrently \"cd src/client && npm run dev\" \"cd src/server && source venv/bin/activate && python main.py\"", "test:connection": "curl -s http://localhost:3000/health | head -n 1 || echo 'āŒ Backend not running on port 3000'" }, "devDependencies": { "concurrently": "^9.1.2" }, "keywords": ["plugin", integration], "license": "Apache-2.0" }; fs.writeJsonSync(path.join(projectPath, 'package.json'), rootPackageJson, { spaces: 2 }); } async function initializeGit(projectPath) { try { execSync('git init', { cwd: projectPath, stdio: 'pipe' }); const gitignore = `# Dependencies # Dependencies node_modules/ # Environment variables .env .env.local # Build outputs dist/ build/ # IDE files .vscode/ .idea/ *.swp # OS files .DS_Store Thumbs.db # Logs *.log # Model weights and large files - UPDATED PATHS src/server/models/*/model/ src/server/models/*/*/model/ src/server/models/*/model/images_output/ src/server/models/*/images_output/ *.safetensors *.bin *.pt *.pth *.onnx *.h5 *.keras # Virtual environment src/server/venv/ venv/ ENV/ env/ venv # Jupyter notebooks .ipynb_checkpoints/ # pytest .pytest_cache/ # Coverage reports htmlcov/ .coverage .coverage.* coverage.xml *.cover # Python __pycache__/ *.pyc *.pyo *.pyd .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg `; fs.writeFileSync(path.join(projectPath, '.gitignore'), gitignore); execSync('git add .', { cwd: projectPath, stdio: 'pipe' }); execSync('git commit -m "Initial commit: Plugin scaffolding"', { cwd: projectPath, stdio: 'pipe' }); } catch (error) { console.warn(chalk.yellow('Warning: Could not initialize git repository')); } } async function installDependencies(projectPath, backendType) { // Install client dependencies execSync('npm install', { cwd: path.join(projectPath, 'src/client'), stdio: 'inherit' }); if (backendType === 'nodejs') { execSync('npm install', { cwd: path.join(projectPath, 'src/server'), stdio: 'inherit' }); } } // Parse command line arguments program.parse();