cliseo
Version:
Instant AI-Powered SEO Optimization CLI for Developers
148 lines (143 loc) ⢠6.97 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProjectAnalyzer = void 0;
const glob_1 = require("glob");
const promises_1 = require("fs/promises");
const path_1 = require("path");
const config_1 = require("../../config");
const openai_1 = __importDefault(require("openai"));
const FRAMEWORK_KEY_FILES = {
next: ['next.config.js', 'pages/_app.tsx', 'pages/_document.tsx', 'app/layout.tsx'],
react: ['src/App.tsx', 'src/main.tsx', 'src/index.tsx', 'index.html'],
angular: ['angular.json', 'src/app/app.component.ts', 'src/index.html'],
vue: ['vite.config.ts', 'src/App.vue', 'src/main.ts', 'index.html'],
unknown: [],
};
class ProjectAnalyzer {
openai;
config = (0, config_1.loadConfig)();
constructor() {
this.openai = new openai_1.default({ apiKey: this.config.openaiApiKey });
}
async analyzeProject(projectRoot) {
console.log('š¬ Step 1: Detecting framework and language...');
const packageJsonFile = await this.readFile((0, path_1.join)(projectRoot, 'package.json'));
const packageJson = packageJsonFile ? JSON.parse(packageJsonFile.content) : {};
const framework = this.detectFramework(packageJson);
const language = await this.detectLanguage(projectRoot);
console.log(`ā
Framework detected: ${framework}`);
console.log(`ā
Language detected: ${language}`);
console.log('\nš¬ Step 2: Gathering high-priority files...');
const highPriorityFiles = await this.getHighPriorityFiles(projectRoot, framework);
const filesForAnalysis = packageJsonFile ? [packageJsonFile, ...highPriorityFiles] : highPriorityFiles;
console.log(`ā
Found ${filesForAnalysis.length} high-priority files for analysis.`);
console.log('\nš¬ Step 3: Performing AI-powered analysis...');
const contentForAnalysis = this.prepareContentForAnalysis(filesForAnalysis);
const aiAnalysis = await this.getAIAnalysis(contentForAnalysis, framework, packageJson);
console.log('ā
AI analysis complete.');
return {
projectName: packageJson.name || 'Unknown Project',
description: aiAnalysis.description,
keywords: aiAnalysis.keywords,
author: aiAnalysis.author,
framework,
language,
};
}
async readFile(filePath) {
try {
const content = await (0, promises_1.readFile)(filePath, 'utf-8');
return { path: filePath, content };
}
catch (error) {
return null; // File not found or couldn't be read
}
}
detectFramework(packageJson) {
const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
if (dependencies.next)
return 'next';
if (dependencies.react)
return 'react';
if (dependencies['@angular/core'])
return 'angular';
if (dependencies.vue)
return 'vue';
return 'unknown';
}
async detectLanguage(projectRoot) {
const tsFiles = await (0, glob_1.glob)('**/*.{ts,tsx}', { cwd: projectRoot, ignore: ['node_modules/**'], absolute: true });
if (tsFiles.length > 0)
return 'typescript';
const jsFiles = await (0, glob_1.glob)('**/*.{js,jsx}', { cwd: projectRoot, ignore: ['node_modules/**'], absolute: true });
if (jsFiles.length > 0)
return 'javascript';
return 'unknown';
}
async getHighPriorityFiles(projectRoot, framework) {
const defaultFiles = ['README.md', 'readme.md'];
const frameworkFiles = FRAMEWORK_KEY_FILES[framework];
const filesToSearch = [...new Set([...defaultFiles, ...frameworkFiles])];
const foundFiles = [];
for (const file of filesToSearch) {
const foundFile = await this.readFile((0, path_1.join)(projectRoot, file));
if (foundFile) {
foundFiles.push(foundFile);
}
}
return foundFiles;
}
prepareContentForAnalysis(files) {
return files
.map(f => `--- File: ${f.path} ---\n${f.content.slice(0, 4000)}`) // Limit content per file
.join('\n\n');
}
async getAIAnalysis(content, framework, packageJson) {
const initialData = {
name: packageJson.name || 'Not specified',
description: packageJson.description || 'Not specified',
author: packageJson.author || 'Not specified',
keywords: packageJson.keywords || [],
};
const prompt = `
You are an expert SEO analyst. I'm analyzing a '${framework}' project.
From its 'package.json', I have this initial information:
- Project Name: ${initialData.name}
- Author: ${initialData.author}
- Description: ${initialData.description}
- Keywords: ${JSON.stringify(initialData.keywords)}
Here is the content from other key project files (like README, App.tsx, etc.):
${content}
Based on ALL of this context, please perform the following tasks:
1. **Refine the Description:** Write a compelling, SEO-friendly project description (2-3 sentences). If the existing description is good, improve it; otherwise, write a new one from scratch based on the file contents.
2. **Expand Keywords:** Generate a list of 10-15 relevant SEO keywords. Combine the existing keywords with new ones you identify from the project's content.
3. **Confirm Author:** Identify the author. Prioritize the author from 'package.json' if available, otherwise look for clues in the README or other files.
Return ONLY a valid JSON object with the keys: "description", "keywords", and "author". Do not include any other text or explanations in your response.
`;
const response = await this.openai.chat.completions.create({
model: this.config.model,
messages: [
{ role: 'system', content: 'You are a helpful SEO analysis assistant that only responds with valid, unformatted JSON.' },
{ role: 'user', content: prompt }
],
temperature: this.config.temperature,
max_tokens: this.config.maxTokens,
});
try {
const analysis = JSON.parse(response.choices[0].message.content || '{}');
return {
description: analysis.description || initialData.description,
keywords: analysis.keywords || initialData.keywords,
author: analysis.author || initialData.author,
};
}
catch (e) {
console.error("Failed to parse AI response as JSON:", response.choices[0].message.content);
throw new Error("The AI returned a response that was not valid JSON.");
}
}
}
exports.ProjectAnalyzer = ProjectAnalyzer;