UNPKG

@entro314labs/at3-toolkit

Version:

Advanced development toolkit for AT3 Stack projects

212 lines (211 loc) 8.07 kB
import { detect as detectPackageManager } from "detect-package-manager"; import { existsSync, readFileSync } from "fs"; import { join } from "path"; export class ProjectDetector { logger; constructor(logger) { this.logger = logger; } async detectProject(projectPath) { this.logger.debug(`Detecting project at: ${projectPath}`); if (!existsSync(projectPath)) { throw new Error(`Project path does not exist: ${projectPath}`); } const packageJsonPath = join(projectPath, "package.json"); if (!existsSync(packageJsonPath)) { throw new Error("No package.json found. This does not appear to be a Node.js project."); } const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")); const packageManager = await detectPackageManager({ cwd: projectPath }); // Detect project type const projectType = this.detectProjectType(packageJson, projectPath); // Analyze dependencies const dependencies = this.analyzeDependencies(packageJson); // Find configuration files const configFiles = this.findConfigFiles(projectPath); // Feature detection const hasTypeScript = this.hasTypeScript(projectPath, dependencies); const hasNextJs = this.hasDependency(dependencies, "next"); const hasReact = this.hasDependency(dependencies, "react"); const hasVue = this.hasDependency(dependencies, "vue"); const hasTailwind = this.hasDependency(dependencies, "tailwindcss"); const hasEslint = this.hasDependency(dependencies, "eslint"); const hasPrettier = this.hasDependency(dependencies, "prettier"); const hasBiome = this.hasDependency(dependencies, "@biomejs/biome"); // AIT3E-specific features const hasAISupport = this.hasDependency(dependencies, "ai") || this.hasDependency(dependencies, "@ai-sdk/openai") || this.hasDependency(dependencies, "@ai-sdk/anthropic") || this.hasDependency(dependencies, "@ai-sdk/google"); const hasSupabase = this.hasDependency(dependencies, "@supabase/supabase-js") || this.hasDependency(dependencies, "@supabase/ssr"); const hasEdgeRuntime = existsSync(join(projectPath, "middleware.ts")) || existsSync(join(projectPath, "src/middleware.ts")); const hasVectorDB = this.hasSupabaseVectorConfig(projectPath); return { path: projectPath, type: projectType, packageManager: packageManager, dependencies, configFiles, hasTypeScript, hasNextJs, hasReact, hasVue, hasTailwind, hasEslint, hasPrettier, hasBiome, hasAISupport, hasSupabase, hasEdgeRuntime, hasVectorDB, }; } detectProjectType(packageJson, projectPath) { const deps = { ...packageJson.dependencies, ...packageJson.devDependencies }; // Check for AIT3E stack (AI + T3 + Edge) const hasAI = deps.ai || deps["@ai-sdk/openai"] || deps["@ai-sdk/anthropic"] || deps["@ai-sdk/google"]; const hasSupabase = deps["@supabase/supabase-js"] || deps["@supabase/ssr"]; const hasNextJS = deps.next; const hasTailwind = deps.tailwindcss; const hasTypeScript = deps.typescript || existsSync(join(projectPath, "tsconfig.json")); if (hasAI && hasSupabase && hasNextJS && (hasTailwind || hasTypeScript)) { return "ait3e"; } // Check for Next.js if (deps.next) return "nextjs"; // Check for Nuxt if (deps.nuxt || deps["@nuxt/core"]) return "nuxt"; // Check for Vue if (deps.vue) return "vue"; // Check for React if (deps.react) return "react"; // Check for Vite if (deps.vite) return "vite"; // Check for Webpack if (deps.webpack) return "webpack"; // Default to Node.js return "node"; } analyzeDependencies(packageJson) { const deps = []; // Production dependencies if (packageJson.dependencies) { Object.entries(packageJson.dependencies).forEach(([name, version]) => { deps.push({ name, version: version, type: "dependency", }); }); } // Development dependencies if (packageJson.devDependencies) { Object.entries(packageJson.devDependencies).forEach(([name, version]) => { deps.push({ name, version: version, type: "devDependency", }); }); } // Peer dependencies if (packageJson.peerDependencies) { Object.entries(packageJson.peerDependencies).forEach(([name, version]) => { deps.push({ name, version: version, type: "peerDependency", }); }); } return deps; } findConfigFiles(projectPath) { const configFiles = []; const commonConfigFiles = [ // TypeScript "tsconfig.json", "tsconfig.build.json", // Next.js "next.config.js", "next.config.ts", "next.config.mjs", // Tailwind "tailwind.config.js", "tailwind.config.ts", "tailwind.config.mjs", "postcss.config.js", "postcss.config.mjs", // Linting ".eslintrc.js", ".eslintrc.json", ".eslintrc.yml", ".eslintrc.yaml", "eslint.config.js", "eslint.config.mjs", ".prettierrc", ".prettierrc.js", ".prettierrc.json", "biome.json", // Testing "vitest.config.ts", "vitest.config.js", "jest.config.js", "jest.config.ts", "playwright.config.ts", // Build tools "vite.config.ts", "vite.config.js", "webpack.config.js", "rollup.config.js", // Other ".gitignore", ".env.example", ".env.local", "README.md", ]; commonConfigFiles.forEach((file) => { if (existsSync(join(projectPath, file))) { configFiles.push(file); } }); return configFiles; } hasTypeScript(projectPath, dependencies) { return (existsSync(join(projectPath, "tsconfig.json")) || this.hasDependency(dependencies, "typescript")); } hasDependency(dependencies, name) { return dependencies.some((dep) => dep.name === name); } hasSupabaseVectorConfig(projectPath) { // Check for Supabase migration files that might contain vector extensions const supabaseMigrationDir = join(projectPath, "supabase", "migrations"); if (existsSync(supabaseMigrationDir)) { try { const { readdirSync, readFileSync } = require("fs"); const migrationFiles = readdirSync(supabaseMigrationDir); return migrationFiles.some((file) => { if (file.endsWith(".sql")) { const content = readFileSync(join(supabaseMigrationDir, file), "utf8"); return content.includes("vector") || content.includes("embedding"); } return false; }); } catch (error) { this.logger.debug("Could not check Supabase migrations for vector config"); return false; } } return false; } }