@orcdkestrator/orcdk-plugin-localstack
Version:
LocalStack lifecycle management plugin for Orcdkestrator
279 lines • 11.2 kB
JavaScript
"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