UNPKG

hedidit

Version:

220 lines (219 loc) 7.08 kB
import { readFile } from "node:fs/promises"; import { join, relative, sep, normalize } from "node:path"; import { existsSync } from "node:fs"; // Tech stack detection configuration export const CONFIG_FILE_CHECKS = [ { file: 'tsconfig.json', tech: 'TypeScript' }, { file: 'jsconfig.json', tech: 'JavaScript' }, { file: 'components.json', tech: 'Shadcn UI' }, { file: 'bun.lock', tech: 'Bun' }, { file: 'yarn.lock', tech: 'Yarn' }, { file: 'pnpm-lock.yaml', tech: 'pnpm' }, { file: 'deno.json', tech: 'Deno' }, { file: 'deno.jsonc', tech: 'Deno' }, { file: 'vite.config.js', tech: 'Vite' }, { file: 'vite.config.ts', tech: 'Vite' }, { file: 'webpack.config.js', tech: 'Webpack' }, { file: 'webpack.config.ts', tech: 'Webpack' }, { file: 'next.config.js', tech: 'Next.js' }, { file: 'next.config.ts', tech: 'Next.js' }, { file: 'svelte.config.js', tech: 'Svelte' }, { file: 'svelte.config.ts', tech: 'Svelte' }, { file: 'astro.config.mjs', tech: 'Astro' }, { file: 'astro.config.ts', tech: 'Astro' }, { file: 'remix.config.js', tech: 'Remix' }, { file: 'tailwind.config.js', tech: 'Tailwind CSS' }, { file: 'tailwind.config.ts', tech: 'Tailwind CSS' }, { file: 'jest.config.js', tech: 'Jest' }, { file: 'jest.config.ts', tech: 'Jest' }, { file: 'vitest.config.ts', tech: 'Vitest' }, { file: 'cypress.config.js', tech: 'Cypress' }, { file: 'playwright.config.ts', tech: 'Playwright' }, ]; // Dependency to tech mapping export const DEP_TO_TECH_MAP = { // Frontend frameworks and libraries 'react': 'React', 'vue': 'Vue.js', 'angular': 'Angular', 'svelte': 'Svelte', 'preact': 'Preact', 'solid-js': 'Solid.js', 'lit': 'Lit', // UI frameworks '@mui/material': 'Material UI', '@chakra-ui/react': 'Chakra UI', '@mantine/core': 'Mantine', 'antd': 'Ant Design', 'bootstrap': 'Bootstrap', 'tailwindcss': 'Tailwind CSS', // Meta frameworks 'next': 'Next.js', 'nuxt': 'Nuxt.js', 'sveltekit': 'SvelteKit', 'astro': 'Astro', 'remix': 'Remix', 'gatsby': 'Gatsby', // Backend frameworks 'express': 'Express', 'fastify': 'Fastify', 'koa': 'Koa', 'nest': 'NestJS', 'hapi': 'Hapi', '@nestjs/core': 'NestJS', // State management 'redux': 'Redux', 'recoil': 'Recoil', 'jotai': 'Jotai', 'zustand': 'Zustand', 'mobx': 'MobX', 'xstate': 'XState', // Data fetching 'graphql': 'GraphQL', '@apollo/client': 'Apollo Client', 'react-query': 'React Query', '@tanstack/react-query': 'TanStack Query', 'swr': 'SWR', // Testing 'jest': 'Jest', 'vitest': 'Vitest', '@testing-library/react': 'React Testing Library', 'cypress': 'Cypress', 'playwright': 'Playwright', // Utilities 'zod': 'Zod', 'yup': 'Yup', 'lodash': 'Lodash', 'ramda': 'Ramda', 'date-fns': 'date-fns', 'dayjs': 'Day.js', 'axios': 'Axios', 'ky': 'Ky', // Build tools 'vite': 'Vite', 'webpack': 'Webpack', 'rollup': 'Rollup', 'esbuild': 'esbuild', 'parcel': 'Parcel', // Others '@modelcontextprotocol/sdk': 'Model Context Protocol', }; // Organization name to tech mapping export const ORG_TO_TECH_MAP = { 'types': 'TypeScript', 'typescript-eslint': 'ESLint', 'testing-library': 'Testing Library', 'storybook': 'Storybook', 'emotion': 'Emotion', 'styled': 'Styled Components', 'tanstack': 'TanStack', 'reduxjs': 'Redux', 'apollo': 'Apollo', 'supabase': 'Supabase', 'prisma': 'Prisma', 'vercel': 'Vercel', 'aws': 'AWS SDK', 'firebase': 'Firebase', 'tarojs': 'Taro', }; // Helper function to capitalize first letter of a string export function capitalizeFirstLetter(str) { return str.charAt(0).toUpperCase() + str.slice(1); } export async function parseGitIgnoreFile(rootPath) { const gitIgnorePath = join(rootPath, ".gitignore"); if (!existsSync(gitIgnorePath)) { return []; } try { const content = await readFile(gitIgnorePath, "utf-8"); return content .split("\n") .map(line => line.trim()) .filter(line => line && !line.startsWith("#")) .map(pattern => { let isNegated = false; if (pattern.startsWith("!")) { isNegated = true; pattern = pattern.slice(1); } const isDirectory = pattern.endsWith("/"); if (isDirectory) { pattern = pattern.slice(0, -1); } return { pattern: pattern.trim(), isNegated, isDirectory }; }); } catch (error) { console.error("Error reading .gitignore:", error); return []; } } export function shouldIgnore(path, rootPath, gitIgnoreRules) { if (gitIgnoreRules.length === 0) { return true; } // Always include the .gitignore file itself if (path.endsWith(".gitignore")) { return false; } // Get relative path for comparison const relativePath = relative(rootPath, path); // Process each rule in order, with later rules overriding earlier ones let ignored = false; for (const rule of gitIgnoreRules) { const { pattern, isNegated, isDirectory } = rule; // Simple pattern matching for now // This can be extended with more complex glob pattern matching const isMatch = matchPattern(relativePath, pattern, isDirectory); if (isMatch) { ignored = !isNegated; } } return ignored; } function matchPattern(path, pattern, isDirectoryPattern) { // Normalize path separators for consistency const normalizedPath = normalize(path).split(sep).join("/"); // Full path match if (pattern === normalizedPath) { return true; } // Check for direct directory matches (like 'nest' in gitignore) const pathParts = normalizedPath.split("/"); for (let i = 0; i < pathParts.length; i++) { if (pathParts[i] === pattern) { return true; } } // Directory-only patterns should only match directories if (isDirectoryPattern && !existsSync(path)) { return false; } // Simple glob pattern matching if (pattern.includes("*")) { const regexPattern = pattern .replace(/\./g, "\\.") .replace(/\*/g, ".*"); const regex = new RegExp(`^${regexPattern}$`); if (regex.test(normalizedPath)) { return true; } // Check if it matches any parent directory or file for (let i = 0; i < pathParts.length; i++) { const partialPath = pathParts.slice(0, i + 1).join("/"); if (regex.test(partialPath)) { return true; } } } // Check if pattern is a directory prefix if (normalizedPath.startsWith(`${pattern}/`)) { return true; } return false; }