chore: bump version to 1.0.11 and build action

This commit is contained in:
github-actions[bot]
2025-08-26 21:36:06 +00:00
parent 374be9a94d
commit e6027df10b
4 changed files with 799 additions and 341 deletions

View File

@@ -7,6 +7,116 @@ import * as core from '@actions/core';
const PlatformSettings = {
name: 'GitHub',
};
const OS_MAP = {
linux: 'linux',
darwin: 'macos',
mac: 'macos',
macos: 'macos',
windows: 'windows',
win: 'windows',
freebsd: 'freebsd',
};
const ARCHES = ['amd64', 'x64', 'arm64', 'armv6', '386', 'i386'];
const FORMATS = ['zip', 'tar.gz', 'tar.bz2', 'tar.xz', 'deb', 'rpm', 'msi', 'exe', 'txt', 'yaml', 'yml', 'json'];
const VARIANTS = ['docs', 'installer'];
export function parseReleaseAsset(filename) {
const original = filename;
let project = '';
let os = '';
let arch = '';
let format = '';
let variant = '';
let version = '';
// Extract format (extension)
const extRegex = new RegExp(`\\.(${FORMATS.join('|')})$`, 'i');
const extMatch = filename.match(extRegex);
format = extMatch ? extMatch[1].toLowerCase() : '';
// Remove extension and normalize separators
const nameWithoutExt = filename.replace(extRegex, '').replace(/[_-]/g, '.');
const parts = nameWithoutExt.split('.');
// API / special variants detection
const apiIndex = parts.findIndex(p => p.toLowerCase() === 'api' || p.toLowerCase() === 'nerm' || p.toLowerCase() === 'beta');
if (apiIndex >= 0) {
project = parts.slice(0, apiIndex).join('.');
let varMatch = parts.slice(apiIndex).join(' ');
// Recognize versions for API specs: nerm, nerm v2025, v3, v2024, v2025, beta
const nermMatch = varMatch.match(/nerm(?:\s+v\d{4})?/i);
const vDigitMatch = varMatch.match(/v\d{1,4}\b/i);
const betaMatch = varMatch.match(/\bbeta\b/i);
const semverMatch = varMatch.match(/\d+\.\d+(?:\.\d+)?/);
if (nermMatch) {
version = nermMatch[0];
}
else if (vDigitMatch) {
version = vDigitMatch[0];
}
else if (betaMatch) {
version = 'beta';
}
else if (semverMatch) {
version = semverMatch[0];
}
// Do not set OS to 'api' leave blank for API specs
os = '';
arch = '';
return { project, os, arch, format, variant, version, original };
}
// Version detection
for (const part of parts) {
const semverMatch = part.match(/^v?(\d+\.\d+(\.\d+)?)$/);
if (semverMatch) {
version = semverMatch[1];
break;
}
}
// OS detection
for (const part of parts) {
const lower = part.toLowerCase();
if (OS_MAP[lower])
os = OS_MAP[lower];
}
// Arch detection
for (const part of parts) {
const lower = part.toLowerCase();
if (ARCHES.includes(lower))
arch = lower === 'x64' ? 'amd64' : lower;
}
// Variant detection
for (const part of parts) {
const lower = part.toLowerCase();
if (VARIANTS.includes(lower))
variant = lower;
}
// Project is first part if not already set
if (!project)
project = parts[0];
return { project, os, arch, format, variant, version, original };
}
// Build a grouping key from parsed asset parts, excluding version and
// collapsing YAML/JSON specs to the same group by omitting the format.
function buildParsedAssetKey(parsed) {
const parts = [];
const project = parsed.project?.toLowerCase().trim();
const os = parsed.os?.toLowerCase().trim();
const arch = parsed.arch?.toLowerCase().trim();
let variant = (parsed.variant || '').toLowerCase();
if (parsed.version) {
variant = variant.replace(parsed.version.toLowerCase(), '').replace(/[._-]+$/g, '').trim();
}
if (project)
parts.push(project);
if (os)
parts.push(os);
if (arch)
parts.push(arch);
if (variant)
parts.push(variant);
const fmt = (parsed.format || '').toLowerCase();
if (fmt && fmt !== 'yaml' && fmt !== 'yml' && fmt !== 'json') {
parts.push(fmt);
}
return parts.join('_');
}
// GraphQL query for basic repository data (without releases)
const REPOSITORY_BASIC_QUERY = `
query RepositoryBasicData($owner: String!, $name: String!) {
@@ -95,11 +205,8 @@ export async function collectGithub(repository) {
let graphqlData = null;
try {
const graphqlClient = graphql.defaults({
headers: {
authorization: token ? `token ${token}` : undefined,
},
headers: { authorization: token ? `token ${token}` : undefined },
});
// Fetch basic repository data (without releases)
const basicResponse = await graphqlClient(REPOSITORY_BASIC_QUERY, {
owner,
name: repo
@@ -111,19 +218,33 @@ export async function collectGithub(repository) {
catch (error) {
console.warn(`Could not fetch GitHub GraphQL basic data for ${repository}:`, error);
}
// Step 2: Fetch releases data separately using GraphQL
// Step 2: Fetch releases data
let totalReleaseDownloads = 0;
let latestReleaseDownloads = 0;
let releaseCount = 0;
let downloadRange = [];
let latestRelease = null;
let assetTotalsByName = {};
let assetReleaseSeries = {};
let assetAttributesByKey = {};
// Additional breakdowns derived from parsed assets
let assetTotalsByParsedKey = {};
let totalsByOs = {};
let totalsByArch = {};
let totalsByFormat = {};
let totalsByVariant = {};
// Per-dimension per-release series for accurate trend charts
let attributeReleaseSeries = {
os: {},
arch: {},
format: {},
variant: {},
version: {},
};
try {
const graphqlClient = graphql.defaults({
headers: {
authorization: token ? `token ${token}` : undefined,
},
headers: { authorization: token ? `token ${token}` : undefined },
});
// Fetch releases data
const releasesResponse = await graphqlClient(RELEASES_QUERY, {
owner,
name: repo,
@@ -138,16 +259,81 @@ export async function collectGithub(repository) {
for (const asset of release.releaseAssets.nodes) {
if (asset) {
releaseDownloads += asset.downloadCount || 0;
const rawAssetName = asset.name;
const parsed = parseReleaseAsset(rawAssetName);
const parsedKey = buildParsedAssetKey(parsed);
const assetKey = parsedKey || rawAssetName.toLowerCase();
const assetDownloads = asset.downloadCount || 0;
// Skip checksum assets entirely
if (parsed.variant && parsed.variant.toLowerCase().includes('checksum')) {
continue;
}
assetTotalsByName[assetKey] = (assetTotalsByName[assetKey] || 0) + assetDownloads;
assetTotalsByParsedKey[parsedKey] = (assetTotalsByParsedKey[parsedKey] || 0) + assetDownloads;
// Persist attribute mapping for charting by dimensions
if (!assetAttributesByKey[assetKey]) {
assetAttributesByKey[assetKey] = {
project: parsed.project || undefined,
os: parsed.os || undefined,
arch: parsed.arch || undefined,
format: parsed.format || undefined,
variant: parsed.variant || undefined,
version: parsed.version || undefined,
};
}
if (parsed.os)
totalsByOs[parsed.os] = (totalsByOs[parsed.os] || 0) + assetDownloads;
if (parsed.arch)
totalsByArch[parsed.arch] = (totalsByArch[parsed.arch] || 0) + assetDownloads;
if (parsed.format)
totalsByFormat[parsed.format] = (totalsByFormat[parsed.format] || 0) + assetDownloads;
if (parsed.variant)
totalsByVariant[parsed.variant] = (totalsByVariant[parsed.variant] || 0) + assetDownloads;
// Build per-dimension series over releases
const tag = release.tagName;
if (parsed.os) {
if (!attributeReleaseSeries.os[parsed.os])
attributeReleaseSeries.os[parsed.os] = {};
attributeReleaseSeries.os[parsed.os][tag] = (attributeReleaseSeries.os[parsed.os][tag] || 0) + assetDownloads;
}
if (parsed.arch) {
if (!attributeReleaseSeries.arch[parsed.arch])
attributeReleaseSeries.arch[parsed.arch] = {};
attributeReleaseSeries.arch[parsed.arch][tag] = (attributeReleaseSeries.arch[parsed.arch][tag] || 0) + assetDownloads;
}
if (parsed.format) {
if (!attributeReleaseSeries.format[parsed.format])
attributeReleaseSeries.format[parsed.format] = {};
attributeReleaseSeries.format[parsed.format][tag] = (attributeReleaseSeries.format[parsed.format][tag] || 0) + assetDownloads;
}
if (parsed.variant) {
if (!attributeReleaseSeries.variant[parsed.variant])
attributeReleaseSeries.variant[parsed.variant] = {};
attributeReleaseSeries.variant[parsed.variant][tag] = (attributeReleaseSeries.variant[parsed.variant][tag] || 0) + assetDownloads;
}
if (parsed.version) {
const versionKey = parsed.version.toLowerCase();
if (!attributeReleaseSeries.version[versionKey])
attributeReleaseSeries.version[versionKey] = {};
attributeReleaseSeries.version[versionKey][tag] = (attributeReleaseSeries.version[versionKey][tag] || 0) + assetDownloads;
}
const day = release?.publishedAt || release?.createdAt;
if (!assetReleaseSeries[assetKey]) {
assetReleaseSeries[assetKey] = [];
}
assetReleaseSeries[assetKey].push({
tagName: release.tagName,
day: day || '',
downloads: assetDownloads,
});
}
}
}
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,
@@ -161,43 +347,30 @@ export async function collectGithub(repository) {
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
// Fallback REST API call
let restData = null;
try {
const { data: repoData } = await octokit.repos.get({
owner,
repo
});
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) {
if (!finalData)
throw new Error('Could not fetch repository data from either GraphQL or REST API');
}
// Get traffic statistics using REST API (requires authentication)
// Repo traffic stats
let viewsCount = 0;
let uniqueVisitors = 0;
let clonesCount = 0;
if (token) {
try {
// Get views data
const { data: viewsData } = await octokit.repos.getViews({
owner,
repo
});
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
});
const { data: clonesData } = await octokit.repos.getClones({ owner, repo });
if (clonesData) {
clonesCount = clonesData.count || 0;
}
@@ -206,19 +379,18 @@ export async function collectGithub(repository) {
console.warn(`Could not fetch GitHub traffic data for ${repository}:`, error);
}
}
// Calculate repository age
// Repository age and last activity
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
repositoryAge = Math.floor((now.getTime() - created.getTime()) / (1000 * 60 * 60 * 24));
}
// 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
lastActivity = Math.floor((now.getTime() - pushed.getTime()) / (1000 * 60 * 60 * 24));
}
return {
platform: PlatformSettings.name,
@@ -247,6 +419,15 @@ export async function collectGithub(repository) {
defaultBranch: finalData.defaultBranchRef?.name || finalData.default_branch || null,
downloadsTotal: totalReleaseDownloads || 0,
downloadRange,
assetTotalsByName,
assetReleaseSeries,
assetTotalsByParsedKey,
totalsByOs,
totalsByArch,
totalsByFormat,
totalsByVariant,
attributeReleaseSeries,
assetAttributesByKey,
}
};
}