UNPKG

@gati-framework/cli

Version:

CLI tool for Gati framework - create, develop, build and deploy cloud-native applications

123 lines 3.98 kB
/** * @module cli/analyzer/hook-extractor * @description Extracts hook definitions from handler TypeScript code * * Implements Task 21: Hook Manifest Recording * - Detects lctx.before(), lctx.after(), lctx.catch() calls * - Extracts hook metadata (id, level, async, timeout, retries) * - Captures source location for debugging */ import * as ts from 'typescript'; /** * Extract all hooks from a TypeScript source file */ export function extractHooks(sourceFile) { const hooks = []; function visit(node) { // Look for lctx.before(), lctx.after(), lctx.catch() calls if (ts.isCallExpression(node)) { const expr = node.expression; if (ts.isPropertyAccessExpression(expr)) { const obj = expr.expression; const method = expr.name.text; // Check if it's lctx.before/after/catch if (ts.isIdentifier(obj) && obj.text === 'lctx' && ['before', 'after', 'catch'].includes(method)) { const hook = extractHookFromCall(node, method, sourceFile); if (hook) { hooks.push(hook); } } } } ts.forEachChild(node, visit); } visit(sourceFile); return hooks; } /** * Extract hook details from a call expression */ function extractHookFromCall(node, type, sourceFile) { const args = node.arguments; if (args.length === 0) { return null; } const hookConfig = args[0]; return { id: extractStringLiteral(hookConfig, 'id') || `${type}_${Date.now()}`, type, level: extractStringLiteral(hookConfig, 'level') || 'handler', isAsync: isAsyncFunction(hookConfig), timeout: extractNumberLiteral(hookConfig, 'timeout'), retries: extractNumberLiteral(hookConfig, 'retries'), sourceLocation: getSourceLocation(node, sourceFile), }; } /** * Extract string literal from object property */ function extractStringLiteral(node, propertyName) { if (!ts.isObjectLiteralExpression(node)) { return undefined; } for (const prop of node.properties) { if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === propertyName && ts.isStringLiteral(prop.initializer)) { return prop.initializer.text; } } return undefined; } /** * Extract number literal from object property */ function extractNumberLiteral(node, propertyName) { if (!ts.isObjectLiteralExpression(node)) { return undefined; } for (const prop of node.properties) { if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === propertyName && ts.isNumericLiteral(prop.initializer)) { return parseInt(prop.initializer.text, 10); } } return undefined; } /** * Check if hook function is async */ function isAsyncFunction(node) { if (!ts.isObjectLiteralExpression(node)) { return false; } for (const prop of node.properties) { if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'fn') { const fn = prop.initializer; // Check for async keyword if (ts.isFunctionExpression(fn) || ts.isArrowFunction(fn)) { return !!(fn.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword)); } } } return false; } /** * Get source location for a node */ function getSourceLocation(node, sourceFile) { const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); return { file: sourceFile.fileName, line: line + 1, // Convert to 1-based column: character + 1, // Convert to 1-based }; } //# sourceMappingURL=hook-extractor.js.map