UNPKG

tdpw

Version:

CLI tool for uploading Playwright test reports to TestDino platform with Azure storage support

447 lines 19 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GitCollector = void 0; const tslib_1 = require("tslib"); const simple_git_1 = tslib_1.__importDefault(require("simple-git")); /** * Collector for Git metadata */ class GitCollector { git; repoPath; constructor(repoPath = process.cwd()) { this.repoPath = repoPath; this.git = (0, simple_git_1.default)({ baseDir: repoPath }); } /** * Gather Git metadata: branch, latest commit, and remote repository info * Uses git commands as primary source, falls back to environment variables in CI/CD */ async getMetadata() { const metadata = {}; // Try to get metadata from git commands first const gitMetadata = await this.getGitCommandMetadata(); // Get environment variable metadata as fallback const envMetadata = await this.getEnvironmentMetadata(); // Merge git and environment metadata, preferring git when available if (gitMetadata.branch) { metadata.branch = gitMetadata.branch; } else if (envMetadata.branch) { metadata.branch = envMetadata.branch; } metadata.commit = { hash: gitMetadata.commit?.hash || envMetadata.commitHash || '', message: gitMetadata.commit?.message || envMetadata.commitMessage || '', author: gitMetadata.commit?.author || envMetadata.author || '', email: gitMetadata.commit?.email || envMetadata.email || '', timestamp: gitMetadata.commit?.timestamp || new Date().toISOString(), }; metadata.repository = { url: gitMetadata.repository?.url || envMetadata.repoUrl || '', name: gitMetadata.repository?.name || envMetadata.repoName || '', }; metadata.pr = { id: gitMetadata.pr?.id || envMetadata.prId || '', status: gitMetadata.pr?.status || envMetadata.prStatus || '', title: gitMetadata.pr?.title || envMetadata.prTitle || '', url: gitMetadata.pr?.url || envMetadata.prUrl || '', }; // Debug logging when verbose mode is enabled if (process.env.TESTDINO_VERBOSE === 'true') { const gitSuccess = Object.keys(gitMetadata).length > 0; const envFallback = Object.keys(envMetadata).length > 0; console.log(`🔍 Git metadata: ${metadata.branch || 'unknown'}, ${gitSuccess ? 'git commands' : envFallback ? 'environment' : 'fallback'}`); } return metadata; } /** * Attempt to gather metadata using git commands (existing functionality) */ async getGitCommandMetadata() { const metadata = {}; try { // Branch name const branchSummary = await this.git.branch(); metadata.branch = branchSummary.current; } catch (error) { if (process.env.LOG_LEVEL === 'debug') { console.log('Git branch command failed:', error); } } try { // Latest commit const log = await this.git.log({ maxCount: 1 }); if (log.latest) { metadata.commit = { hash: log.latest.hash, message: log.latest.message, author: log.latest.author_name, email: log.latest.author_email, timestamp: log.latest.date, }; } } catch (error) { if (process.env.LOG_LEVEL === 'debug') { console.log('Git log command failed:', error); } } try { // Remote repo URL const remotes = await this.git.getRemotes(true); if (remotes.length) { // prefer 'origin' const origin = remotes.find(r => r.name === 'origin') || remotes[0]; if (origin) { const url = origin.refs.push || origin.refs.fetch; metadata.repository = { url, name: this.extractRepoNameFromUrl(url), }; } } } catch (error) { if (process.env.LOG_LEVEL === 'debug') { console.log('Git remote command failed:', error); } } // Initialize empty PR metadata (filled by environment variables) metadata.pr = { id: '', status: '', title: '', url: '', }; return metadata; } /** * Extract git metadata from environment variables (CI/CD contexts) */ async getEnvironmentMetadata() { const env = process.env; const envInfo = {}; // GitHub Actions if (env.GITHUB_ACTIONS === 'true') { // For Pull Requests, prioritize GITHUB_HEAD_REF (source branch) if (env.GITHUB_EVENT_NAME === 'pull_request' && env.GITHUB_HEAD_REF) { envInfo.branch = env.GITHUB_HEAD_REF; // This gives us the actual source branch like "pratik/integrate-testreport" } else { // For push events, extract from GITHUB_REF const extractedBranch = this.extractBranchFromRef(env.GITHUB_REF); if (extractedBranch) envInfo.branch = extractedBranch; } if (env.GITHUB_SHA) envInfo.commitHash = env.GITHUB_SHA; if (env.GITHUB_ACTOR) envInfo.author = env.GITHUB_ACTOR; if (env.GITHUB_REPOSITORY) envInfo.repoName = env.GITHUB_REPOSITORY; if (env.GITHUB_REPOSITORY) envInfo.repoUrl = `https://github.com/${env.GITHUB_REPOSITORY}`; // Pull Request info if (env.GITHUB_EVENT_NAME === 'pull_request') { // Extract PR number from GITHUB_REF (refs/pull/3279/merge -> 3279) if (env.GITHUB_REF) { const prMatch = env.GITHUB_REF.match(/refs\/pull\/(\d+)\//); if (prMatch?.[1]) { envInfo.prId = prMatch[1]; if (env.GITHUB_REPOSITORY) { envInfo.prUrl = `https://github.com/${env.GITHUB_REPOSITORY}/pull/${prMatch[1]}`; } } } // Try to get PR details from GitHub API for PRs if (env.GITHUB_REPOSITORY && envInfo.prId) { const prDetails = await this.fetchGitHubPullRequestDetails(env.GITHUB_REPOSITORY, envInfo.prId); if (prDetails) { envInfo.prTitle = prDetails.title; envInfo.prStatus = prDetails.status; } } // Try to get commit message from GitHub API for PRs if (env.GITHUB_REPOSITORY && env.GITHUB_HEAD_REF) { // For PRs, get the HEAD commit from the source branch to avoid merge commit messages const branchCommitInfo = await this.fetchGitHubBranchCommit(env.GITHUB_REPOSITORY, env.GITHUB_HEAD_REF); if (branchCommitInfo) { envInfo.commitMessage = branchCommitInfo.message; envInfo.email = branchCommitInfo.email; // Add author email // Also update the commit hash to the actual source commit, not the merge commit envInfo.commitHash = branchCommitInfo.sha; } } } } // GitLab CI else if (env.GITLAB_CI === 'true') { if (env.CI_COMMIT_REF_NAME) envInfo.branch = env.CI_COMMIT_REF_NAME; if (env.CI_COMMIT_SHA) envInfo.commitHash = env.CI_COMMIT_SHA; if (env.CI_COMMIT_MESSAGE) envInfo.commitMessage = env.CI_COMMIT_MESSAGE; if (env.CI_COMMIT_AUTHOR) envInfo.author = env.CI_COMMIT_AUTHOR; if (env.CI_COMMIT_AUTHOR_EMAIL) envInfo.email = env.CI_COMMIT_AUTHOR_EMAIL; if (env.CI_PROJECT_PATH) envInfo.repoName = env.CI_PROJECT_PATH; if (env.CI_PROJECT_URL) envInfo.repoUrl = env.CI_PROJECT_URL; // Merge Request info if (env.CI_MERGE_REQUEST_ID) { envInfo.prId = env.CI_MERGE_REQUEST_ID; if (env.CI_MERGE_REQUEST_TITLE) envInfo.prTitle = env.CI_MERGE_REQUEST_TITLE; if (env.CI_MERGE_REQUEST_PROJECT_URL) { envInfo.prUrl = `${env.CI_MERGE_REQUEST_PROJECT_URL}/-/merge_requests/${env.CI_MERGE_REQUEST_ID}`; } } } // Azure DevOps else if (env.TF_BUILD === 'True') { if (env.BUILD_SOURCEBRANCH) envInfo.branch = env.BUILD_SOURCEBRANCH.replace('refs/heads/', ''); if (env.BUILD_SOURCEVERSION) envInfo.commitHash = env.BUILD_SOURCEVERSION; if (env.BUILD_REQUESTEDFOR) envInfo.author = env.BUILD_REQUESTEDFOR; if (env.BUILD_REQUESTEDFOREMAIL) envInfo.email = env.BUILD_REQUESTEDFOREMAIL; if (env.BUILD_REPOSITORY_NAME) envInfo.repoName = env.BUILD_REPOSITORY_NAME; if (env.BUILD_REPOSITORY_URI) envInfo.repoUrl = env.BUILD_REPOSITORY_URI; // Pull Request info if (env.SYSTEM_PULLREQUEST_PULLREQUESTID) { envInfo.prId = env.SYSTEM_PULLREQUEST_PULLREQUESTID; if (env.SYSTEM_PULLREQUEST_PULLREQUESTTITLE) envInfo.prTitle = env.SYSTEM_PULLREQUEST_PULLREQUESTTITLE; if (env.SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI) { envInfo.prUrl = `${env.SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI}/pullrequest/${env.SYSTEM_PULLREQUEST_PULLREQUESTID}`; } } } // Jenkins else if (env.JENKINS_URL) { const gitBranch = env.BRANCH_NAME || env.GIT_BRANCH; if (gitBranch) envInfo.branch = gitBranch.replace('origin/', ''); if (env.GIT_COMMIT) envInfo.commitHash = env.GIT_COMMIT; if (env.GIT_AUTHOR_NAME) envInfo.author = env.GIT_AUTHOR_NAME; if (env.GIT_AUTHOR_EMAIL) envInfo.email = env.GIT_AUTHOR_EMAIL; if (env.GIT_URL) envInfo.repoUrl = env.GIT_URL; if (env.GIT_URL) envInfo.repoName = this.extractRepoNameFromUrl(env.GIT_URL); // Pull Request info (if using GitHub Pull Request Builder plugin) if (env.ghprbPullId) { envInfo.prId = env.ghprbPullId; if (env.ghprbPullTitle) envInfo.prTitle = env.ghprbPullTitle; if (env.ghprbPullLink) envInfo.prUrl = env.ghprbPullLink; } } // CircleCI else if (env.CIRCLECI === 'true') { if (env.CIRCLE_BRANCH) envInfo.branch = env.CIRCLE_BRANCH; if (env.CIRCLE_SHA1) envInfo.commitHash = env.CIRCLE_SHA1; if (env.CIRCLE_USERNAME) envInfo.author = env.CIRCLE_USERNAME; if (env.CIRCLE_PROJECT_USERNAME && env.CIRCLE_PROJECT_REPONAME) { envInfo.repoName = `${env.CIRCLE_PROJECT_USERNAME}/${env.CIRCLE_PROJECT_REPONAME}`; } if (env.CIRCLE_REPOSITORY_URL) envInfo.repoUrl = env.CIRCLE_REPOSITORY_URL; // Pull Request info if (env.CIRCLE_PULL_REQUEST) { if (env.CIRCLE_PR_NUMBER) envInfo.prId = env.CIRCLE_PR_NUMBER; envInfo.prUrl = env.CIRCLE_PULL_REQUEST; } } // Generic CI environment variables (fallback) if (!envInfo.branch) { const fallbackBranch = env.CI_BRANCH || env.BRANCH_NAME || env.GIT_BRANCH; if (fallbackBranch) envInfo.branch = fallbackBranch; } if (!envInfo.commitHash) { const fallbackCommit = env.CI_COMMIT || env.COMMIT_SHA || env.GIT_COMMIT; if (fallbackCommit) envInfo.commitHash = fallbackCommit; } if (!envInfo.author) { const fallbackAuthor = env.CI_AUTHOR || env.GIT_AUTHOR || env.COMMIT_AUTHOR; if (fallbackAuthor) envInfo.author = fallbackAuthor; } if (!envInfo.repoUrl) { const fallbackUrl = env.CI_REPOSITORY_URL || env.REPOSITORY_URL || env.GIT_URL; if (fallbackUrl) envInfo.repoUrl = fallbackUrl; } return envInfo; } /** * Extract branch name from git ref (e.g., "refs/heads/main" -> "main") */ extractBranchFromRef(ref) { if (!ref) return undefined; if (ref.startsWith('refs/heads/')) { return ref.replace('refs/heads/', ''); } if (ref.startsWith('refs/tags/')) { return ref.replace('refs/tags/', ''); } return ref; } /** * Extract repository name from URL (e.g., "https://github.com/user/repo.git" -> "user/repo") */ extractRepoNameFromUrl(url) { if (!url) return ''; try { // Remove .git suffix const cleanUrl = url.replace(/\.git$/, ''); // Extract the last two parts of the path const parts = cleanUrl.split('/').filter(part => part.length > 0); if (parts.length >= 2) { return parts.slice(-2).join('/'); } return cleanUrl; } catch { return url; } } /** * Fetch the HEAD commit information from a specific branch to get the actual commit message * This avoids merge commit messages in PR scenarios */ async fetchGitHubBranchCommit(repository, branch) { try { // Use GitHub's API to fetch the latest commit from the specific branch const url = `https://api.github.com/repos/${repository}/commits/${branch}`; if (process.env.LOG_LEVEL === 'debug') { console.log(`🔍 Fetching branch HEAD commit from GitHub API: ${url}`); } const response = await fetch(url, { method: 'GET', headers: { 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'tdpw/1.0.0', // If GITHUB_TOKEN is available, use it for higher rate limits ...(process.env.GITHUB_TOKEN && { 'Authorization': `token ${process.env.GITHUB_TOKEN}` }) }, // Set a short timeout to avoid blocking signal: AbortSignal.timeout(5000) }); if (response.ok) { const data = await response.json(); const sha = data?.sha; const message = data?.commit?.message; const email = data?.commit?.author?.email; if (process.env.LOG_LEVEL === 'debug') { console.log(`✅ Successfully fetched branch commit - SHA: ${sha?.substring(0, 8)}..., Message: ${message?.substring(0, 50)}..., Email: ${email}`); } if (sha && message) { return { sha, message, email: email || '' }; } return null; } else { if (process.env.LOG_LEVEL === 'debug') { console.log(`❌ GitHub API request failed: ${response.status} ${response.statusText}`); } return null; } } catch (error) { if (process.env.LOG_LEVEL === 'debug') { console.log(`❌ Error fetching GitHub branch commit:`, error); } return null; } } /** * Fetch pull request details from GitHub API to get PR title and status */ async fetchGitHubPullRequestDetails(repository, prId) { try { // Use GitHub's API to fetch PR details const url = `https://api.github.com/repos/${repository}/pulls/${prId}`; if (process.env.LOG_LEVEL === 'debug') { console.log(`🔍 Fetching PR details from GitHub API: ${url}`); } const response = await fetch(url, { method: 'GET', headers: { 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'tdpw/1.0.0', // If GITHUB_TOKEN is available, use it for higher rate limits ...(process.env.GITHUB_TOKEN && { 'Authorization': `token ${process.env.GITHUB_TOKEN}` }) }, // Set a short timeout to avoid blocking signal: AbortSignal.timeout(5000) }); if (response.ok) { const data = await response.json(); const title = data?.title; const state = data?.state; const draft = data?.draft; // Determine PR status based on state and draft status let status = ''; if (draft) { status = 'draft'; } else if (state === 'open') { status = 'open'; } else if (state === 'closed') { status = 'closed'; } else if (state === 'merged') { status = 'merged'; } if (process.env.LOG_LEVEL === 'debug') { console.log(`✅ Successfully fetched PR details - Title: ${title}, Status: ${status}`); } if (title) { return { title, status }; } return null; } else { if (process.env.LOG_LEVEL === 'debug') { console.log(`❌ GitHub API request for PR details failed: ${response.status} ${response.statusText}`); } return null; } } catch (error) { if (process.env.LOG_LEVEL === 'debug') { console.log(`❌ Error fetching GitHub PR details:`, error); } return null; } } } exports.GitCollector = GitCollector; //# sourceMappingURL=git.js.map