UNPKG

genezio

Version:

Command line utility to interact with Genezio infrastructure.

373 lines (372 loc) 15.7 kB
import { promises as fs } from "fs"; import path from "path"; import { EXCLUDED_DIRECTORIES, KEY_DEPENDENCY_FILES } from "./command.js"; import { debugLogger } from "../../utils/logging.js"; export async function isTypescript(contents) { // If there is no package.json, the language is not ts, nor js if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); // Only try to parse tsconfig.json if it exists in contents const tsconfigJsonContent = contents["tsconfig.json"] ? JSON.parse(contents["tsconfig.json"]) : undefined; if (tsconfigJsonContent) { return true; } if (packageJsonContent.main && packageJsonContent.main.endsWith(".ts")) { return true; } // Check for TypeScript-related dependencies const dependencies = packageJsonContent.dependencies || {}; const devDependencies = packageJsonContent.devDependencies || {}; const tsRelatedDeps = ["typescript", "ts-node", "@types/node"]; if (tsRelatedDeps.some((dep) => dep in dependencies || dep in devDependencies)) { return true; } return false; } // We try to find the entry file of the component by checking the following: // 1. Check if the component has a main file in package.json // 2. Check if the component has a file that matches the patterns // 3. Return the default file export async function findEntryFile(componentPath, contents, patterns, defaultFile) { const candidateFile = await getEntryFileFromPackageJson(componentPath, contents); if (candidateFile) { return candidateFile; } const FUNCTION_EXTENSIONS_WITH_TS = ["js", "mjs", "cjs", "py", "ts", "mts", "cts"]; const entryFile = await findFileByPatterns(componentPath, patterns, FUNCTION_EXTENSIONS_WITH_TS); if (entryFile) { return path.relative(componentPath, entryFile); } return defaultFile; } async function findFileByPatterns(directory, patterns, extensions) { const entries = await fs.readdir(directory, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(directory, entry.name); if (entry.isDirectory()) { // Skip excluded directories if (EXCLUDED_DIRECTORIES.includes(entry.name)) continue; // Recursively search within subdirectories const result = await findFileByPatterns(fullPath, patterns, extensions); if (result) return result; } else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(`.${ext}`))) { // Check if the file content matches all given patterns const content = await fs.readFile(fullPath, "utf-8"); const allPatternsMatch = patterns.every((pattern) => pattern.test(content)); if (allPatternsMatch) { return fullPath; } } } return undefined; } // Returns an object containing `candidateFile`: The path to the main file in package.json if it exists. async function getEntryFileFromPackageJson(directory, contents) { if (!contents["package.json"]) { return undefined; } const packageJsonContent = JSON.parse(contents["package.json"]); const mainPath = packageJsonContent.main; if (!mainPath) { return undefined; } const fullPath = path.join(directory, mainPath); try { if (fullPath) await fs.access(fullPath); return mainPath; } catch { debugLogger.debug(`File ${fullPath} does not exist`); return undefined; } } export async function hasPostgresDependency(contents, dependencyFile) { if (!KEY_DEPENDENCY_FILES.includes(dependencyFile)) { return false; } if (!contents[dependencyFile]) { return false; } const jsPostgresIndicators = ["pg", "pg-promise", "postgres", "@vercel/postgres"]; const pythonPostgresIndicators = ["psycopg2", "asyncpg", "py-postgresql"]; const dependencyList = jsPostgresIndicators.concat(pythonPostgresIndicators); return await searchDependency(contents, dependencyFile, dependencyList); } export async function hasMongoDependency(contents, dependencyFile) { if (!KEY_DEPENDENCY_FILES.includes(dependencyFile)) { return false; } if (!contents[dependencyFile]) { return false; } const jsMongoIndicators = ["mongodb", "mongoose", "connect-mongo"]; const pythonMongoIndicators = ["pymongo"]; const dependencyList = jsMongoIndicators.concat(pythonMongoIndicators); return await searchDependency(contents, dependencyFile, dependencyList); } /** * This function receives a dependency file such as package.json or requirements.txt * and a list of dependency such as ["mongodb", "mongoose", "connect-mongo"]. * * It returns true if any of the dependencies are found in the file. * * This is used to determine if a project is using certain services such as Postgres or MongoDB * Can be used for other services too - redis, mysql, kafka etc. */ export async function searchDependency(contents, dependencyFile, dependencyList) { if (!contents[dependencyFile]) { return false; } if (dependencyFile === "package.json") { const packageJsonContent = JSON.parse(contents["package.json"]); return dependencyList.some((indicator) => indicator in (packageJsonContent.dependencies || {}) || indicator in (packageJsonContent.devDependencies || {})); } else if (dependencyFile === "requirements.txt") { const requirementsContent = contents["requirements.txt"]; return requirementsContent .split("\n") .some((line) => dependencyList.some((indicator) => line.trim().startsWith(indicator))); } return false; } // Checks if the project is a Express component // `contents` is a map of important file paths and their contents export async function isExpressBackend(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); // If the project is a serverless-http backend, it should be treated in a different way const isServerlessHttp = "serverless-http" in (packageJsonContent.dependencies || {}); return packageJsonContent ? "express" in (packageJsonContent.dependencies || {}) && !isServerlessHttp : false; } // Checks if the project is a Fastify component // `contents` is a map of important file paths and their contents export async function isFastifyBackend(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); // If the project is a serverless-http backend, it should be treated in a different way const isServerlessHttp = "serverless-http" in (packageJsonContent.dependencies || {}); return packageJsonContent ? "fastify" in (packageJsonContent.dependencies || {}) && !isServerlessHttp : false; } // Checks if the project is a serverless-http component // `contents` is a map of important file paths and their contents export async function isServerlessHttpBackend(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); return packageJsonContent ? "serverless-http" in (packageJsonContent.dependencies || {}) : false; } // Checks if the project is a Genezio Typesafe component export async function isGenezioTypesafe(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); return packageJsonContent ? "@genezio/types" in (packageJsonContent.dependencies || {}) : false; } // Checks if the project is a Next.js component // `contents` is a map of important file paths and their contents export async function isNextjsComponent(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); return packageJsonContent ? "next" in (packageJsonContent.dependencies || {}) : false; } // Checks if the project is a Nest.js component // `contents` is a map of important file paths and their contents export async function isNestjsComponent(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); return packageJsonContent ? "@nestjs/core" in (packageJsonContent.dependencies || {}) : false; } // Checks if the project is a Nuxt component // `contents` is a map of important file paths and their contents export async function isNuxtComponent(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); return packageJsonContent ? "nuxt" in (packageJsonContent.dependencies || {}) || "nuxt" in (packageJsonContent.devDependencies || {}) : false; } // Checks if the project is a Nitro component // `contents` is a map of important file paths and their contents export async function isNitroComponent(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); return packageJsonContent ? "nitro" in (packageJsonContent.dependencies || {}) || "nitropack" in (packageJsonContent.devDependencies || {}) : false; } // Checks if the project is a React component // `contents` is a map of important file paths and their contents export async function isReactComponent(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); const dependencies = packageJsonContent.dependencies || {}; const devDependencies = packageJsonContent.devDependencies || {}; // Return false if it's a react-native project if ("react-native" in dependencies || "react-native" in devDependencies) { return false; } return "react" in dependencies || "react" in devDependencies; } // Checks if the project is a Vite component // `contents` is a map of important file paths and their contents export async function isViteComponent(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); return packageJsonContent ? "vite" in (packageJsonContent.dependencies || {}) || "vite" in (packageJsonContent.devDependencies || {}) : false; } // Checks if the project is a Vue component export async function isVueComponent(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); return packageJsonContent ? "vue" in (packageJsonContent.dependencies || {}) || "vue" in (packageJsonContent.devDependencies || {}) : false; } // Checks if the project is an Angular component export async function isAngularComponent(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); return packageJsonContent ? "@angular/core" in (packageJsonContent.dependencies || {}) || "@angular/core" in (packageJsonContent.devDependencies || {}) : false; } // Checks if the project is a Svelte component export async function isSvelteComponent(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); return packageJsonContent ? "svelte" in (packageJsonContent.dependencies || {}) || "svelte" in (packageJsonContent.devDependencies || {}) : false; } // Checks if the project is a Remix component export async function isRemixComponent(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); return Object.keys(packageJsonContent.dependencies || {}).some((key) => key.startsWith("@remix-run")); } // Checks if the project is an Ember component export async function isEmberComponent(contents) { if (!contents["package.json"]) { return false; } const packageJsonContent = JSON.parse(contents["package.json"]); return packageJsonContent ? "ember-cli" in (packageJsonContent.dependencies || {}) || "ember-cli" in (packageJsonContent.devDependencies || {}) : false; } // Checks if the project is a Python component (presence of 'requirements.txt' or 'pyproject.toml') export function isPythonComponent(contents) { return contents["requirements.txt"] !== undefined || contents["pyproject.toml"] !== undefined; } // Checks if the project is a Golang component (presence of 'go.mod') // `contents` is a map of important file paths and their contents export function isGolangComponent(contents) { if (!contents["go.mod"]) { return false; } const goMod = contents["go.mod"]; return goMod !== undefined; } // Checks if the project is a Docker component (presence of 'Dockerfile') // `contents` is a map of important file paths and their contents export function isContainerComponent(contents) { if (!contents["Dockerfile"]) { return false; } const dockerfile = contents["Dockerfile"]; return dockerfile !== undefined; } // Checks if the project is a Flask component (presence of 'requirements.txt', and 'flask' in 'requirements.txt') export function isFlaskComponent(contents) { if (contents["requirements.txt"]) { return /flask(?:==|$|\s)/i.test(contents["requirements.txt"]); } if (contents["pyproject.toml"]) { const content = contents["pyproject.toml"]; return /\bflask\b/i.test(content); } return false; } // Checks if the project is a Django component (presence of 'requirements.txt', and 'django' in 'requirements.txt') export function isDjangoComponent(contents) { if (contents["requirements.txt"]) { return /django(?:==|$|\s)/i.test(contents["requirements.txt"]); } if (contents["pyproject.toml"]) { const content = contents["pyproject.toml"]; return /\bdjango\b/i.test(content); } return false; } // Checks if the project is a FastAPI component (presence of 'requirements.txt', and 'fastapi' in 'requirements.txt') export function isFastAPIComponent(contents) { if (contents["requirements.txt"]) { return /fastapi(?:==|$|\s)/i.test(contents["requirements.txt"]); } if (contents["pyproject.toml"]) { const content = contents["pyproject.toml"]; return /\bfastapi\b/i.test(content); } return false; } // Checks if the project is a Python function that is compatible with AWS Lambda (presence of 'requirements.txt') export function isPythonLambdaFunction(contents) { return contents["requirements.txt"] !== undefined || contents["pyproject.toml"] !== undefined; } // Checks if the project is a Streamlit component (presence of 'requirements.txt' or 'pyproject.toml' and 'streamlit' in 'requirements.txt' or 'pyproject.toml') export function isStreamlitComponent(contents) { return ((contents["requirements.txt"] !== undefined && contents["requirements.txt"].includes("streamlit")) || (contents["pyproject.toml"] !== undefined && contents["pyproject.toml"].includes("streamlit"))); }