/** * Centralized generated/artifact detection. * * This module is intentionally lightweight and synchronous because it is used by * hot-path source collection and dispatch role detection. It combines cheap * path/name heuristics with optional header inspection for common generated-code * banners. */ import * as fs from "node:fs"; import * as path from "node:path"; const DEFAULT_HEADER_BYTES = 4096; const GENERATED_DIR_NAMES = new Set([ "generated", "__generated__", "codegen", "__codegen__", "generated-sources", "generated_sources", "openapi-generated", ".openapi-generator", ]); const GENERATED_HEADER_PATTERNS = [ /@generated\b/i, /@ts-nocheck\b/i, /\bcode generated\b/i, /\bdo not edit\b/i, /\bautomatically generated\b/i, /\bthis file was generated\b/i, /\bgenerated by protoc\b/i, /\bgenerated by graphql-code-generator\b/i, /\bgenerated by openapi-generator\b/i, /\bgenerated by swagger codegen\b/i, ]; const GENERATED_FILE_PATTERNS = [ /(^|[._-])generated([._-]|$)/i, /(^|[._-])gen([._-])/i, /_generated\.(go|rs|py|rb|java|kt|ts|tsx|js|jsx|mjs|cjs)$/i, /_gen\.(go|rs|py|rb|java|kt|ts|tsx|js|jsx|mjs|cjs)$/i, /\.pb\.(go|ts|tsx|js|jsx|mjs|cjs)$/i, /_pb2(_grpc)?\.py$/i, /\.sql\.(go|ts|js)$/i, /_sqlc\.go$/i, /\.g\.([jt]sx?|mjs|cjs|rs|go)$/i, /\.min\.(js|mjs|cjs|css)$/i, /\.(bundle|chunk)\.(js|mjs|cjs|css)$/i, ]; const DECLARATION_FILE_PATTERNS = [/\.d\.ts$/i, /\.d\.mts$/i, /\.d\.cts$/i]; export interface GeneratedArtifactOptions { /** Optional file content or prefix. When omitted, content markers are skipped. */ content?: string; /** Read a small file prefix to detect generated-code banners. */ readContentHeader?: boolean; /** Number of bytes to read when readContentHeader is true. */ maxHeaderBytes?: number; /** Treat declaration stubs (.d.ts/.d.mts/.d.cts) as artifacts. */ includeDeclarations?: boolean; } function pathSegments(filePath: string): string[] { return path .normalize(filePath) .split(/[\\/]+/) .filter(Boolean); } export function isGeneratedArtifactDirectoryName(dirName: string): boolean { return GENERATED_DIR_NAMES.has(dirName.trim().toLowerCase()); } export function isDeclarationFile(filePath: string): boolean { const base = path.basename(filePath).toLowerCase(); return DECLARATION_FILE_PATTERNS.some((pattern) => pattern.test(base)); } function hasGeneratedArtifactPath(filePath: string): boolean { const segments = pathSegments(filePath); const dirSegments = segments.slice(0, -1); if ( dirSegments.some((segment) => isGeneratedArtifactDirectoryName(segment)) ) { return true; } const base = path.basename(filePath); return GENERATED_FILE_PATTERNS.some((pattern) => pattern.test(base)); } function hasGeneratedArtifactContent(content: string): boolean { const header = content.slice(0, DEFAULT_HEADER_BYTES); return GENERATED_HEADER_PATTERNS.some((pattern) => pattern.test(header)); } function readFileHeader( filePath: string, maxBytes = DEFAULT_HEADER_BYTES, ): string | undefined { let fd: number | undefined; try { fd = fs.openSync(filePath, "r"); const buffer = Buffer.alloc(Math.max(0, maxBytes)); const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0); return buffer.toString("utf8", 0, bytesRead); } catch { return undefined; } finally { if (fd !== undefined) { try { fs.closeSync(fd); } catch { /* ignore close errors */ } } } } export function isGeneratedOrArtifact( filePath: string, options: GeneratedArtifactOptions = {}, ): boolean { if (hasGeneratedArtifactPath(filePath)) return true; if (options.includeDeclarations && isDeclarationFile(filePath)) return true; if (options.content !== undefined) { return hasGeneratedArtifactContent(options.content); } if (options.readContentHeader) { const header = readFileHeader(filePath, options.maxHeaderBytes); return header !== undefined && hasGeneratedArtifactContent(header); } return false; }