embedia
Version:
Zero-configuration AI chatbot integration CLI - direct file copy with embedded API keys
137 lines (117 loc) • 4.21 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
class LayoutDetector {
async findLayoutFiles(projectPath, routerType) {
const layouts = [];
if (routerType === 'app') {
// Check all possible app router layout locations
const possiblePaths = [
'app/layout.tsx',
'app/layout.ts',
'app/layout.jsx',
'app/layout.js',
'src/app/layout.tsx',
'src/app/layout.ts',
'src/app/layout.jsx',
'src/app/layout.js'
];
for (const layoutPath of possiblePaths) {
const fullPath = path.join(projectPath, layoutPath);
if (await fs.pathExists(fullPath)) {
const content = await fs.readFile(fullPath, 'utf-8');
layouts.push({
path: layoutPath,
fullPath: fullPath,
content: content,
isTypeScript: layoutPath.endsWith('.ts') || layoutPath.endsWith('.tsx'),
hasClientDirective: content.includes("'use client'") || content.includes('"use client"'),
hasScriptImport: content.includes('next/script')
});
}
}
} else if (routerType === 'pages') {
// Check pages router app files
const appPaths = [
'pages/_app.tsx',
'pages/_app.ts',
'pages/_app.jsx',
'pages/_app.js',
'src/pages/_app.tsx',
'src/pages/_app.ts',
'src/pages/_app.jsx',
'src/pages/_app.js'
];
for (const appPath of appPaths) {
const fullPath = path.join(projectPath, appPath);
if (await fs.pathExists(fullPath)) {
const content = await fs.readFile(fullPath, 'utf-8');
layouts.push({
path: appPath,
fullPath: fullPath,
content: content,
isTypeScript: appPath.endsWith('.ts') || appPath.endsWith('.tsx')
});
}
}
}
return layouts;
}
async findBestLayoutForIntegration(layouts) {
if (layouts.length === 0) return null;
// Prefer TypeScript files
const tsLayouts = layouts.filter(l => l.isTypeScript);
if (tsLayouts.length > 0) return tsLayouts[0];
// Otherwise return first layout
return layouts[0];
}
async createLayoutIfMissing(projectPath, routerType, isTypeScript) {
if (routerType === 'app') {
const layoutDir = await fs.pathExists(path.join(projectPath, 'src')) ? 'src/app' : 'app';
const layoutPath = path.join(projectPath, layoutDir, `layout.${isTypeScript ? 'tsx' : 'jsx'}`);
if (!await fs.pathExists(layoutPath)) {
await fs.ensureDir(path.join(projectPath, layoutDir));
const layoutContent = this.generateDefaultLayout(isTypeScript, true);
await fs.writeFile(layoutPath, layoutContent);
return layoutPath;
}
} else if (routerType === 'pages') {
const appDir = await fs.pathExists(path.join(projectPath, 'src')) ? 'src/pages' : 'pages';
const appPath = path.join(projectPath, appDir, `_app.${isTypeScript ? 'tsx' : 'jsx'}`);
if (!await fs.pathExists(appPath)) {
await fs.ensureDir(path.join(projectPath, appDir));
const appContent = this.generateDefaultApp(isTypeScript);
await fs.writeFile(appPath, appContent);
return appPath;
}
}
return null;
}
generateDefaultLayout(isTypeScript, isAppRouter) {
if (isAppRouter) {
const typeAnnotations = isTypeScript ? ': { children: React.ReactNode }' : '';
return `import './globals.css'
export const metadata = {
title: 'My App',
description: 'Created with Embedia',
}
export default function RootLayout({ children }${typeAnnotations}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}`;
}
return '';
}
generateDefaultApp(isTypeScript) {
const typeImport = isTypeScript ? "import type { AppProps } from 'next/app'" : '';
const typeAnnotation = isTypeScript ? ': AppProps' : '';
return `${typeImport}
function MyApp({ Component, pageProps }${typeAnnotation}) {
return <Component {...pageProps} />
}
export default MyApp`;
}
}
module.exports = LayoutDetector;