Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 68 additions & 29 deletions src/app/api/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { NextResponse } from "next/server";
import { getGeminiModel } from "@/lib/gemini";
import { getRepoData, getRepoContents } from "@/lib/octokit";

// Ensure API keys are read at runtime
export const dynamic = "force-dynamic";

/**
* AI README Generation Endpoint
* Optimized for data accuracy and clean prompt interpolation.
*/
export async function POST(req: Request) {
// 1. Safe JSON Body Parsing
let rawUrl: string;
try {
const body = await req.json();
Expand All @@ -16,7 +18,6 @@ export async function POST(req: Request) {
}

try {
// 2. Strict URL Validation
const trimmedUrl = rawUrl?.trim();
if (!trimmedUrl) {
return NextResponse.json({ error: "GitHub URL is required" }, { status: 400 });
Expand All @@ -29,12 +30,10 @@ export async function POST(req: Request) {
return NextResponse.json({ error: "Please provide a valid URL" }, { status: 400 });
}

// Hostname Guard
if (parsedUrl.hostname !== "github.com" && parsedUrl.hostname !== "www.github.com") {
return NextResponse.json({ error: "Only GitHub URLs are supported" }, { status: 400 });
}

// Extract Owner and Repo from path
const pathSegments = parsedUrl.pathname.split("/").filter(Boolean);
const owner = pathSegments[0];
const repo = pathSegments[1];
Expand All @@ -43,41 +42,81 @@ export async function POST(req: Request) {
return NextResponse.json({ error: "URL must include owner and repository name" }, { status: 400 });
}

// 3. Parallel Data Fetching
const [repoInfo, repoContents] = await Promise.all([
getRepoData(owner, repo),
getRepoContents(owner, repo)
]);

// 4. Type-Safe File Mapping (Fixes the 'any' linting error)
// We define the shape { name: string } inline to satisfy ESLint
const fileList = Array.isArray(repoContents) && repoContents.length > 0
? repoContents.map((f: { name: string }) => f.name).join(", ")
: "Standard repository structure";
const files = Array.isArray(repoContents) ? repoContents.map((f: { name: string }) => f.name) : [];
const fileListString = files.length > 0 ? files.join(", ") : "Standard repository structure";

// Tech Stack detection logic
const hasNode = files.includes("package.json");
const hasPython = files.includes("requirements.txt") || files.includes("setup.py");
const hasDocker = files.includes("Dockerfile") || files.includes("docker-compose.yml");

// Fix: Cleanly joined Tech Stack labels
const stackLabels = [
hasNode && "Node.js Environment",
hasPython && "Python Environment",
hasDocker && "Containerized"
].filter(Boolean).join(", ") || "Generic Software Environment";

// Fix: Dynamic License detection
const licenseName = repoInfo?.license?.name || repoInfo?.license?.spdx_id || "the repository's license file";

// 5. Initialize Gemini 2.5
const model = getGeminiModel();

// 6. The "Expert Prompt" with Fallbacks
// Fix: Prompt updated with neutral fallbacks and dynamic license
const prompt = `
You are an expert Technical Writer. Generate a professional README.md for:

Name: ${repo}
Description: ${repoInfo?.description || "A modern software project."}
Primary Language: ${repoInfo?.language || "Not specified"}
Root Directory Files: ${fileList}

Requirements:
- Include professional SVG badges from shields.io.
- Create a visual "Directory Structure" section (tree style).
- Include "Features", "Installation", and "Usage" sections.
- If 'package.json' exists, provide Node.js installation steps.
- Ensure a welcoming, professional developer-centric tone.

Return ONLY the Markdown content.
**Role**: You are a Principal Solutions Architect and World-Class Technical Writer.
**Task**: Generate a professional, high-conversion README.md for the GitHub repository: "${repo}".

---
### 1. PROJECT CONTEXT (VERIFIED DATA)
- **Project Name**: ${repo}
- **Description**: ${repoInfo?.description || "No description provided."}
- **Primary Language**: ${repoInfo?.language || "Language unknown"}
- **Detected Root Files**: ${fileListString}
- **Tech Stack Context**: ${stackLabels}

---
### 2. STRICT README STRUCTURE REQUIREMENTS

1. **Visual Header**:
- Center-aligned H1 with project name.
- A compelling 1-sentence tagline describing the **Value Proposition**.
- A centered row of Shields.io badges (Build, License, PRs Welcome, Stars).

2. **The Strategic "Why" (Overview)**:
- **The Problem**: Use a blockquote to describe the real-world pain point this project solves.
- **The Solution**: Explain how this project provides a superior outcome for the user.

3. **Key Features**:
- Minimum 5 features. Use emojis and focus on **User Benefits**.

4. **Technical Architecture**:
- Provide a table of the tech stack: | Technology | Purpose | Key Benefit |.
- Create a tree-style directory structure code block using 📁 for folders and 📄 for files based on the file manifest provided.

5. **Operational Setup**:
- **Prerequisites**: List required runtimes.
- **Installation**: Provide step-by-step terminal commands.
${hasNode ? "- Use npm/yarn/pnpm since package.json was detected." : ""}
${hasPython ? "- Use pip/venv since Python markers were detected." : ""}
- **Environment**: If any .env or config files are in the manifest, include a configuration section.

6. **Community & Governance**:
- Professional "Contributing" section (Fork -> Branch -> PR).
- Detailed "License" section: Reference ${licenseName} and provide a summary of permissions.

---
### 3. TONE & STYLE
- **Tone**: Authoritative, polished, and developer-centric.
- **Visuals**: Extensive use of Markdown formatting.
- **Constraint**: Return ONLY the raw Markdown. No conversational filler.
`;

// 7. AI Generation
const result = await model.generateContent(prompt);
const response = await result.response;
const markdown = response.text();
Expand Down
Empty file added src/lib/prompts.ts
Empty file.