Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/workflows/fetch-release-stats.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Fetch Release Stats

on:
workflow_dispatch:

jobs:
fetch-stats:
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Install dependencies
run: |
cd release_stats && npm install -D typescript @types/node tsx

- name: Run stats script
run: |
cd release_stats && npx tsx fetch-release-stats.ts

- name: Display CSV in summary
run: |
echo "# Release Download Statistics" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Snapshot Date:** $(date +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

# Convert CSV to markdown table
awk -F',' 'NR==1 {
print "| " $1 " | " $2 " | " $3 " | " $4 " | " $5 " | " $6 " | " $7 " | " $8 " | " $9 " | " $10 " | " $11 " |"
print "|---|---|---|---|---|---|---|---|---|---|---|"
} NR>1 {
print "| " $1 " | " $2 " | " $3 " | " $4 " | " $5 " | " $6 " | " $7 " | " $8 " | " $9 " | " $10 " | " $11 " |"
}' session-desktop-release-stats.csv >> $GITHUB_STEP_SUMMARY

- name: Upload CSV artifact
uses: actions/upload-artifact@v4
with:
name: release-stats-${{ github.run_number }}
path: session-desktop-release-stats.csv
retention-days: 5

- name: Display CSV preview
run: |
echo "CSV Preview:"
head -n 5 session-desktop-release-stats.csv
178 changes: 178 additions & 0 deletions release_stats/fetch-release-stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import https from 'https';
import fs from 'fs';

// GitHub API configuration
const REPO = 'session-foundation/session-desktop';
const API_URL = `https://api.github.com/repos/${REPO}/releases`;

// Types
interface Asset {
name: string;
download_count: number;
}

interface Release {
tag_name: string;
published_at: string;
assets: Asset[];
}

interface ReleaseStats {
version: string;
snapshotDate: string;
dateCaptured: string;
debDownloads: number;
appimageDownloads: number;
rpmDownloads: number;
dmgArm64Downloads: number;
dmgX64Downloads: number;
zipArm64Downloads: number;
zipX64Downloads: number;
exeDownloads: number;
}

// Function to make HTTPS requests
function fetchJSON(url: string): Promise<Release[]> {
return new Promise((resolve, reject) => {
https
.get(
url,
{
headers: {
'User-Agent': 'Node.js Script',
Accept: 'application/vnd.github.v3+json',
},
},
(res) => {
let data = '';

res.on('data', (chunk: Buffer) => {
data += chunk.toString();
});

res.on('end', () => {
if (res.statusCode === 200) {
resolve(JSON.parse(data));
} else {
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
}
});
}
)
.on('error', reject);
});
}

// Function to count downloads by file extension
function countDownloadsByExtension(assets: Asset[], extension: string): number {
return assets
.filter((asset) => asset.name.endsWith(extension))
.reduce((sum, asset) => sum + asset.download_count, 0);
}

// Function to count downloads by extension and pattern
function countDownloadsByPattern(
assets: Asset[],
extension: string,
pattern: string
): number {
return assets
.filter(
(asset) => asset.name.endsWith(extension) && asset.name.includes(pattern)
)
.reduce((sum, asset) => sum + asset.download_count, 0);
}

// Function to get current date in YYYY-MM-DD format
function getCurrentDate(): string {
const now = new Date();
return now.toISOString().split('T')[0];
}

// Main function
async function generateReleaseStatsCSV(): Promise<void> {
try {
console.log('Fetching releases from GitHub...');
const releases = await fetchJSON(API_URL);

// Take only the last 10 releases
const last10Releases = releases.slice(0, 10);

console.log(`Processing ${last10Releases.length} releases...`);

const snapshotDate = getCurrentDate();
console.log(`Snapshot date: ${snapshotDate}`);

// Build CSV data
const csvRows: string[] = [];

// Header row
csvRows.push(
'version,snapshot_date,release_date,.deb,.appimage,.rpm,.dmg_arm64,.dmg_x64,.zip_arm64,.zip_x64,.exe'
);

// Data rows
for (const release of last10Releases) {
const stats: ReleaseStats = {
version: release.tag_name.replace(/^v/, ''), // Remove 'v' prefix
snapshotDate: snapshotDate,
dateCaptured: release.published_at.split('T')[0], // Get YYYY-MM-DD
debDownloads: countDownloadsByExtension(release.assets, '.deb'),
appimageDownloads: countDownloadsByExtension(
release.assets,
'.AppImage'
),
rpmDownloads: countDownloadsByExtension(release.assets, '.rpm'),
dmgArm64Downloads: countDownloadsByPattern(
release.assets,
'.dmg',
'arm64'
),
dmgX64Downloads: countDownloadsByPattern(release.assets, '.dmg', 'x64'),
zipArm64Downloads: countDownloadsByPattern(
release.assets,
'.zip',
'arm64'
),
zipX64Downloads: countDownloadsByPattern(release.assets, '.zip', 'x64'),
exeDownloads: countDownloadsByExtension(release.assets, '.exe'),
};

csvRows.push(
[
stats.version,
stats.snapshotDate,
stats.dateCaptured,
stats.debDownloads,
stats.appimageDownloads,
stats.rpmDownloads,
stats.dmgArm64Downloads,
stats.dmgX64Downloads,
stats.zipArm64Downloads,
stats.zipX64Downloads,
stats.exeDownloads,
].join(',')
);
}

// Write to file
const csvContent = csvRows.join('\n');
const filename = 'session-desktop-release-stats.csv';

fs.writeFileSync(filename, csvContent);

console.log(`\nCSV file generated: ${filename}`);
console.log(`Total releases processed: ${last10Releases.length}`);
console.log('\nFull content:');
console.log(csvRows.join('\n'));
} catch (error) {
console.error(
'Error:',
error instanceof Error ? error.message : 'Unknown error'
);
process.exit(1);
}
}

// Run the script
generateReleaseStatsCSV();
10 changes: 10 additions & 0 deletions release_stats/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"esModuleInterop": true,
"strict": true,
"outDir": "./dist"
},
"include": ["*.ts"]
}
Loading