diff --git a/.github/workflows/fetch-release-stats.yml b/.github/workflows/fetch-release-stats.yml new file mode 100644 index 0000000..23ceb26 --- /dev/null +++ b/.github/workflows/fetch-release-stats.yml @@ -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 diff --git a/release_stats/fetch-release-stats.ts b/release_stats/fetch-release-stats.ts new file mode 100644 index 0000000..6a75308 --- /dev/null +++ b/release_stats/fetch-release-stats.ts @@ -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 { + 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 { + 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(); diff --git a/release_stats/tsconfig.json b/release_stats/tsconfig.json new file mode 100644 index 0000000..470d95a --- /dev/null +++ b/release_stats/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "esModuleInterop": true, + "strict": true, + "outDir": "./dist" + }, + "include": ["*.ts"] +}