mirror of
https://github.com/LukeHagar/usage-statistics.git
synced 2025-12-06 04:21:55 +00:00
chore: streamline release workflows by adding version bump and built files commit step; remove redundant asset upload steps
This commit is contained in:
268
dist/collectors/github.js
vendored
Normal file
268
dist/collectors/github.js
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* GitHub repository statistics collector with enhanced metrics using Octokit SDK and GraphQL
|
||||
*/
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { graphql } from '@octokit/graphql';
|
||||
const PlatformSettings = {
|
||||
name: 'GitHub',
|
||||
};
|
||||
// GraphQL query for basic repository data (without releases)
|
||||
const REPOSITORY_BASIC_QUERY = `
|
||||
query RepositoryBasicData($owner: String!, $name: String!) {
|
||||
repository(owner: $owner, name: $name) {
|
||||
id
|
||||
name
|
||||
description
|
||||
homepageUrl
|
||||
stargazerCount
|
||||
forkCount
|
||||
watchers {
|
||||
totalCount
|
||||
}
|
||||
openIssues: issues(states: OPEN) {
|
||||
totalCount
|
||||
}
|
||||
closedIssues: issues(states: CLOSED) {
|
||||
totalCount
|
||||
}
|
||||
primaryLanguage {
|
||||
name
|
||||
}
|
||||
diskUsage
|
||||
createdAt
|
||||
updatedAt
|
||||
pushedAt
|
||||
defaultBranchRef {
|
||||
name
|
||||
}
|
||||
repositoryTopics(first: 10) {
|
||||
nodes {
|
||||
topic {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
licenseInfo {
|
||||
name
|
||||
spdxId
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
// GraphQL query for releases with download data
|
||||
const RELEASES_QUERY = `
|
||||
query RepositoryReleases($owner: String!, $name: String!, $first: Int!) {
|
||||
repository(owner: $owner, name: $name) {
|
||||
releases(first: $first, orderBy: {field: CREATED_AT, direction: DESC}) {
|
||||
nodes {
|
||||
id
|
||||
tagName
|
||||
name
|
||||
description
|
||||
createdAt
|
||||
publishedAt
|
||||
releaseAssets(first: 100) {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
size
|
||||
downloadCount
|
||||
downloadUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export async function collectGithub(repository) {
|
||||
try {
|
||||
const [owner, repo] = repository.split('/');
|
||||
if (!owner || !repo) {
|
||||
throw new Error(`Invalid repository format: ${repository}. Expected "owner/repo"`);
|
||||
}
|
||||
// Initialize Octokit for REST API calls
|
||||
const token = process.env.GITHUB_TOKEN || process.env.INPUT_GITHUB_TOKEN || '';
|
||||
const octokit = new Octokit({
|
||||
auth: token,
|
||||
userAgent: 'usage-statistics-tracker'
|
||||
});
|
||||
if (!token) {
|
||||
console.warn('No GitHub token provided. Using unauthenticated requests (rate limited).');
|
||||
}
|
||||
// Step 1: Fetch basic repository data using GraphQL
|
||||
let graphqlData = null;
|
||||
try {
|
||||
const graphqlClient = graphql.defaults({
|
||||
headers: {
|
||||
authorization: token ? `token ${token}` : undefined,
|
||||
},
|
||||
});
|
||||
// Fetch basic repository data (without releases)
|
||||
const basicResponse = await graphqlClient(REPOSITORY_BASIC_QUERY, {
|
||||
owner,
|
||||
name: repo
|
||||
});
|
||||
if (basicResponse.repository) {
|
||||
graphqlData = basicResponse.repository;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.warn(`Could not fetch GitHub GraphQL basic data for ${repository}:`, error);
|
||||
}
|
||||
// Step 2: Fetch releases data separately using GraphQL
|
||||
let totalReleaseDownloads = 0;
|
||||
let latestReleaseDownloads = 0;
|
||||
let releaseCount = 0;
|
||||
let downloadRange = [];
|
||||
let latestRelease = null;
|
||||
try {
|
||||
const graphqlClient = graphql.defaults({
|
||||
headers: {
|
||||
authorization: token ? `token ${token}` : undefined,
|
||||
},
|
||||
});
|
||||
// Fetch releases data
|
||||
const releasesResponse = await graphqlClient(RELEASES_QUERY, {
|
||||
owner,
|
||||
name: repo,
|
||||
first: 100
|
||||
});
|
||||
if (releasesResponse.repository?.releases?.nodes) {
|
||||
const releases = releasesResponse.repository.releases.nodes.filter(Boolean);
|
||||
releaseCount = releases.length;
|
||||
for (const release of releases) {
|
||||
let releaseDownloads = 0;
|
||||
if (release?.releaseAssets?.nodes) {
|
||||
for (const asset of release.releaseAssets.nodes) {
|
||||
if (asset) {
|
||||
releaseDownloads += asset.downloadCount || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
totalReleaseDownloads += releaseDownloads;
|
||||
// Latest release is the first one in the list
|
||||
if (release && release === releases[0]) {
|
||||
latestReleaseDownloads = releaseDownloads;
|
||||
latestRelease = release.tagName;
|
||||
}
|
||||
// Add to download range with proper date format for charts
|
||||
if (release?.publishedAt) {
|
||||
downloadRange.push({
|
||||
day: release.publishedAt,
|
||||
downloads: releaseDownloads,
|
||||
tagName: release.tagName
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.warn(`Could not fetch GitHub GraphQL releases data for ${repository}:`, error);
|
||||
}
|
||||
// Fallback to REST API if GraphQL fails or for additional data
|
||||
let restData = null;
|
||||
try {
|
||||
const { data: repoData } = await octokit.repos.get({
|
||||
owner,
|
||||
repo
|
||||
});
|
||||
restData = repoData;
|
||||
}
|
||||
catch (error) {
|
||||
console.warn(`Could not fetch GitHub REST data for ${repository}:`, error);
|
||||
}
|
||||
// Use the best available data (GraphQL preferred, REST as fallback)
|
||||
const finalData = graphqlData || restData;
|
||||
if (!finalData) {
|
||||
throw new Error('Could not fetch repository data from either GraphQL or REST API');
|
||||
}
|
||||
// Get traffic statistics using REST API (requires authentication)
|
||||
let viewsCount = 0;
|
||||
let uniqueVisitors = 0;
|
||||
let clonesCount = 0;
|
||||
if (token) {
|
||||
try {
|
||||
// Get views data
|
||||
const { data: viewsData } = await octokit.repos.getViews({
|
||||
owner,
|
||||
repo
|
||||
});
|
||||
if (viewsData) {
|
||||
viewsCount = viewsData.count || 0;
|
||||
uniqueVisitors = viewsData.uniques || 0;
|
||||
}
|
||||
// Get clones data
|
||||
const { data: clonesData } = await octokit.repos.getClones({
|
||||
owner,
|
||||
repo
|
||||
});
|
||||
if (clonesData) {
|
||||
clonesCount = clonesData.count || 0;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.warn(`Could not fetch GitHub traffic data for ${repository}:`, error);
|
||||
}
|
||||
}
|
||||
// Calculate repository age
|
||||
let repositoryAge = 0;
|
||||
if (finalData.createdAt) {
|
||||
const created = new Date(finalData.createdAt);
|
||||
const now = new Date();
|
||||
repositoryAge = Math.floor((now.getTime() - created.getTime()) / (1000 * 60 * 60 * 24)); // days
|
||||
}
|
||||
// Calculate activity metrics
|
||||
let lastActivity = 0;
|
||||
if (finalData.pushedAt) {
|
||||
const pushed = new Date(finalData.pushedAt);
|
||||
const now = new Date();
|
||||
lastActivity = Math.floor((now.getTime() - pushed.getTime()) / (1000 * 60 * 60 * 24)); // days
|
||||
}
|
||||
return {
|
||||
platform: PlatformSettings.name,
|
||||
name: repository,
|
||||
timestamp: new Date().toISOString(),
|
||||
metrics: {
|
||||
stars: finalData.stargazerCount || finalData.stargazers_count || 0,
|
||||
forks: finalData.forkCount || finalData.forks_count || 0,
|
||||
watchers: finalData.watchers?.totalCount || finalData.watchers_count || 0,
|
||||
totalIssues: finalData.openIssues?.totalCount + finalData.closedIssues?.totalCount || 0,
|
||||
openIssues: finalData.openIssues?.totalCount || 0,
|
||||
closedIssues: finalData.closedIssues?.totalCount || 0,
|
||||
language: finalData.primaryLanguage?.name || finalData.language || null,
|
||||
size: finalData.diskUsage || finalData.size || null,
|
||||
repositoryAge,
|
||||
lastActivity,
|
||||
releaseCount,
|
||||
totalReleaseDownloads,
|
||||
latestReleaseDownloads,
|
||||
viewsCount,
|
||||
uniqueVisitors,
|
||||
latestRelease,
|
||||
clonesCount,
|
||||
topics: finalData.repositoryTopics?.nodes?.length || finalData.topics?.length || 0,
|
||||
license: finalData.licenseInfo?.name || finalData.license?.name || null,
|
||||
defaultBranch: finalData.defaultBranchRef?.name || finalData.default_branch || null,
|
||||
downloadsTotal: totalReleaseDownloads || 0,
|
||||
downloadRange,
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return {
|
||||
platform: PlatformSettings.name,
|
||||
name: repository,
|
||||
timestamp: new Date().toISOString(),
|
||||
metrics: {},
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
export async function collectGithubBatch(repositories) {
|
||||
const results = [];
|
||||
for (const repo of repositories) {
|
||||
results.push(collectGithub(repo));
|
||||
}
|
||||
return Promise.all(results);
|
||||
}
|
||||
Reference in New Issue
Block a user