UNPKG

genezio

Version:

Command line utility to interact with Genezio infrastructure.

401 lines (400 loc) 14.5 kB
import fs from "fs"; import os from "os"; import path from "path"; import FileDetails from "../models/fileDetails.js"; import glob from "glob"; import archiver from "archiver"; import { parse } from "yaml"; import { exit } from "process"; import awsCronParser from "aws-cron-parser"; import { log } from "./logging.js"; import { promises as fsPromises } from "fs"; import { debugLogger } from "./logging.js"; import dotenv from "dotenv"; import fsExtra from "fs-extra"; import { getGlobalPackageManager } from "../packageManagers/packageManager.js"; import { UserError } from "../errors.js"; export async function getAllFilesRecursively(folderPath) { let files = []; const items = await fsPromises.readdir(folderPath, { withFileTypes: true }); for (const item of items) { if (item.isDirectory()) { files = [...files, ...(await getAllFilesRecursively(path.join(folderPath, item.name)))]; } else { files.push(path.join(folderPath, item.name)); } } return files; } export function ensureRelativePaths(file) { if (file.startsWith("!")) { // negated patterns are passed through return "!" + ensureRelativePaths(file.substring(1)); } if (file.endsWith(path.sep)) { // user probably wants to include all files in the directory return ensureRelativePaths(path.join(file, "./**")); } const absolutePath = path.resolve(file); const relativePath = path.relative(".", absolutePath); return relativePath; } export async function getAllFilesFromPath(inputPath, recursive = true) { // get genezioIgnore file let genezioIgnore = []; const genezioIgnorePath = path.join(inputPath, ".genezioignore"); if (fs.existsSync(genezioIgnorePath)) { const genezioIgnoreContent = await readUTF8File(genezioIgnorePath); genezioIgnore = genezioIgnoreContent .split(os.EOL) .filter((line) => line !== "" && !line.startsWith("#")); } genezioIgnore = genezioIgnore.map((p) => ensureRelativePaths(p)); return new Promise((resolve, reject) => { let pattern; if (recursive) { pattern = `**`; } else { pattern = `*`; } glob(pattern, { dot: true, ignore: genezioIgnore, cwd: inputPath, }, (err, files) => { if (err) { reject(err); } const fileDetails = files.map((file) => { return { name: path.parse(file).name, extension: path.parse(file).ext, path: file, filename: file, }; }); resolve(fileDetails); }); }); } export async function getAllFilesFromCurrentPath(recursive = true) { return getAllFilesFromPath(process.cwd(), recursive); } export async function zipDirectory(sourceDir, outPath, includeHiddenFiles = false, exclusion = [], reinclusion = []) { const archive = archiver("zip", { zlib: { level: 9 } }); const stream = fs.createWriteStream(outPath); if (!exclusion) { exclusion = []; } return new Promise((resolve, reject) => { archive.on("error", (err) => reject(err)); archive.pipe(stream); archive.glob("**/*", { cwd: sourceDir, dot: includeHiddenFiles, // skip deals with directories, it doesn't allow any children to be returned skip: exclusion, // ignore deals with files, it doesn't prevent files from folder content to be returned ignore: exclusion, }); if (reinclusion.length > 0) { archive.glob("**/*", { cwd: sourceDir, dot: includeHiddenFiles, pattern: reinclusion, }); } stream.on("close", () => resolve()); archive.finalize(); }); } export async function zipFile(sourcePath, outPath) { const archive = archiver("zip", { zlib: { level: 9 } }); const stream = fs.createWriteStream(outPath); return new Promise((resolve, reject) => { archive .file(sourcePath, { name: path.basename(sourcePath) }) .on("error", (err) => reject(err)) .pipe(stream); stream.on("close", () => resolve()); archive.finalize(); }); } export async function zipDirectoryToDestinationPath(sourceDir, destinationPath, outPath, includeHiddenFiles = false, exclusions = []) { const archive = archiver("zip", { zlib: { level: 9 } }); const stream = fs.createWriteStream(outPath); return new Promise((resolve, reject) => { archive.on("error", (err) => reject(err)).pipe(stream); // Add files to the archive, excluding specified patterns archive.glob("**/*", { cwd: sourceDir, ignore: exclusions, dot: includeHiddenFiles, }, { prefix: destinationPath }); stream.on("close", () => resolve()); archive.finalize(); }); } export async function fileExists(filePath) { return new Promise((resolve) => { fs.stat(filePath, (exists) => { if (exists == null) { return resolve(true); } else if (exists.code === "ENOENT") { return resolve(false); } else { return resolve(false); } }); }); } export async function isDirectoryEmpty(directoryPath) { return new Promise((resolve) => { fs.readdir(directoryPath, (error, files) => { if (error) { resolve(true); } resolve(files.length === 0); }); }); } export async function directoryContainsHtmlFiles(directoryPath) { return new Promise((resolve, reject) => { fs.readdir(directoryPath, (error, files) => { if (error) { reject(error); } resolve(files.some((file) => file.endsWith(".html"))); }); }); } export async function getFileSize(filePath) { return new Promise((resolve, reject) => { fs.stat(filePath, (error, stats) => { if (error) { reject(error); } resolve(stats.size); }); }); } export async function getBundleFolderSizeLimit(directoryPath) { const files = await getAllFilesRecursively(directoryPath); const totalSize = files.reduce((acc, file) => { let size; try { size = fs.statSync(file).size; } catch (error) { debugLogger.debug(`Error getting size of file ${file}: ${error}`); return acc; } return acc + size; }, 0); debugLogger.debug(`Total size of the bundle: ${totalSize} bytes`); return totalSize; } export async function directoryContainsIndexHtmlFiles(directoryPath) { return new Promise((resolve, reject) => { fs.readdir(directoryPath, (error, files) => { if (error) { reject(error); } resolve(files.some((file) => file === "index.html")); }); }); } /** * Deletes a folder and its contents. * If the DEBUG environment variable is set to true, the folder will not be removed. * @param folderPath - The path of the folder to delete. * @returns A promise that resolves when the folder is deleted. */ export async function deleteFolder(folderPath) { if (process.env["DEBUG"] === "true") { debugLogger.debug(`DEBUG is set to true. Skipping deletion of ${folderPath}.`); return; } return fs.rmSync(folderPath, { recursive: true, force: true }); } /** * Creates a temporary folder with a given name or a random name of 6 characters if no name is provided. * The folder is created inside a parent folder with a unique name based on the current process ID. * If the folder already exists, it will not be created again. * @param name - Optional name for the temporary folder. * @param shouldDeleteContents - Optional flag to delete the contents of the folder if it already exists. * @returns A promise that resolves with the path of the created folder. */ export async function createTemporaryFolder(name, shouldDeleteContents) { return new Promise((resolve, reject) => { const folderName = `genezio-${process.pid}`; if (!fs.existsSync(path.join(os.tmpdir(), folderName))) { fs.mkdirSync(path.join(os.tmpdir(), folderName)); } if (name === undefined) { // Generate a random name of 6 characters name = Math.random().toString(36).substring(2, 8); } const tempFolder = path.join(os.tmpdir(), folderName, name); if (fs.existsSync(tempFolder)) { if (shouldDeleteContents) { fs.rmSync(tempFolder, { recursive: true }); } else { resolve(tempFolder); return; } } fs.mkdir(tempFolder, (error) => { if (error) { reject(error); } resolve(tempFolder); }); }); } export async function createLocalTempFolder(name, shouldDeleteContents) { return new Promise((resolve, reject) => { const folderName = `local-genezio`; if (!fs.existsSync(path.join(os.tmpdir(), folderName))) { fs.mkdirSync(path.join(os.tmpdir(), folderName)); } // install @types/node in root folder to be accessible by all projects if (!fs.existsSync(path.join(os.tmpdir(), folderName, "node_modules", "@types/node"))) { getGlobalPackageManager().installSync(["@types/node"], path.join(os.tmpdir(), folderName)); } if (name === undefined) { // Generate a random name of 6 characters name = Math.random().toString(36).substring(2, 8); } const tempFolder = path.join(fs.realpathSync(os.tmpdir()), folderName, name); if (fs.existsSync(tempFolder)) { if (shouldDeleteContents) { fsExtra.emptyDirSync(tempFolder); resolve(tempFolder); } else { resolve(tempFolder); return; } } else { fs.mkdir(tempFolder, (error) => { if (error) { reject(error); } resolve(tempFolder); }); } }); } export async function deleteFile(filePath) { return new Promise((resolve, reject) => { try { fs.rmSync(filePath); } catch (error) { reject(error); } resolve(); }); } export function getFileDetails(filePath) { const { ext, name, dir, base } = path.parse(filePath); return new FileDetails(name, ext, dir, base); } export function readUTF8File(filePath) { return new Promise((resolve, reject) => { fs.readFile(filePath, "utf8", function (error, data) { if (error) { reject(error); } resolve(data); }); }); } export function writeToFile(folderPath, filename, content, createPathIfNeeded = false) { return new Promise((resolve, reject) => { const fullPath = path.join(folderPath, filename); if (!fs.existsSync(path.dirname(fullPath)) && createPathIfNeeded) { fs.mkdirSync(path.dirname(fullPath), { recursive: true }); } // create the file if it doesn't exist fs.writeFile(fullPath, content, function (error) { if (error) { reject(error); return; } resolve(); }); }); } export async function checkYamlFileExists(yamlPath = "./genezio.yaml") { if (!(await fileExists(yamlPath))) { return false; } return true; } export async function validateYamlFile() { const configurationFileContentUTF8 = await readUTF8File("./genezio.yaml"); let configurationFileContent = null; try { configurationFileContent = await parse(configurationFileContentUTF8); } catch (error) { throw new UserError(`The configuration yaml file is not valid.\n${error}`); } if (configurationFileContent.classes.length === 0) { log.info("You don't have any classes in your genezio.yaml file. You can add classes using the 'genezio addClass <className> <classType>' command."); exit(1); } for (const elem of configurationFileContent.classes) { if (elem.methods === undefined) { continue; } for (const method of elem.methods) { if (method.type === "cron") { if (method.cronString === undefined) { log.warn(`You need to specify a cronString for the method ${elem.path}.${method.name}.`); exit(1); } else { try { awsCronParser.parse(method.cronString); } catch (error) { log.error(`The cronString ${method.cronString} for the method ${elem.path}.${method.name} is not valid.`); log.error("You must use a 6-part cron expression."); if (error instanceof Error) { log.error(error.toString()); } exit(1); } } } } } } export async function readEnvironmentVariablesFile(envFilePath) { const envVars = new Array(); // Read environment variables from .env file const dotenvVars = dotenv.config({ path: envFilePath }).parsed; if (!dotenvVars) { log.warn(`No environment variables found in ${envFilePath}.`); } for (const [key, value] of Object.entries(dotenvVars || {})) { envVars.push({ name: key, value: value }); } return envVars; } export async function listFilesWithExtension(folderPath, fileExtension) { return fsPromises.readdir(folderPath, { withFileTypes: true }).then((dirents) => { return dirents .filter((dirent) => dirent.isFile() && path.extname(dirent.name) === fileExtension) .map((dirent) => path.join(folderPath, dirent.name)); }); }