UNPKG

project-indexer

Version:

Local project indexer with browser trigger support

515 lines (472 loc) 14.4 kB
// import axios from 'axios'; // import dotenv from 'dotenv'; // import FormData from 'form-data'; // import fs from 'fs'; // import {globby} from 'globby'; // import ignore from 'ignore'; // For parsing .gitignore files // import path from 'path'; // dotenv.config(); // // Default file extensions that are considered important project files // const DEFAULT_EXTENSIONS = [ // // Web development // '.js', '.ts', '.tsx', '.jsx', '.html', '.css', '.scss', '.less', '.sass', // // Backend development // '.py', '.java', '.rb', '.php', '.go', '.rs', '.cs', '.cpp', '.c', '.h', '.hpp', // // Data and configuration // '.json', '.xml', '.yml', '.yaml', '.toml', '.ini', '.env.example', '.template', '.gradle', // // Documentation // '.md', '.txt', '.rst', '.adoc', // // Templates // '.ejs', '.hbs', '.pug', '.mustache', '.twig', // // Shell scripts // '.sh', '.bash', '.zsh', '.fish', '.bat', '.cmd', '.ps1', // // Project-specific files // 'Dockerfile', 'docker-compose.yml', 'Makefile', 'Jenkinsfile', 'Procfile' // ]; // // Universal ignore patterns for any project // const UNIVERSAL_IGNORE_PATTERNS = [ // // Dependency directories // 'node_modules/**', // 'bower_components/**', // 'jspm_packages/**', // 'package-lock.json', // 'yarn.lock', // 'pnpm-lock.yaml', // '.pnpm-store/**', // 'vendor/**', // '.bundle/**', // 'composer.lock', // 'Gemfile.lock', // 'requirements.txt.lock', // 'poetry.lock', // 'Pipfile.lock', // 'cargo.lock', // // Build outputs and cache // 'build/**', // 'dist/**', // 'out/**', // 'output/**', // 'target/**', // 'bin/**', // 'obj/**', // 'lib/**', // 'libs/**', // 'debug/**', // 'release/**', // 'coverage/**', // 'public/build/**', // '.svelte-kit/**', // '.next/**', // '.nuxt/**', // '.cache/**', // '.parcel-cache/**', // '.webpack/**', // '.serverless/**', // '.gradle/**', // '.eggs/**', // '__pycache__/**', // '*.py[cod]', // '*$py.class', // '*.so', // '*.dylib', // '*.dll', // '*.exe', // '*.out', // '*.app', // '*.jar', // '*.war', // '*.nar', // '*.ear', // '*.zip', // '*.tar.gz', // '*.rar', // // IDE and editor configurations // '.vscode/**', // '.idea/**', // '.eclipse/**', // '.settings/**', // '.project', // '.classpath', // '.factorypath', // '*.sublime-*', // '.atom/**', // '.nova/**', // '.vs/**', // '.editorconfig', // '.prettierrc*', // '.eslintrc*', // '.stylelintrc*', // '.jshintrc', // '*.code-workspace', // '*.swp', // '*.swo', // '*~', // '.history/**', // // OS and environment files // '.DS_Store', // '.AppleDouble', // '.LSOverride', // 'Icon\r', // 'Thumbs.db', // 'ehthumbs.db', // 'Desktop.ini', // '*.lnk', // '*.url', // '.env', // '.env.*', // '!.env.example', // '!.env.template', // '*.log', // '*.tmp', // '*.temp', // '*.pid', // '*.seed', // '*.bak', // '*.swp', // '*.dump', // 'npm-debug.log*', // 'yarn-debug.log*', // 'yarn-error.log*', // 'lerna-debug.log*', // '.pnpm-debug.log*', // // Version control // '.git/**', // '.gitattributes', // '.gitmodules', // '.svn/**', // '.hg/**', // '.bzr/**', // 'CVS/**', // // CI/CD and deployment // '.circleci/**', // '.github/**', // '.gitlab/**', // '.gitlab-ci.yml', // '.travis.yml', // 'appveyor.yml', // '.azure-pipelines/**', // '.jenkins/**', // 'Jenkinsfile', // '.wercker/**', // '.semaphore/**', // '.drone.yml', // '.codeship/**', // // Docker // '.docker/**', // 'docker-compose.override.yml', // // Testing // 'coverage/**', // '.nyc_output/**', // '.cypress/**', // '.jest-cache/**', // // Framework-specific // '.angular/**', // '.turbo/**', // '.meteor/**', // // TypeScript // '*.tsbuildinfo', // // Misc // '*.iml', // '.sass-cache/**', // '.terraform/**', // '*.tfstate', // '*.tfstate.*', // '.vagrant/**', // ]; // /** // * Attempts to read and parse all .gitignore files in the project // * @param {string} baseDir - The base directory of the project // * @returns {string[]} - Array of gitignore patterns // */ // async function getGitignorePatterns(baseDir) { // try { // // Find all .gitignore files in the project // const gitignoreFiles = await globby(['**/.gitignore'], { // cwd: baseDir, // absolute: true, // ignore: ['**/node_modules/**'], // dot: true, // }); // let allPatterns = []; // // Read each .gitignore file and collect patterns // for (const gitignoreFile of gitignoreFiles) { // try { // const content = fs.readFileSync(gitignoreFile, 'utf8'); // // Get directory containing this .gitignore // const gitignoreDir = path.dirname(gitignoreFile); // const relativeDir = path.relative(baseDir, gitignoreDir); // // Process each line in the .gitignore file // const patterns = content // .split('\n') // .map(line => line.trim()) // .filter(line => line && !line.startsWith('#')) // .map(pattern => { // // Make patterns relative to the project root // if (relativeDir && pattern.startsWith('/')) { // return path.join(relativeDir, pattern.substring(1)).replace(/\\/g, '/'); // } else if (relativeDir && !pattern.startsWith('!')) { // return path.join(relativeDir, pattern).replace(/\\/g, '/'); // } // return pattern; // }); // allPatterns = [...allPatterns, ...patterns]; // } catch (err) { // console.warn(`Error reading gitignore file ${gitignoreFile}: ${err.message}`); // } // } // return allPatterns; // } catch (err) { // console.warn(`Could not process gitignore files: ${err.message}`); // return []; // } // } // /** // * Get project-type-specific ignore patterns based on detected project files // * @param {string} baseDir - The base directory of the project // * @returns {string[]} - Additional ignore patterns specific to the project type // */ // async function getProjectSpecificIgnores(baseDir) { // const projectFiles = await globby([ // 'package.json', // 'pom.xml', // 'build.gradle', // 'requirements.txt', // 'Gemfile', // 'Cargo.toml', // 'go.mod', // 'composer.json', // 'project.clj', // 'build.sbt', // 'mix.exs', // 'rebar.config', // 'pubspec.yaml', // 'stackage.yaml', // 'Pipfile', // 'pyproject.toml', // ], { // cwd: baseDir, // deep: 1, // Only look at the root level // absolute: false, // We just need the names // }); // const additionalIgnores = []; // // Node.js/JavaScript projects // if (projectFiles.includes('package.json')) { // additionalIgnores.push( // 'coverage/**', // '.nyc_output/**', // '.storybook-out/**', // '.tern-port', // '.storybook-static/**', // 'storybook-static/**' // ); // } // // Java projects // if (projectFiles.includes('pom.xml') || projectFiles.includes('build.gradle')) { // additionalIgnores.push( // 'target/**', // 'build/**', // '.gradle/**', // 'out/**', // '*.class', // '*.jar', // 'gradle-app.setting', // '.gradletasknamecache', // '*.war', // '*.ear', // '*.hprof', // '.jrebel', // 'rebel.xml', // '.apt_generated/**', // '.apt_generated_tests/**', // '.settings/**', // '.metadata/**' // ); // } // // Python projects // if (projectFiles.includes('requirements.txt') || // projectFiles.includes('Pipfile') || // projectFiles.includes('pyproject.toml')) { // additionalIgnores.push( // '__pycache__/**', // '*.py[cod]', // '*$py.class', // '*.so', // '.Python', // 'env/**', // 'venv/**', // 'ENV/**', // 'env.bak/**', // 'venv.bak/**', // '.pytest_cache/**', // '.coverage', // 'htmlcov/**', // '.tox/**', // '.nox/**', // '.hypothesis/**', // '.pytype/**', // 'cython_debug/**', // '*.egg-info/**', // '.installed.cfg', // '*.egg', // 'celerybeat-schedule', // 'celerybeat.pid', // '*.sage.py', // '.ipynb_checkpoints/**' // ); // } // // Ruby projects // if (projectFiles.includes('Gemfile')) { // additionalIgnores.push( // '*.gem', // '*.rbc', // '/.config', // '/coverage/', // '/InstalledFiles', // '/pkg/', // '/spec/reports/', // '/spec/examples.txt', // '/test/tmp/', // '/test/version_tmp/', // '/tmp/', // '.byebug_history', // '.dat*', // '.repl_history', // '*.bridgesupport', // 'build-iPhoneOS/', // 'build-iPhoneSimulator/' // ); // } // // Rust projects // if (projectFiles.includes('Cargo.toml')) { // additionalIgnores.push( // '/target/', // '**/*.rs.bk', // 'Cargo.lock' // ); // } // // Go projects // if (projectFiles.includes('go.mod')) { // additionalIgnores.push( // '*.test', // '*.out', // '/vendor/', // '/Godeps/' // ); // } // // PHP projects // if (projectFiles.includes('composer.json')) { // additionalIgnores.push( // '/vendor/', // 'composer.phar', // '/phpunit.xml', // '/.phpunit.result.cache' // ); // } // return additionalIgnores; // } // /** // * Gets files to be indexed based on provided extensions and ignore patterns // * @param {string} baseDir - The base directory of the project // * @param {string[]} extensions - Array of file extensions to include // * @returns {Promise<string[]>} - Array of file paths // */ // async function getFocusedFiles(baseDir, extensions) { // // Prepare include patterns from extensions // const includePatterns = extensions.map(ext => { // // Handle extensions that start with dot and those that don't // return ext.startsWith('.') ? `**/*${ext}` : `**/*.${ext}`; // }); // // Add exact-match filenames without extensions that we want to include // includePatterns.push( // '**/Dockerfile', // '**/docker-compose.yml', // '**/Makefile', // '**/LICENSE', // '**/README*', // '**/CONTRIBUTING*', // '**/Jenkinsfile' // ); // // Collect all ignore patterns // const gitignorePatterns = await getGitignorePatterns(baseDir); // const projectSpecificIgnores = await getProjectSpecificIgnores(baseDir); // const allIgnorePatterns = [ // ...UNIVERSAL_IGNORE_PATTERNS, // ...gitignorePatterns, // ...projectSpecificIgnores // ]; // // Use the ignore package to create a proper ignore filter // const ig = ignore().add(allIgnorePatterns); // // First, get all files that match the include patterns // const allMatchingFiles = await globby(includePatterns, { // cwd: baseDir, // absolute: true, // dot: true, // Include dotfiles // }); // // Then filter out ignored files // return allMatchingFiles.filter(absPath => { // const relativePath = path.relative(baseDir, absPath).replace(/\\/g, '/'); // return !ig.ignores(relativePath); // }); // } // /** // * Indexes the project files and uploads them to the server // * @param {string} projectPath - Path to the project directory // * @param {string} serverUrl - URL of the server // * @param {string} projectId - Project ID // * @param {string} token - Authorization token // * @param {string} deviceId - Device ID // * @param {string[]} extensions - Array of file extensions to include // * @returns {Promise<Object>} - Server response // */ // export async function indexProject( // projectPath, // serverUrl, // projectId, // token, // deviceId, // extensions = DEFAULT_EXTENSIONS // ) { // const baseDir = path.resolve(projectPath); // console.log(`[Indexing] Starting project indexing from ${baseDir}`); // // Get files to be indexed // const files = await getFocusedFiles(baseDir, extensions); // console.log(`[Indexing] Found ${files.length} core project files.`); // if (files.length === 0) { // console.warn('[Warning] No files found to index. Check your ignore patterns and extensions.'); // return { success: false, message: 'No files found to index' }; // } // // Prepare form data for upload // const form = new FormData(); // const filePaths = []; // files.forEach((absPath) => { // const relativePath = path.relative(baseDir, absPath).replace(/\\/g, '/'); // const fileStream = fs.createReadStream(absPath); // form.append('files', fileStream, { filename: path.basename(absPath) }); // filePaths.push(relativePath); // }); // filePaths.forEach(fp => form.append('filePaths', fp)); // try { // console.log(`[Uploading] Uploading ${files.length} files to server...`); // const response = await axios.post( // `${serverUrl}/${projectId}/files`, // form, // { // headers: { // ...form.getHeaders(), // 'X-Device-Id': deviceId, // Authorization: `Bearer ${token}`, // }, // maxBodyLength: Infinity, // maxContentLength: Infinity, // } // ); // console.log(`[Success] Successfully uploaded ${files.length} files.`); // return response.data; // } catch (err) { // console.error(`[Upload failed] ${err.message}`); // if (err.response) { // console.error(`[Server response] Status: ${err.response.status}, Data:`, err.response.data); // } // throw err; // } // }