From 01e54494e3f86fd7fb29592d3866364333cae703 Mon Sep 17 00:00:00 2001 From: himanshu8443 Date: Sat, 21 Jun 2025 16:17:22 +0530 Subject: [PATCH] feat: add auto-dev environment with build and server functionality --- auto-dev.js | 220 ++++++++++++++++++++++++++++++++++++++++ build-simple.js | 181 +++++++++++++++++++++++++++++++++ dev-server.js | 178 +++++++++++++++++++++++++++++++++ test-providers.js | 248 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 827 insertions(+) create mode 100644 auto-dev.js create mode 100644 build-simple.js create mode 100644 dev-server.js create mode 100644 test-providers.js diff --git a/auto-dev.js b/auto-dev.js new file mode 100644 index 0000000..a2ddccb --- /dev/null +++ b/auto-dev.js @@ -0,0 +1,220 @@ +#!/usr/bin/env node + +const { spawn } = require("child_process"); +const path = require("path"); +const fs = require("fs"); + +// Colors for console output +const colors = { + reset: "\x1b[0m", + bright: "\x1b[1m", + green: "\x1b[32m", + red: "\x1b[31m", + yellow: "\x1b[33m", + blue: "\x1b[34m", + magenta: "\x1b[35m", + cyan: "\x1b[36m", +}; + +const log = { + info: (msg) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`), + success: (msg) => console.log(`${colors.green}✅${colors.reset} ${msg}`), + error: (msg) => console.log(`${colors.red}❌${colors.reset} ${msg}`), + warning: (msg) => console.log(`${colors.yellow}⚠️${colors.reset} ${msg}`), + server: (msg) => console.log(`${colors.cyan}🌐${colors.reset} ${msg}`), + watch: (msg) => console.log(`${colors.magenta}👀${colors.reset} ${msg}`), +}; + +class AutoDev { + constructor() { + this.processes = new Map(); + this.isShuttingDown = false; + } + + async checkDependencies() { + log.info("Checking dependencies..."); + + const requiredPackages = ["nodemon", "concurrently", "express", "cors"]; + const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8")); + + const missing = requiredPackages.filter( + (pkg) => + !packageJson.dependencies?.[pkg] && !packageJson.devDependencies?.[pkg] + ); + + if (missing.length > 0) { + log.error(`Missing packages: ${missing.join(", ")}`); + log.info("Installing missing packages..."); + await this.runCommand("npm", ["install", ...missing, "--save-dev"]); + } + + log.success("All dependencies are installed"); + } + + async runCommand(command, args, options = {}) { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { + stdio: "inherit", + shell: true, + ...options, + }); + + child.on("close", (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Command failed with code ${code}`)); + } + }); + + child.on("error", reject); + }); + } + async initialBuild() { + log.info("Running initial build..."); + try { + await this.runCommand("node", ["build-simple.js"]); + log.success("Initial build completed"); + } catch (error) { + log.error("Initial build failed:", error.message); + throw error; + } + } + + startWatcher() { + log.watch("Starting file watcher..."); + + const watcher = spawn("npx", ["nodemon"], { + stdio: ["inherit", "pipe", "pipe"], + shell: true, + env: { ...process.env, NODE_ENV: "development" }, + }); + + watcher.stdout.on("data", (data) => { + const output = data.toString().trim(); + if (output) { + console.log(`${colors.magenta}[WATCH]${colors.reset} ${output}`); + } + }); + + watcher.stderr.on("data", (data) => { + const output = data.toString().trim(); + if (output) { + console.log(`${colors.yellow}[WATCH]${colors.reset} ${output}`); + } + }); + + watcher.on("close", (code) => { + if (!this.isShuttingDown) { + log.error(`Watcher exited with code ${code}`); + } + }); + + this.processes.set("watcher", watcher); + return watcher; + } + + startDevServer() { + log.server("Starting development server..."); + + const server = spawn("node", ["dev-server.js"], { + stdio: ["inherit", "pipe", "pipe"], + shell: true, + }); + + server.stdout.on("data", (data) => { + const output = data.toString().trim(); + if (output) { + console.log(`${colors.cyan}[SERVER]${colors.reset} ${output}`); + } + }); + + server.stderr.on("data", (data) => { + const output = data.toString().trim(); + if (output) { + console.log(`${colors.red}[SERVER]${colors.reset} ${output}`); + } + }); + + server.on("close", (code) => { + if (!this.isShuttingDown) { + log.error(`Server exited with code ${code}`); + } + }); + + this.processes.set("server", server); + return server; + } + + setupSignalHandlers() { + const cleanup = () => { + if (this.isShuttingDown) return; + this.isShuttingDown = true; + + console.log("\n"); + log.info("Shutting down auto-dev environment..."); + + for (const [name, process] of this.processes) { + log.info(`Stopping ${name}...`); + process.kill("SIGTERM"); + } + + setTimeout(() => { + log.success("Auto-dev environment stopped"); + process.exit(0); + }, 1000); + }; + + process.on("SIGINT", cleanup); + process.on("SIGTERM", cleanup); + } + + async start() { + console.log(` +${colors.bright}🚀 Vega Providers Auto-Development Environment${colors.reset} + +${colors.cyan}Features:${colors.reset} +• 👀 Auto-watch TypeScript files in /providers +• 🔨 Auto-rebuild on file changes +• 🌐 Development server with hot-reload +• 📊 Real-time build feedback + +${colors.yellow}Press Ctrl+C to stop${colors.reset} +`); + + try { + // Setup signal handlers + this.setupSignalHandlers(); + + // Check dependencies + await this.checkDependencies(); + + // Initial build + await this.initialBuild(); + + // Start watcher and server + this.startWatcher(); + + // Wait a bit before starting server + setTimeout(() => { + this.startDevServer(); + }, 2000); + + log.success("Auto-development environment is running!"); + log.info( + "Make changes to your providers and watch them rebuild automatically" + ); + } catch (error) { + log.error("Failed to start auto-dev environment:", error.message); + process.exit(1); + } + } +} + +// CLI interface +if (require.main === module) { + const autoDev = new AutoDev(); + autoDev.start(); +} + +module.exports = AutoDev; diff --git a/build-simple.js b/build-simple.js new file mode 100644 index 0000000..0fa163a --- /dev/null +++ b/build-simple.js @@ -0,0 +1,181 @@ +const fs = require("fs"); +const path = require("path"); +const { execSync } = require("child_process"); + +// Build configuration +const PROVIDERS_DIR = "./providers"; +const DIST_DIR = "./dist"; + +// Colors for console output +const colors = { + reset: "\x1b[0m", + bright: "\x1b[1m", + green: "\x1b[32m", + red: "\x1b[31m", + yellow: "\x1b[33m", + blue: "\x1b[34m", + magenta: "\x1b[35m", + cyan: "\x1b[36m", +}; + +const log = { + info: (msg) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`), + success: (msg) => console.log(`${colors.green}✅${colors.reset} ${msg}`), + error: (msg) => console.log(`${colors.red}❌${colors.reset} ${msg}`), + warning: (msg) => console.log(`${colors.yellow}⚠️${colors.reset} ${msg}`), + build: (msg) => console.log(`${colors.magenta}🔨${colors.reset} ${msg}`), + file: (msg) => console.log(`${colors.cyan}📄${colors.reset} ${msg}`), +}; + +/** + * Simple and efficient provider builder + */ +class ProviderBuilder { + constructor() { + this.startTime = Date.now(); + this.providers = []; + this.manifest = []; + } + + /** + * Clean the dist directory + */ + cleanDist() { + if (fs.existsSync(DIST_DIR)) { + fs.rmSync(DIST_DIR, { recursive: true, force: true }); + } + fs.mkdirSync(DIST_DIR, { recursive: true }); + log.success("Cleaned dist directory"); + } + + /** + * Discover all provider directories + */ + discoverProviders() { + const items = fs.readdirSync(PROVIDERS_DIR, { withFileTypes: true }); + + this.providers = items + .filter((item) => item.isDirectory()) + .filter((item) => !item.name.startsWith(".")) + .map((item) => item.name); + + log.info( + `Found ${this.providers.length} providers: ${this.providers.join(", ")}` + ); + } + + /** + * Compile all TypeScript files using tsconfig.json + */ + compileAllProviders() { + log.build("Compiling TypeScript files..."); + + try { + // Use TypeScript to compile all files according to tsconfig.json + execSync("npx tsc", { + stdio: "pipe", + encoding: "utf8", + }); + + log.success("TypeScript compilation completed"); + return true; + } catch (error) { + log.error("TypeScript compilation failed:"); + if (error.stdout) { + console.log(error.stdout); + } + if (error.stderr) { + console.log(error.stderr); + } + return false; + } + } + + /** + * Organize compiled files by provider + */ + organizeFiles() { + log.build("Organizing compiled files..."); + + for (const provider of this.providers) { + const providerSrcDir = path.join(PROVIDERS_DIR, provider); + const providerDistDir = path.join(DIST_DIR, provider); + + // Create provider dist directory + if (!fs.existsSync(providerDistDir)) { + fs.mkdirSync(providerDistDir, { recursive: true }); + } + + // Copy compiled JS files + const files = [ + "catalog.js", + "posts.js", + "meta.js", + "stream.js", + "episodes.js", + ]; + let fileCount = 0; + + for (const file of files) { + const srcFile = path.join(DIST_DIR, provider, file); + const destFile = path.join(providerDistDir, file); + + if (fs.existsSync(srcFile)) { + // File already in the right place + fileCount++; + } + } + + if (fileCount > 0) { + log.success(` ${provider}: ${fileCount} modules ready`); + } else { + log.warning(` ${provider}: No modules found`); + } + } + } + + /** + * Build everything + */ + build() { + const isWatchMode = process.env.NODE_ENV === "development"; + + if (isWatchMode) { + console.log( + `\n${colors.cyan}🔄 Auto-build triggered${ + colors.reset + } ${new Date().toLocaleTimeString()}` + ); + } else { + console.log( + `\n${colors.bright}🚀 Starting provider build...${colors.reset}\n` + ); + } + + this.cleanDist(); + this.discoverProviders(); + + const compiled = this.compileAllProviders(); + if (!compiled) { + log.error("Build failed due to compilation errors"); + process.exit(1); + } + + this.organizeFiles(); + + const buildTime = Date.now() - this.startTime; + log.success(`Build completed in ${buildTime}ms`); + + if (isWatchMode) { + console.log(`${colors.green}👀 Watching for changes...${colors.reset}\n`); + } else { + console.log( + `${colors.bright}✨ Build completed successfully!${colors.reset}\n` + ); + } + } +} + +// Run the build +const builder = new ProviderBuilder(); +builder.build(); diff --git a/dev-server.js b/dev-server.js new file mode 100644 index 0000000..5abf989 --- /dev/null +++ b/dev-server.js @@ -0,0 +1,178 @@ +const express = require("express"); +const cors = require("cors"); +const path = require("path"); +const fs = require("fs"); +const { execSync } = require("child_process"); + +/** + * Local development server for testing providers + */ +class DevServer { + constructor() { + this.app = express(); + this.port = 3001; + this.distDir = path.join(__dirname, "dist"); + + this.setupMiddleware(); + this.setupRoutes(); + } + + setupMiddleware() { + // Enable CORS for mobile app + this.app.use( + cors({ + origin: "*", + methods: ["GET", "POST", "OPTIONS"], + allowedHeaders: ["Content-Type", "Authorization"], + }) + ); + + // Serve static files from dist directory + this.app.use("/dist", express.static(this.distDir)); + + // JSON parsing + this.app.use(express.json()); + + // Logging + this.app.use((req, res, next) => { + console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`); + next(); + }); + } + + setupRoutes() { + // Serve manifest.json + this.app.get("/manifest.json", (req, res) => { + const manifestPath = path.join(this.distDir, "manifest.json"); + + if (fs.existsSync(manifestPath)) { + res.sendFile(manifestPath); + } else { + res.status(404).json({ error: "Manifest not found. Run build first." }); + } + }); + + // Serve individual provider files + this.app.get("/dist/:provider/:file", (req, res) => { + const { provider, file } = req.params; + const filePath = path.join(this.distDir, provider, file); + + if (fs.existsSync(filePath)) { + res.sendFile(filePath); + } else { + res.status(404).json({ + error: `File not found: ${provider}/${file}`, + hint: "Make sure to run build first", + }); + } + }); + + // Build endpoint - trigger rebuild + this.app.post("/build", (req, res) => { + try { + console.log("🔨 Triggering rebuild..."); + execSync("node build.js", { stdio: "inherit" }); + res.json({ success: true, message: "Build completed" }); + } catch (error) { + console.error("Build failed:", error); + res.status(500).json({ + success: false, + error: error.message, + }); + } + }); + + // Status endpoint + this.app.get("/status", (req, res) => { + const providers = this.getAvailableProviders(); + res.json({ + status: "running", + port: this.port, + providers: providers.length, + providerList: providers, + buildTime: this.getBuildTime(), + }); + }); + + // List available providers + this.app.get("/providers", (req, res) => { + const providers = this.getAvailableProviders(); + res.json(providers); + }); + + // Health check + this.app.get("/health", (req, res) => { + res.json({ status: "healthy", timestamp: new Date().toISOString() }); + }); + + // 404 handler + this.app.use((req, res) => { + res.status(404).json({ + error: "Not found", + availableEndpoints: [ + "GET /manifest.json", + "GET /dist/:provider/:file", + "POST /build", + "GET /status", + "GET /providers", + "GET /health", + ], + }); + }); + } + + getAvailableProviders() { + if (!fs.existsSync(this.distDir)) { + return []; + } + + return fs + .readdirSync(this.distDir, { withFileTypes: true }) + .filter((item) => item.isDirectory()) + .map((item) => item.name); + } + + getBuildTime() { + const manifestPath = path.join(this.distDir, "manifest.json"); + if (fs.existsSync(manifestPath)) { + const stats = fs.statSync(manifestPath); + return stats.mtime.toISOString(); + } + return null; + } + + start() { + this.app.listen(this.port, "0.0.0.0", () => { + console.log(` +🚀 Vega Providers Dev Server Started! + +📡 Server URL: http://localhost:${this.port} +📱 Mobile URL: http://:${this.port} + +📋 Available endpoints: + • GET /manifest.json - Provider manifest + • GET /dist/:provider/:file - Provider modules + • POST /build - Trigger rebuild + • GET /status - Server status + • GET /providers - List providers + • GET /health - Health check + +💡 Usage: + 1. Run 'node build.js' to build providers + 2. Update vega app to use: http://localhost:${this.port} + 3. Test your providers! + +🔄 Auto-rebuild: POST to /build to rebuild after changes + `); + + // Check if build exists + if (!fs.existsSync(this.distDir)) { + console.log('\n⚠️ No build found. Run "node build.js" first!\n'); + } + }); + } +} + +// Start the server +const server = new DevServer(); +server.start(); diff --git a/test-providers.js b/test-providers.js new file mode 100644 index 0000000..e3c3299 --- /dev/null +++ b/test-providers.js @@ -0,0 +1,248 @@ +const axios = require("axios"); +const fs = require("fs"); +const path = require("path"); + +/** + * Provider testing utility + */ +class ProviderTester { + constructor(serverUrl = "http://localhost:3001") { + this.serverUrl = serverUrl; + this.axios = axios.create({ + baseURL: serverUrl, + timeout: 10000, + }); + } + + /** + * Test server connectivity + */ + async testConnection() { + try { + const response = await this.axios.get("/health"); + console.log("✅ Server connection OK"); + return true; + } catch (error) { + console.error("❌ Server connection failed:", error.message); + return false; + } + } + + /** + * Test manifest endpoint + */ + async testManifest() { + try { + const response = await this.axios.get("/manifest.json"); + const providers = response.data; + + console.log(`✅ Manifest OK - Found ${providers.length} providers:`); + providers.forEach((p) => { + console.log(` 📦 ${p.display_name} (${p.value}) v${p.version}`); + }); + + return providers; + } catch (error) { + console.error("❌ Manifest test failed:", error.message); + return null; + } + } + + /** + * Test individual provider modules + */ + async testProvider(providerName) { + console.log(`\n🧪 Testing provider: ${providerName}`); + + const modules = ["catalog", "posts", "meta", "stream", "episodes"]; + const results = {}; + + for (const module of modules) { + try { + const response = await this.axios.get( + `/dist/${providerName}/${module}.js` + ); + results[module] = { + success: true, + size: response.data.length, + hasExports: response.data.includes("exports."), + }; + console.log(` ✅ ${module}.js (${results[module].size} bytes)`); + } catch (error) { + results[module] = { + success: false, + error: error.response?.status === 404 ? "Not found" : error.message, + }; + const isOptional = module === "episodes"; + const icon = isOptional ? "⚠️ " : "❌"; + console.log( + ` ${icon} ${module}.js - ${results[module].error}${ + isOptional ? " (optional)" : "" + }` + ); + } + } + + return results; + } + + /** + * Test all providers + */ + async testAllProviders() { + console.log("🚀 Starting comprehensive provider test...\n"); + + // Test connection + const connected = await this.testConnection(); + if (!connected) return; + + // Test manifest + const providers = await this.testManifest(); + if (!providers) return; + + // Test each provider + const results = {}; + for (const provider of providers) { + results[provider.value] = await this.testProvider(provider.value); + } + + // Summary + console.log("\n📊 Test Summary:"); + console.log("=".repeat(50)); + + let totalProviders = 0; + let passedProviders = 0; + + for (const [providerName, modules] of Object.entries(results)) { + totalProviders++; + const requiredModules = ["catalog", "posts", "meta", "stream"]; + const passedRequired = requiredModules.every( + (mod) => modules[mod]?.success + ); + + if (passedRequired) { + passedProviders++; + console.log(`✅ ${providerName} - All required modules OK`); + } else { + console.log(`❌ ${providerName} - Missing required modules`); + } + } + + console.log( + `\n📈 Results: ${passedProviders}/${totalProviders} providers passed` + ); + + if (passedProviders === totalProviders) { + console.log("🎉 All providers are ready for testing!"); + } else { + console.log("⚠️ Some providers need attention before testing."); + } + + return results; + } + + /** + * Trigger rebuild on server + */ + async rebuild() { + try { + console.log("🔨 Triggering rebuild..."); + const response = await this.axios.post("/build"); + console.log("✅ Rebuild completed"); + return true; + } catch (error) { + console.error( + "❌ Rebuild failed:", + error.response?.data?.error || error.message + ); + return false; + } + } + + /** + * Get server status + */ + async getStatus() { + try { + const response = await this.axios.get("/status"); + const status = response.data; + + console.log("📊 Server Status:"); + console.log(` 🟢 Status: ${status.status}`); + console.log(` 🔌 Port: ${status.port}`); + console.log(` 📦 Providers: ${status.providers}`); + console.log(` 🕐 Last Build: ${status.buildTime || "Never"}`); + + if (status.providerList.length > 0) { + console.log(" 📋 Available Providers:"); + status.providerList.forEach((p) => console.log(` • ${p}`)); + } + + return status; + } catch (error) { + console.error("❌ Failed to get status:", error.message); + return null; + } + } +} + +/** + * CLI interface + */ +async function main() { + const args = process.argv.slice(2); + const command = args[0] || "test"; + const providerName = args[1]; + + const tester = new ProviderTester(); + + switch (command) { + case "test": + if (providerName) { + await tester.testProvider(providerName); + } else { + await tester.testAllProviders(); + } + break; + + case "status": + await tester.getStatus(); + break; + + case "rebuild": + await tester.rebuild(); + break; + + case "connection": + await tester.testConnection(); + break; + + case "manifest": + await tester.testManifest(); + break; + + default: + console.log(` +Usage: node test-providers.js [command] [provider] + +Commands: + test [provider] - Test all providers or specific provider + status - Show server status + rebuild - Trigger rebuild + connection - Test server connection + manifest - Test manifest endpoint + +Examples: + node test-providers.js # Test all providers + node test-providers.js test vega # Test specific provider + node test-providers.js status # Show status + node test-providers.js rebuild # Rebuild and test + `); + } +} + +if (require.main === module) { + main().catch(console.error); +} + +module.exports = ProviderTester;