arela
Version:
AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.
263 lines • 9.34 kB
JavaScript
/**
* Flow Discovery Module
* Finds entry points in the codebase:
* - API routes (Express, Fastify, etc.)
* - Event handlers (DOM events, custom events)
* - Component lifecycle methods
* - Function exports from pages/routes
*/
import { glob } from 'glob';
import fs from 'fs-extra';
import path from 'path';
/**
* Discover all entry points in the codebase
*/
export async function discoverEntryPoints(cwd) {
const startTime = Date.now();
const entryPoints = [];
let pointId = 0;
// Find TypeScript/JavaScript files
const tsFiles = await glob('**/*.{ts,tsx,js,jsx}', {
cwd,
ignore: ['node_modules/**', 'dist/**', '**/*.test.*', '**/*.spec.*'],
});
for (const file of tsFiles) {
const filePath = path.join(cwd, file);
try {
const content = await fs.readFile(filePath, 'utf-8');
// Find API routes (Express, Fastify patterns)
const apiRoutes = findApiRoutes(content, file);
entryPoints.push(...apiRoutes.map(r => ({
...r,
id: `entry_${++pointId}`,
})));
// Find event handlers
const eventHandlers = findEventHandlers(content, file);
entryPoints.push(...eventHandlers.map(h => ({
...h,
id: `entry_${++pointId}`,
})));
// Find component exports (React patterns)
const componentExports = findComponentExports(content, file);
entryPoints.push(...componentExports.map(c => ({
...c,
id: `entry_${++pointId}`,
})));
// Find page routes (Next.js, Remix patterns)
const pageRoutes = findPageRoutes(content, file);
entryPoints.push(...pageRoutes.map(p => ({
...p,
id: `entry_${++pointId}`,
})));
// Find custom hooks
const customHooks = findCustomHooks(content, file);
entryPoints.push(...customHooks.map(h => ({
...h,
id: `entry_${++pointId}`,
})));
// Find middleware
const middleware = findMiddleware(content, file);
entryPoints.push(...middleware.map(m => ({
...m,
id: `entry_${++pointId}`,
})));
}
catch (error) {
// Skip files that can't be read
}
}
return {
entryPoints,
totalFound: entryPoints.length,
discoveryDuration: Date.now() - startTime,
};
}
/**
* Find API route definitions (Express, Fastify, etc.)
*/
function findApiRoutes(content, filePath) {
const routes = [];
// Express/Fastify route patterns
const expressPattern = /(?:app|router|server)\.(get|post|put|delete|patch|head|options)\(['"`]([^'"`]+)['"`]\s*,\s*(?:async\s+)?(function\s+\w+|\(.*?\)\s*=>|\w+)\s*[{(]/gm;
let match;
let lineNum = 1;
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const lineMatch = expressPattern.exec(line);
if (lineMatch) {
const [, method, route, handler] = lineMatch;
routes.push({
id: '',
name: `${method.toUpperCase()} ${route}`,
type: 'api_route',
path: filePath,
line: i + 1,
method: method.toUpperCase(),
handler: handler.trim(),
description: `API endpoint ${method.toUpperCase()} ${route}`,
});
}
}
// Reset regex state
expressPattern.lastIndex = 0;
return routes;
}
/**
* Find event handlers (DOM events, custom events)
*/
function findEventHandlers(content, filePath) {
const handlers = [];
// addEventListener patterns
const eventPattern = /\.(addEventListener|on(?:Click|Change|Submit|Focus|Blur|Input|Hover|Load))\(['"`]?(\w+)['"`]?\s*,\s*(?:async\s+)?(function\s+\w+|\(.*?\)\s*=>)/gm;
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
let match;
while ((match = eventPattern.exec(line)) !== null) {
const [, method, eventName, handler] = match;
handlers.push({
id: '',
name: `${eventName} event handler`,
type: 'event_handler',
path: filePath,
line: i + 1,
handler: handler.trim(),
description: `Event handler for "${eventName}"`,
});
}
}
// Reset regex state
eventPattern.lastIndex = 0;
return handlers;
}
/**
* Find React/Vue component exports
*/
function findComponentExports(content, filePath) {
const exports = [];
// Check if file looks like a component
if (!filePath.match(/\.(tsx|jsx|vue)$/)) {
return exports;
}
// React component patterns
const componentPattern = /(?:export\s+(?:default\s+)?function\s+(\w+)|export\s+(?:default\s+)?const\s+(\w+)\s*=|export\s+(?:default\s+)?\(|function\s+(\w+)\s*\(\s*(?:props|{|\))|const\s+(\w+)\s*=\s*(?:\([^)]*\)|{|\[)\s*=>)/gm;
const lines = content.split('\n');
let match;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
componentPattern.lastIndex = 0;
if ((match = componentPattern.exec(line)) !== null) {
const componentName = match[1] || match[2] || match[3] || match[4] || 'Component';
if (line.includes('export') || (i > 0 && content.substring(0, content.indexOf(line.substring(0, 20))).includes('export'))) {
exports.push({
id: '',
name: componentName,
type: 'component_export',
path: filePath,
line: i + 1,
description: `React/Vue component: ${componentName}`,
});
}
}
}
return exports;
}
/**
* Find Next.js/Remix page routes
*/
function findPageRoutes(content, filePath) {
const routes = [];
// Check if in pages directory
if (!filePath.match(/\/(pages|app|routes)\//)) {
return routes;
}
// Check for default export (page component)
if (content.includes('export default')) {
const routePath = filePath
.replace(/\.(tsx|jsx|ts|js)$/, '')
.replace(/\/page$/, '')
.replace(/\/?index$/, '');
routes.push({
id: '',
name: `Route: ${routePath}`,
type: 'page_route',
path: filePath,
line: 1,
description: `Page route: ${routePath}`,
});
}
// Look for API route handlers (getServerSideProps, loader, etc.)
const loaderPattern = /export\s+(?:async\s+)?(?:function\s+\w+|const\s+\w+\s*=.*=>)\s*\(.*(?:request|params|context)/gm;
if (loaderPattern.test(content)) {
routes.push({
id: '',
name: `Data loader: ${filePath}`,
type: 'page_route',
path: filePath,
line: 1,
description: `Server-side data loader`,
});
}
return routes;
}
/**
* Find custom React hooks
*/
function findCustomHooks(content, filePath) {
const hooks = [];
// Check if file looks like a hooks file
if (!filePath.includes('hook') && !filePath.includes('use')) {
return hooks;
}
const hookPattern = /(?:export\s+)?(?:function|const)\s+(use\w+)\s*\(/gm;
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
hookPattern.lastIndex = 0;
let match;
while ((match = hookPattern.exec(line)) !== null) {
const hookName = match[1];
hooks.push({
id: '',
name: hookName,
type: 'hook',
path: filePath,
line: i + 1,
description: `Custom React hook: ${hookName}`,
});
}
}
return hooks;
}
/**
* Find middleware functions
*/
function findMiddleware(content, filePath) {
const middleware = [];
// Middleware patterns
const middlewarePattern = /(?:export\s+)?(?:function|const)\s+(\w+)\s*\(\s*(?:req|request|ctx|context|next)[^)]*\)\s*(?::|=>|{)/gm;
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
middlewarePattern.lastIndex = 0;
let match;
while ((match = middlewarePattern.exec(line)) !== null) {
const middlewareName = match[1];
// Only include if it looks like middleware (not a normal function)
if (middlewareName.includes('middleware') || middlewareName.includes('handler') ||
middlewareName.includes('Middleware') || middlewareName.includes('Handler')) {
middleware.push({
id: '',
name: middlewareName,
type: 'middleware',
path: filePath,
line: i + 1,
description: `Middleware: ${middlewareName}`,
});
}
}
}
return middleware;
}
//# sourceMappingURL=discovery.js.map