anomaly-cli
Version:
A command-line interface tool for anomaly detection and management
227 lines (226 loc) โข 11 kB
JavaScript
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}`);
}
}
});
}