UNPKG

@orcdkestrator/orcdk-plugin-localstack

Version:
279 lines 11.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.LocalStackPlugin = void 0; const core_1 = require("@orcdkestrator/core"); const path = __importStar(require("path")); const cli_1 = require("./cli"); const hot_reload_1 = require("./hot-reload"); const utils_1 = require("./utils"); /** * LocalStack plugin for local AWS development * Manages LocalStack lifecycle using the LocalStack CLI * * @example * ```json * { * "name": "localstack", * "enabled": true, * "config": { * "autoStart": true, * "environment": { * "DEBUG": "1", * "PERSISTENCE": "1", * "GATEWAY_LISTEN": "0.0.0.0:4566" * }, * "waitForReady": { * "maxAttempts": 60, * "retryDelayMs": 1000 * }, * "stopOnCleanup": false, * "debug": true, * "hotReloading": { * "enabled": true, * "watchInterval": 700, * "lambdaPaths": [ * { * "functionName": "my-function", * "localPath": "/absolute/path/to/lambda/code", * "handler": "handler.function", * "runtime": "python3.8" * } * ] * } * } * } * ``` * * Configuration options: * - autoStart: Whether to automatically start LocalStack if not running (default: true) * - environment: Environment variables to pass to LocalStack * - waitForReady: Configuration for health check waiting * - stopOnCleanup: Whether to stop LocalStack when deployment finishes * - debug: Enable debug logging * - hotReloading: Configuration for Lambda hot reloading */ class LocalStackPlugin { constructor() { this.name = '@orcdkestrator/orcdk-plugin-localstack'; this.version = '1.0.0'; this.cli = null; this.config = null; this.orcdkConfig = null; this.eventBus = null; this.hotReloadWatcher = null; } async initialize(config, orcdkConfig) { this.config = config; this.orcdkConfig = orcdkConfig; this.cli = new cli_1.LocalStackCLI(); // Initialize hot reload watcher if enabled const localStackConfig = this.config?.config; if (localStackConfig?.hotReloading?.enabled) { this.hotReloadWatcher = new hot_reload_1.HotReloadWatcher(localStackConfig); } // Subscribe to events this.eventBus = core_1.EventBus.getInstance(); this.subscribeToEvents(); } /** * Subscribe to relevant events */ subscribeToEvents() { if (!this.eventBus) return; // Listen for pattern detection event to start LocalStack this.eventBus.on(core_1.EventTypes['orchestrator:before:pattern-detection'], async () => { if (!this.shouldRun()) return; const config = this.config?.config; const autoStart = config?.autoStart !== false; // Default to true if (autoStart) { await this.ensureNotRunning(); await this.checkDependencies(); await this.startLocalStack(); } else { // When autoStart is false, only check if LocalStack is healthy await this.ensureRunning(); } }); } shouldRun() { const env = process.env.CDK_ENVIRONMENT; const envConfig = this.orcdkConfig?.environments[env || '']; const shouldRun = !!(envConfig?.isLocal && this.config?.enabled); this.debug(`shouldRun: ${shouldRun} (env: ${env}, isLocal: ${envConfig?.isLocal}, enabled: ${this.config?.enabled})`); return shouldRun; } async ensureNotRunning() { const port = this.getPort(); this.debug(`Checking if LocalStack is already running on port ${port}`); if (await this.cli.isHealthy(port)) { this.exitWithError(`LocalStack is already running on port ${port}.\n` + 'To stop the existing instance, run: localstack stop\n' + 'Or configure a different port using GATEWAY_LISTEN environment variable.'); } this.debug('LocalStack is not running'); } async ensureRunning() { const port = this.getPort(); this.debug(`Checking if LocalStack is running on port ${port} (autoStart disabled)`); if (!(await this.cli.isHealthy(port))) { this.exitWithError(`LocalStack is not running on port ${port}.\n` + 'Since autoStart is disabled, you need to start LocalStack manually:\n' + ' localstack start\n' + 'Or enable autoStart in your orcdk.config.json'); } console.log('[localstack] LocalStack is already running'); } async checkDependencies() { this.debug('Checking for LocalStack CLI'); if (!(await this.cli.hasLocalStackCLI())) { this.exitWithError('LocalStack CLI not found. Please install it using: pip install localstack\n' + 'For more information, visit: https://docs.localstack.cloud/getting-started/installation/'); } this.debug('LocalStack CLI found'); } async startLocalStack() { console.log('[localstack] Starting LocalStack...'); const env = this.getEnvironment(); this.debug(`Environment variables: ${JSON.stringify(env)}`); await this.cli.start(env); const config = this.config?.config; const maxAttempts = config?.waitForReady?.maxAttempts ?? 30; const retryDelayMs = config?.waitForReady?.retryDelayMs ?? 2000; this.debug(`Waiting for LocalStack to be ready (maxAttempts: ${maxAttempts}, retryDelayMs: ${retryDelayMs})`); await this.cli.waitForReady(this.getPort(), maxAttempts, retryDelayMs); console.log('[localstack] LocalStack is ready'); // Start hot reloading if enabled await this.startHotReloading(); } getPort() { const listen = this.getEnvironment().GATEWAY_LISTEN || '127.0.0.1:4566'; const port = parseInt(listen.split(':').pop() || '4566'); // Validate port is within valid range if (isNaN(port) || port < 1 || port > 65535) { throw new Error(`[localstack] Invalid port number: ${port}. Port must be between 1 and 65535.`); } return port; } getEnvironment() { const config = this.config?.config; return config?.environment || {}; } /** * Start hot reloading functionality */ async startHotReloading() { if (!this.hotReloadWatcher) { return; } const config = this.config?.config; const lambdaPaths = config?.hotReloading?.lambdaPaths || []; if (lambdaPaths.length === 0) { this.debug('Hot reloading enabled but no Lambda paths configured'); return; } this.debug('Setting up hot reload Lambda functions'); // Get project root for path resolution const projectRoot = process.cwd(); // Create hot reload enabled Lambda functions with resolved paths for (const lambdaPath of lambdaPaths) { try { // Expand environment variables in the path const expandedPath = (0, utils_1.expandEnvironmentVariables)(lambdaPath.localPath); // Resolve path relative to project root if not absolute const resolvedPath = path.isAbsolute(expandedPath) ? expandedPath : path.resolve(projectRoot, expandedPath); // Expand environment variables in function name and handler const expandedFunctionName = (0, utils_1.expandEnvironmentVariables)(lambdaPath.functionName); const expandedHandler = (0, utils_1.expandEnvironmentVariables)(lambdaPath.handler); await this.cli.createHotReloadFunction(expandedFunctionName, resolvedPath, expandedHandler, lambdaPath.runtime, this.getEnvironment()); this.debug(`Created hot reload function: ${expandedFunctionName} at ${resolvedPath}`); } catch (error) { // Non-fatal error - function might already exist this.debug(`Failed to create hot reload function ${lambdaPath.functionName}: ${error}`); } } // Start file watching await this.hotReloadWatcher.startWatching(); console.log('[localstack] Hot reloading is active'); } exitWithError(message) { throw new Error(`[localstack] ${message}`); } async cleanup() { // Stop hot reloading first if (this.hotReloadWatcher) { this.hotReloadWatcher.stopWatching(); } const config = this.config?.config; if (config?.stopOnCleanup && this.cli) { console.log('[localstack] Stopping LocalStack...'); await this.cli.stop(); } // Unsubscribe from events if (this.eventBus) { this.eventBus.removeAllListeners(core_1.EventTypes['orchestrator:before:pattern-detection']); } } debug(message) { const config = this.config?.config; if (config?.debug) { console.log(`[localstack:debug] ${message}`); } } /** * Get completion commands for bash completion */ getCompletionCommands() { return [{ name: 'localstack', description: 'Manage LocalStack', arguments: [{ name: 'action', required: true, choices: ['stop', 'restart', 'status', 'clean'] }] }]; } } exports.LocalStackPlugin = LocalStackPlugin; // Export as default for easy importing exports.default = LocalStackPlugin; //# sourceMappingURL=index.js.map