@orcdkestrator/orcdk-plugin-localstack
Version:
LocalStack lifecycle management plugin for Orcdkestrator
178 lines • 7.22 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.HotReloadWatcher = exports.DEFAULT_WATCH_INTERVAL_MS = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const core_1 = require("@orcdkestrator/core");
const utils_1 = require("./utils");
const runtime_config_1 = require("./runtime-config");
// Default debounce interval for file change detection (milliseconds)
exports.DEFAULT_WATCH_INTERVAL_MS = 700;
/**
* Hot reload file watcher for LocalStack Lambda functions
* Monitors file changes and triggers LocalStack hot reload via events
*/
class HotReloadWatcher {
constructor(config) {
this.watchers = new Map();
this.lastModified = new Map();
this.isWatching = false;
this.config = config;
this.eventBus = core_1.EventBus.getInstance();
}
/**
* Start watching configured Lambda paths for changes
*/
async startWatching() {
if (!this.config.hotReloading?.enabled || this.isWatching) {
return;
}
const lambdaPaths = this.config.hotReloading.lambdaPaths || [];
if (lambdaPaths.length === 0) {
this.debug('No Lambda paths configured for hot reloading');
return;
}
this.debug(`Starting hot reload watching for ${lambdaPaths.length} Lambda functions`);
// Get project root for path resolution
const projectRoot = process.cwd();
for (const lambdaPath of lambdaPaths) {
// Expand environment variables first
const expandedPath = (0, utils_1.expandEnvironmentVariables)(lambdaPath.localPath);
// Get file extensions for this runtime
const fileExtensions = lambdaPath.fileExtensions ||
(0, runtime_config_1.getFileExtensionsForRuntime)(lambdaPath.runtime, this.config.hotReloading?.runtimeFileExtensions);
// Resolve relative paths before watching
const resolvedConfig = {
...lambdaPath,
localPath: path.isAbsolute(expandedPath)
? expandedPath
: path.resolve(projectRoot, expandedPath),
functionName: (0, utils_1.expandEnvironmentVariables)(lambdaPath.functionName),
handler: (0, utils_1.expandEnvironmentVariables)(lambdaPath.handler),
fileExtensions
};
await this.watchLambdaPath(resolvedConfig);
}
this.isWatching = true;
this.debug('Hot reload watching started');
}
/**
* Stop watching all Lambda paths
*/
stopWatching() {
if (!this.isWatching) {
return;
}
this.debug('Stopping hot reload watching');
for (const [path, watcher] of this.watchers) {
watcher.close();
this.debug(`Stopped watching ${path}`);
}
this.watchers.clear();
this.lastModified.clear();
this.isWatching = false;
this.debug('Hot reload watching stopped');
}
/**
* Watch a specific Lambda path for changes
*/
async watchLambdaPath(lambdaConfig) {
const { functionName, localPath, handler, runtime, fileExtensions } = lambdaConfig;
if (!fs.existsSync(localPath)) {
this.debug(`Lambda path does not exist: ${localPath}`);
return;
}
const stat = fs.statSync(localPath);
if (!stat.isDirectory()) {
this.debug(`Lambda path is not a directory: ${localPath}`);
return;
}
this.debug(`Setting up hot reload for ${functionName} at ${localPath}`);
this.debug(`Watching file extensions: ${fileExtensions.join(', ')}`);
// Initialize last modified time
this.lastModified.set(localPath, Date.now());
// Create file watcher
const watcher = fs.watch(localPath, { recursive: true }, (eventType, filename) => {
if (!filename)
return;
const fullPath = path.join(localPath, filename);
const ext = path.extname(filename).toLowerCase();
// Filter for relevant file types based on configured extensions
if (!fileExtensions.includes(ext)) {
return;
}
this.handleFileChange(functionName, localPath, fullPath, handler, runtime);
});
this.watchers.set(localPath, watcher);
this.debug(`Started watching ${localPath} for ${functionName}`);
}
/**
* Handle file change event with debouncing
*/
handleFileChange(functionName, localPath, changedFile, handler, runtime) {
const now = Date.now();
const lastMod = this.lastModified.get(localPath) || 0;
const interval = this.config.hotReloading?.watchInterval || exports.DEFAULT_WATCH_INTERVAL_MS;
// Debounce rapid file changes
if (now - lastMod < interval) {
return;
}
this.lastModified.set(localPath, now);
this.debug(`File change detected: ${changedFile}`);
// Emit event for hot reload
this.eventBus.emitEvent('localstack:hot-reload:code-updated', {
functionName,
localPath,
changedFile,
handler,
runtime,
timestamp: new Date(),
}, 'LocalStackHotReload');
// eslint-disable-next-line no-console
console.log(`[localstack:hot-reload] Code updated for ${functionName}: ${path.basename(changedFile)}`);
}
/**
* Debug logging helper
*/
debug(message) {
if (this.config.debug) {
// eslint-disable-next-line no-console
console.log(`[localstack:hot-reload:debug] ${message}`);
}
}
}
exports.HotReloadWatcher = HotReloadWatcher;
//# sourceMappingURL=hot-reload.js.map