208 lines
6.3 KiB
TypeScript
208 lines
6.3 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { parseJavaScriptFile, buildComponentTree } from '@/lib/file-parser';
|
|
import { FileManifest, FileInfo, RouteInfo } from '@/types/file-manifest';
|
|
// SandboxState type used implicitly through global.activeSandbox
|
|
|
|
declare global {
|
|
var activeSandbox: any;
|
|
}
|
|
|
|
export async function GET() {
|
|
try {
|
|
if (!global.activeSandbox) {
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: 'No active sandbox'
|
|
}, { status: 404 });
|
|
}
|
|
|
|
console.log('[get-sandbox-files] Fetching and analyzing file structure...');
|
|
|
|
// Get list of all relevant files
|
|
const findResult = await global.activeSandbox.runCommand({
|
|
cmd: 'find',
|
|
args: [
|
|
'.',
|
|
'-name', 'node_modules', '-prune', '-o',
|
|
'-name', '.git', '-prune', '-o',
|
|
'-name', 'dist', '-prune', '-o',
|
|
'-name', 'build', '-prune', '-o',
|
|
'-type', 'f',
|
|
'(',
|
|
'-name', '*.jsx',
|
|
'-o', '-name', '*.js',
|
|
'-o', '-name', '*.tsx',
|
|
'-o', '-name', '*.ts',
|
|
'-o', '-name', '*.css',
|
|
'-o', '-name', '*.json',
|
|
')',
|
|
'-print'
|
|
]
|
|
});
|
|
|
|
if (findResult.exitCode !== 0) {
|
|
throw new Error('Failed to list files');
|
|
}
|
|
|
|
const fileList = (await findResult.stdout()).split('\n').filter((f: string) => f.trim());
|
|
console.log('[get-sandbox-files] Found', fileList.length, 'files');
|
|
|
|
// Read content of each file (limit to reasonable sizes)
|
|
const filesContent: Record<string, string> = {};
|
|
|
|
for (const filePath of fileList) {
|
|
try {
|
|
// Check file size first
|
|
const statResult = await global.activeSandbox.runCommand({
|
|
cmd: 'stat',
|
|
args: ['-f', '%z', filePath]
|
|
});
|
|
|
|
if (statResult.exitCode === 0) {
|
|
const fileSize = parseInt(await statResult.stdout());
|
|
|
|
// Only read files smaller than 10KB
|
|
if (fileSize < 10000) {
|
|
const catResult = await global.activeSandbox.runCommand({
|
|
cmd: 'cat',
|
|
args: [filePath]
|
|
});
|
|
|
|
if (catResult.exitCode === 0) {
|
|
const content = await catResult.stdout();
|
|
// Remove leading './' from path
|
|
const relativePath = filePath.replace(/^\.\//, '');
|
|
filesContent[relativePath] = content;
|
|
}
|
|
}
|
|
}
|
|
} catch (parseError) {
|
|
console.debug('Error parsing component info:', parseError);
|
|
// Skip files that can't be read
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Get directory structure
|
|
const treeResult = await global.activeSandbox.runCommand({
|
|
cmd: 'find',
|
|
args: ['.', '-type', 'd', '-not', '-path', '*/node_modules*', '-not', '-path', '*/.git*']
|
|
});
|
|
|
|
let structure = '';
|
|
if (treeResult.exitCode === 0) {
|
|
const dirs = (await treeResult.stdout()).split('\n').filter((d: string) => d.trim());
|
|
structure = dirs.slice(0, 50).join('\n'); // Limit to 50 lines
|
|
}
|
|
|
|
// Build enhanced file manifest
|
|
const fileManifest: FileManifest = {
|
|
files: {},
|
|
routes: [],
|
|
componentTree: {},
|
|
entryPoint: '',
|
|
styleFiles: [],
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
// Process each file
|
|
for (const [relativePath, content] of Object.entries(filesContent)) {
|
|
const fullPath = `/${relativePath}`;
|
|
|
|
// Create base file info
|
|
const fileInfo: FileInfo = {
|
|
content: content,
|
|
type: 'utility',
|
|
path: fullPath,
|
|
relativePath,
|
|
lastModified: Date.now(),
|
|
};
|
|
|
|
// Parse JavaScript/JSX files
|
|
if (relativePath.match(/\.(jsx?|tsx?)$/)) {
|
|
const parseResult = parseJavaScriptFile(content, fullPath);
|
|
Object.assign(fileInfo, parseResult);
|
|
|
|
// Identify entry point
|
|
if (relativePath === 'src/main.jsx' || relativePath === 'src/index.jsx') {
|
|
fileManifest.entryPoint = fullPath;
|
|
}
|
|
|
|
// Identify App.jsx
|
|
if (relativePath === 'src/App.jsx' || relativePath === 'App.jsx') {
|
|
fileManifest.entryPoint = fileManifest.entryPoint || fullPath;
|
|
}
|
|
}
|
|
|
|
// Track style files
|
|
if (relativePath.endsWith('.css')) {
|
|
fileManifest.styleFiles.push(fullPath);
|
|
fileInfo.type = 'style';
|
|
}
|
|
|
|
fileManifest.files[fullPath] = fileInfo;
|
|
}
|
|
|
|
// Build component tree
|
|
fileManifest.componentTree = buildComponentTree(fileManifest.files);
|
|
|
|
// Extract routes (simplified - looks for Route components or page pattern)
|
|
fileManifest.routes = extractRoutes(fileManifest.files);
|
|
|
|
// Update global file cache with manifest
|
|
if (global.sandboxState?.fileCache) {
|
|
global.sandboxState.fileCache.manifest = fileManifest;
|
|
}
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
files: filesContent,
|
|
structure,
|
|
fileCount: Object.keys(filesContent).length,
|
|
manifest: fileManifest,
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('[get-sandbox-files] Error:', error);
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: (error as Error).message
|
|
}, { status: 500 });
|
|
}
|
|
}
|
|
|
|
function extractRoutes(files: Record<string, FileInfo>): RouteInfo[] {
|
|
const routes: RouteInfo[] = [];
|
|
|
|
// Look for React Router usage
|
|
for (const [path, fileInfo] of Object.entries(files)) {
|
|
if (fileInfo.content.includes('<Route') || fileInfo.content.includes('createBrowserRouter')) {
|
|
// Extract route definitions (simplified)
|
|
const routeMatches = fileInfo.content.matchAll(/path=["']([^"']+)["'].*(?:element|component)={([^}]+)}/g);
|
|
|
|
for (const match of routeMatches) {
|
|
const [, routePath] = match;
|
|
// componentRef available in match but not used currently
|
|
routes.push({
|
|
path: routePath,
|
|
component: path,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check for Next.js style pages
|
|
if (fileInfo.relativePath.startsWith('pages/') || fileInfo.relativePath.startsWith('src/pages/')) {
|
|
const routePath = '/' + fileInfo.relativePath
|
|
.replace(/^(src\/)?pages\//, '')
|
|
.replace(/\.(jsx?|tsx?)$/, '')
|
|
.replace(/index$/, '');
|
|
|
|
routes.push({
|
|
path: routePath,
|
|
component: path,
|
|
});
|
|
}
|
|
}
|
|
|
|
return routes;
|
|
} |