UNPKG

anomaly-cli

Version:

A command-line interface tool for anomaly detection and management

227 lines (226 loc) โ€ข 11 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import fs from "fs/promises"; import fsSync from "fs"; import path from "path"; import chalk from "chalk"; import prompts from "prompts"; import ora from "ora"; import archiver from "archiver"; import { getStorage, ref, uploadBytesResumable } from "firebase/storage"; import { app } from "../firebase/clientApp.js"; const BACKEND_ENDPOINT = "https://anomaly-git-app-apidon.vercel.app/api/createApp"; export function createApp(user) { return __awaiter(this, void 0, void 0, function* () { try { console.log(chalk.blue.bold(`\n๐Ÿš€ Initializing new app creation for ${chalk.cyan(user.email)}\n`)); // Step 1: Get app name from user const appNameResponse = yield prompts({ type: "text", name: "appName", message: "What would you like to name your app?", validate: (value) => value.length < 3 ? "App name must be at least 3 characters long" : value.length > 50 ? "App name must be less than 50 characters" : true, }); if (!appNameResponse.appName) { console.log(chalk.yellow("\n๐Ÿ‘‹ App creation cancelled by user")); return; } const appName = appNameResponse.appName.trim(); console.log(chalk.green(`\nโœ… App name confirmed: ${chalk.bold.white(appName)}`)); // Step 2: Get current directory and create zip const currentDir = process.cwd(); const projectName = path.basename(currentDir); const zipFileName = `${projectName}-${Date.now()}.zip`; const tempZipPath = path.join(currentDir, zipFileName); console.log(chalk.cyan(`\n๐Ÿ“ Preparing project archive for deployment`)); console.log(chalk.gray(` โ””โ”€ Project: ${chalk.white(projectName)}`)); console.log(chalk.gray(` โ””โ”€ Source: ${currentDir}`)); const zipSpinner = ora("๐Ÿ“ฆ Creating project archive (excluding node_modules and .git)...").start(); try { yield createZipFile(currentDir, tempZipPath); zipSpinner.succeed(chalk.green("๐Ÿ“ฆ Project archive created successfully")); } catch (error) { zipSpinner.fail(chalk.red("โŒ Failed to create project archive")); throw error; } // Step 3: Upload to Firebase Storage console.log(chalk.cyan("\nโ˜๏ธ Uploading project archive to cloud storage...")); try { const uploadedFileName = yield uploadToFirebase(tempZipPath, zipFileName, user); console.log(chalk.green(`โœ… Upload completed successfully`)); console.log(chalk.gray(` โ””โ”€ Cloud file: ${chalk.white(uploadedFileName)}`)); // Step 4: Send request to backend console.log(chalk.cyan("\n๐Ÿ”ง Requesting app deployment from backend service...")); const appId = yield createAppInBackend(uploadedFileName, appName, user); console.log(chalk.green.bold("\n๐ŸŽ‰ Application deployed successfully!")); console.log(chalk.gray(` โ””โ”€ App Name: ${chalk.white(appName)}`)); console.log(chalk.gray(` โ””โ”€ App ID: ${chalk.white(appId)}`)); console.log(chalk.gray(` โ””โ”€ Source Archive: ${chalk.white(uploadedFileName)}`)); // Display dashboard URL const dashboardUrl = `https://anomaly-git-app-apidon.vercel.app/dashboard/${appId}`; console.log(chalk.blue.bold(`\n๐ŸŒ Your app dashboard is ready:`)); console.log(chalk.cyan.underline(` ${dashboardUrl}`)); console.log(chalk.gray(`\n๐Ÿ’ก Visit the dashboard URL above to manage your application`)); } finally { // Clean up temporary zip file try { yield fs.unlink(tempZipPath); console.log(chalk.gray("๐Ÿงน Temporary files cleaned up")); } catch (cleanupError) { console.log(chalk.yellow("โš ๏ธ Warning: Could not remove temporary archive file")); } } console.log(chalk.green.bold("\nโœจ App creation completed successfully! ๐Ÿš€\n")); } catch (error) { if (error.name === "ExitPromptError") { console.log(chalk.yellow("\n๐Ÿ‘‹ App creation cancelled by user")); return; } console.error(chalk.red(`\nโŒ App creation failed: ${error.message}`)); console.error(chalk.gray("๐Ÿ’ก Please check your connection and try again")); throw error; } }); } function createZipFile(sourceDir, outputPath) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { const output = fsSync.createWriteStream(outputPath); const archive = archiver("zip", { zlib: { level: 9 }, // Maximum compression }); output.on("close", () => { const sizeInMB = (archive.pointer() / (1024 * 1024)).toFixed(2); console.log(chalk.gray(` ๐Ÿ“Š Archive size: ${sizeInMB} MB`)); resolve(); }); archive.on("error", (err) => { reject(err); }); archive.pipe(output); // Add all files except node_modules and other common excludes const projectName = path.basename(sourceDir); const zipFileName = path.basename(outputPath); archive.glob("**/*", { cwd: sourceDir, ignore: ["node_modules/**", ".git/**", "dist/**", zipFileName], dot: true, }, { prefix: `${projectName}/`, }); archive.finalize(); }); }); } function uploadToFirebase(filePath, fileName, user) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { try { const fileBuffer = yield fs.readFile(filePath); const storagePath = `source_files/${user.uid}/${fileName}`; const storageRef = ref(getStorage(app), storagePath); const metadata = { contentType: "application/zip", }; const uploadTask = uploadBytesResumable(storageRef, fileBuffer, metadata); let progressSpinner; uploadTask.on("state_changed", (snapshot) => { const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; const progressText = `โ˜๏ธ Uploading... ${Math.round(progress)}%`; if (!progressSpinner) { progressSpinner = ora(progressText).start(); } else { progressSpinner.text = progressText; } }, (error) => { if (progressSpinner) progressSpinner.fail(); reject(error); }, () => { if (progressSpinner) { progressSpinner.succeed(chalk.green("โ˜๏ธ Upload completed!")); } resolve(fileName); }); } catch (error) { reject(error); } })); }); } function createAppInBackend(fileName, appName, user) { return __awaiter(this, void 0, void 0, function* () { const backendSpinner = ora("๐Ÿ”ง Communicating with deployment service...").start(); try { // Get the auth token const authToken = yield user.user.getIdToken(); // Create AbortController for timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout const response = yield fetch(BACKEND_ENDPOINT, { method: "POST", headers: { authorization: authToken, "Content-Type": "application/json", }, body: JSON.stringify({ fileName: fileName, appName: appName, }), signal: controller.signal, }); clearTimeout(timeoutId); if (response.ok) { backendSpinner.succeed(chalk.green("๐Ÿ”ง Deployment service responded successfully")); // Parse the response to get the appId const responseData = yield response.json(); const appId = responseData.appId; if (!appId) { throw new Error("Deployment service did not return a valid app ID"); } return appId; } else { // Try to get error message from response let errorMessage = response.statusText; try { const errorData = yield response.text(); errorMessage = errorData; } catch (_a) { // If JSON parsing fails, use statusText } throw new Error(`Deployment service error (${response.status}): ${errorMessage}`); } } catch (error) { backendSpinner.fail(chalk.red("โŒ Deployment service request failed")); if (error.name === "AbortError") { throw new Error("Request timed out after 30 seconds - please check your internet connection"); } else if (error.message.includes("fetch")) { throw new Error("Unable to reach deployment service - please verify your internet connection"); } else { throw new Error(`Deployment request failed: ${error.message}`); } } }); }