tdpw
Version:
CLI tool for uploading Playwright test reports to TestDino platform with Azure storage support
447 lines • 19 kB
JavaScript
;
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