diff --git a/apps/admin/actions/check-repository.ts b/apps/admin/actions/check-repository.ts new file mode 100644 index 00000000..47c1e6dc --- /dev/null +++ b/apps/admin/actions/check-repository.ts @@ -0,0 +1,21 @@ +"use server" + +import { z } from "zod" +import { getRepoOwnerAndName } from "~/lib/repositories" +import { authedProcedure } from "~/lib/safe-actions" +import { GitHubTechStackAnalyzer } from "~/lib/tech-stack-analyzer" +import { prisma } from "~/services/prisma" + +export const analyzeRepository = authedProcedure + .createServerAction() + .input(z.object({ id: z.string() })) + .handler(async ({ input: { id } }) => { + const { repository } = await prisma.tool.findUniqueOrThrow({ where: { id } }) + const repo = getRepoOwnerAndName(repository) + + if (!repo) return null + + const github = new GitHubTechStackAnalyzer() + const analysis = await github.analyzeTechStack(repo.owner, repo.name) + return analysis + }) diff --git a/apps/admin/app/(dashboard)/tools/_components/tool-actions.tsx b/apps/admin/app/(dashboard)/tools/_components/tool-actions.tsx index c655a161..a1f21c4e 100644 --- a/apps/admin/app/(dashboard)/tools/_components/tool-actions.tsx +++ b/apps/admin/app/(dashboard)/tools/_components/tool-actions.tsx @@ -8,6 +8,7 @@ import type React from "react" import { useState } from "react" import { toast } from "sonner" import { useServerAction } from "zsa-react" +import { analyzeRepository } from "~/actions/check-repository" import { ToolScheduleDialog } from "~/app/(dashboard)/tools/_components/tool-schedule-dialog" import { ToolsDeleteDialog } from "~/app/(dashboard)/tools/_components/tools-delete-dialog" import { reuploadToolAssets } from "~/app/(dashboard)/tools/_lib/actions" @@ -42,6 +43,17 @@ export const ToolActions = ({ tool, row, className, ...props }: ToolActionsProps }, }) + const { execute: analyzeRepositoryAction } = useServerAction(analyzeRepository, { + onSuccess: ({ data }) => { + console.log(data) + toast.success("Repository analyzed") + }, + + onError: ({ err }) => { + toast.error(err.message) + }, + }) + const handleDialogSuccess = () => { setShowDeleteDialog(false) row?.toggleSelected(false) @@ -103,6 +115,10 @@ export const ToolActions = ({ tool, row, className, ...props }: ToolActionsProps Reupload Assets + analyzeRepositoryAction({ id: tool.id })}> + Analyze Repository + + diff --git a/apps/admin/lib/tech-stack-analyzer.ts b/apps/admin/lib/tech-stack-analyzer.ts new file mode 100644 index 00000000..af160c57 --- /dev/null +++ b/apps/admin/lib/tech-stack-analyzer.ts @@ -0,0 +1,443 @@ +import type { graphql } from "@octokit/graphql/types" +import { githubClient } from "~/services/github" + +interface TechStackSignature { + required_files: string[] + optional_files: string[] + dependencies: string[] + files_content: string[] + topics?: string[] +} + +interface FileNode { + path: string + type: string + object?: { + text?: string + } +} + +interface RepositoryContent { + repository: { + object: { + tree: { + entries: FileNode[] + } + } + languages: { + edges: { + node: { + name: string + } + size: number + }[] + } + dependencyGraphManifests: { + nodes: { + dependencies: { + nodes: { + packageName: string + }[] + } + }[] + } + repositoryTopics: { + nodes: { + topic: { + name: string + } + }[] + } + } +} + +interface AnalysisResult { + repository: string + detected_stacks: string[] + primary_languages: string[] + confidence_scores: Record + matched_topics: Record +} + +export class GitHubTechStackAnalyzer { + private client: graphql + private tech_stacks: Record + + constructor() { + this.client = githubClient + + this.tech_stacks = { + React: { + required_files: ["package.json"], + optional_files: [ + "src/App.tsx", + "src/App.jsx", + "src/app.tsx", + "src/app.jsx", + "tsconfig.json", + ], + dependencies: ["react", "react-dom", "@types/react"], + files_content: ["import React", 'from "react"', "React.Component", "useState", "useEffect"], + topics: ["react", "reactjs", "react-components", "frontend"], + }, + Vue: { + required_files: ["package.json"], + optional_files: ["src/App.vue", "src/app.vue", "vue.config.js", "vite.config.ts"], + dependencies: ["vue", "@vue/cli-service", "@vitejs/plugin-vue"], + files_content: ["createApp", "defineComponent", "