feat: add auto-dev environment with build and server functionality

This commit is contained in:
himanshu8443
2025-06-21 16:17:22 +05:30
parent cfc7a37e6a
commit 01e54494e3
4 changed files with 827 additions and 0 deletions

220
auto-dev.js Normal file
View File

@@ -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;

181
build-simple.js Normal file
View File

@@ -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();

178
dev-server.js Normal file
View File

@@ -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://<your-ip>:${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();

248
test-providers.js Normal file
View File

@@ -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;