Skip to main content
Glama
localstack
by localstack
deployment-utils.ts7.01 kB
import { runCommand } from "../../core/command-runner"; import fs from "fs"; import path from "path"; export interface DependencyCheckResult { isAvailable: boolean; tool: string; version?: string; errorMessage?: string; } export type ProjectType = "cdk" | "terraform" | "cloudformation" | "ambiguous" | "unknown"; /** * Check if the required deployment tool (cdklocal or tflocal) is available in the system PATH * @param projectType The type of project requiring either 'cdk' or 'terraform' tooling * @returns Promise with availability status and tool information */ export async function checkDependencies( projectType: "cdk" | "terraform" ): Promise<DependencyCheckResult> { const tool = projectType === "cdk" ? "cdklocal" : "tflocal"; try { const { stdout, error } = await runCommand(tool, ["--version"], { timeout: 10000 }); if (error) throw error; return { isAvailable: true, tool, version: stdout.trim(), }; } catch (error) { const errorMessage = projectType === "cdk" ? `❌ cdklocal is not installed or not available in PATH. Please install aws-cdk-local by following the official documentation: https://github.com/localstack/aws-cdk-local Installation: npm install -g aws-cdk-local aws-cdk After installation, make sure the 'cdklocal' command is available in your PATH.` : `❌ tflocal is not installed or not available in PATH. Please install terraform-local by following the official documentation: https://github.com/localstack/terraform-local Installation: pip install terraform-local After installation, make sure the 'tflocal' command is available in your PATH.`; return { isAvailable: false, tool, errorMessage, }; } } /** * Infer the project type by inspecting the contents of the given directory * @param directory The path to the project directory * @returns Promise with the inferred project type */ export async function inferProjectType(directory: string): Promise<ProjectType> { try { const stats = await fs.promises.stat(directory); if (!stats.isDirectory()) { throw new Error(`Path ${directory} is not a directory`); } const files = await fs.promises.readdir(directory); const hasCdkJson = files.includes("cdk.json"); const hasCdkFiles = files.some( (file) => file.startsWith("cdk.") || file === "app.py" || file === "app.js" || file === "app.ts" ); const hasTerraformFiles = files.some( (file) => file.endsWith(".tf") || file.endsWith(".tf.json") ); const hasCloudFormationTemplates = files.some( (file) => file.endsWith(".yaml") || file.endsWith(".yml") ); const isCdk = hasCdkJson || hasCdkFiles; const isTerraform = hasTerraformFiles; const isCloudFormation = hasCloudFormationTemplates; if ( [isCdk, isTerraform, isCloudFormation].filter(Boolean).length > 1 ) { return "ambiguous"; } else if (isCdk) { return "cdk"; } else if (isTerraform) { return "terraform"; } else if (isCloudFormation) { return "cloudformation"; } else { return "unknown"; } } catch (error) { return "unknown"; } } /** * Validate variables object to prevent command injection * @param variables The variables object to validate * @returns Array of validation errors, empty if valid */ export function validateVariables(variables?: Record<string, string>): string[] { if (!variables) { return []; } const dangerousPatterns = [ ";", // Command separator "&&", // Command chaining "||", // Command chaining "$(", // Command substitution "`", // Command substitution (backticks) "|", // Pipe operator ">", // Output redirection "<", // Input redirection "&", // Background execution "\n", // Newline "\r", // Carriage return ]; const errors: string[] = []; for (const [key, value] of Object.entries(variables)) { for (const pattern of dangerousPatterns) { if (key.includes(pattern)) { errors.push(`Variable key "${key}" contains forbidden character: ${pattern}`); } if (value.includes(pattern)) { errors.push(`Variable value for "${key}" contains forbidden character: ${pattern}`); } } if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(key)) { errors.push(`Variable key "${key}" is not a valid identifier`); } } return errors; } /** * Parse Terraform outputs from JSON format * @param outputJson The JSON string from terraform output -json * @returns Formatted markdown string of outputs */ export function parseTerraformOutputs(outputJson: string): string { try { const outputs = JSON.parse(outputJson); if (!outputs || Object.keys(outputs).length === 0) { return "No outputs defined in this Terraform configuration."; } let result = "## 📋 Terraform Outputs\n\n"; result += "| Name | Value | Description |\n"; result += "|------|-------|-------------|\n"; for (const [name, config] of Object.entries(outputs as Record<string, any>)) { const value = config.value ?? "N/A"; const description = config.description ?? ""; const displayValue = typeof value === "string" ? value : JSON.stringify(value); result += `| **${name}** | \`${displayValue}\` | ${description} |\n`; } return result; } catch (error) { return `Error parsing Terraform outputs: ${error instanceof Error ? error.message : String(error)}`; } } /** * Parse CDK outputs from deploy command stdout * @param stdout The stdout from cdklocal deploy command * @returns Formatted markdown string of outputs */ export function parseCdkOutputs(stdout: string): string { try { const lines = stdout.split("\n"); const outputLines: string[] = []; let inOutputsSection = false; for (const line of lines) { if (line.trim().startsWith("Outputs:")) { inOutputsSection = true; continue; } if (inOutputsSection) { if (line.trim() === "" || line.match(/^[A-Z].*:$/)) { break; } const outputMatch = line.match(/^([^=]+)\s*=\s*(.+)$/); if (outputMatch) { outputLines.push(line.trim()); } } } if (outputLines.length === 0) { return "No outputs defined in this CDK stack."; } let result = "## 📋 CDK Stack Outputs\n\n"; result += "| Output | Value |\n"; result += "|--------|-------|\n"; for (const line of outputLines) { const [name, value] = line.split(" = ").map((s) => s.trim()); result += `| **${name}** | \`${value}\` |\n`; } return result; } catch (error) { return `Error parsing CDK outputs: ${error instanceof Error ? error.message : String(error)}`; } } export type DeploymentEventType = "header" | "command" | "output" | "error" | "success" | "warning"; export interface DeploymentEvent { type: DeploymentEventType; title?: string; content: string; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/localstack/localstack-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server