Skip to content

Commit 24cef93

Browse files
committed
feat: add release stats workflow & script
1 parent 2914294 commit 24cef93

File tree

3 files changed

+242
-0
lines changed

3 files changed

+242
-0
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Fetch Release Stats
2+
3+
on:
4+
workflow_dispatch:
5+
6+
jobs:
7+
fetch-stats:
8+
runs-on: ubuntu-latest
9+
permissions:
10+
contents: write
11+
12+
steps:
13+
- name: Checkout repository
14+
uses: actions/checkout@v4
15+
16+
- name: Setup Node.js
17+
uses: actions/setup-node@v4
18+
with:
19+
node-version: "20"
20+
21+
- name: Install dependencies
22+
run: |
23+
cd release_stats && npm install -D typescript @types/node tsx
24+
25+
- name: Run stats script
26+
run: |
27+
cd release_stats && npx tsx fetch-release-stats.ts
28+
29+
- name: Display CSV in summary
30+
run: |
31+
echo "# Release Download Statistics" >> $GITHUB_STEP_SUMMARY
32+
echo "" >> $GITHUB_STEP_SUMMARY
33+
echo "**Snapshot Date:** $(date +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
34+
echo "" >> $GITHUB_STEP_SUMMARY
35+
36+
# Convert CSV to markdown table
37+
awk -F',' 'NR==1 {
38+
print "| " $1 " | " $2 " | " $3 " | " $4 " | " $5 " | " $6 " | " $7 " | " $8 " | " $9 " | " $10 " | " $11 " |"
39+
print "|---|---|---|---|---|---|---|---|---|---|---|"
40+
} NR>1 {
41+
print "| " $1 " | " $2 " | " $3 " | " $4 " | " $5 " | " $6 " | " $7 " | " $8 " | " $9 " | " $10 " | " $11 " |"
42+
}' session-desktop-release-stats.csv >> $GITHUB_STEP_SUMMARY
43+
44+
- name: Upload CSV artifact
45+
uses: actions/upload-artifact@v4
46+
with:
47+
name: release-stats-${{ github.run_number }}
48+
path: session-desktop-release-stats.csv
49+
retention-days: 5
50+
51+
- name: Display CSV preview
52+
run: |
53+
echo "CSV Preview:"
54+
head -n 5 session-desktop-release-stats.csv
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import https from 'https';
2+
import fs from 'fs';
3+
4+
// GitHub API configuration
5+
const REPO = 'session-foundation/session-desktop';
6+
const API_URL = `https://api.github.com/repos/${REPO}/releases`;
7+
8+
// Types
9+
interface Asset {
10+
name: string;
11+
download_count: number;
12+
}
13+
14+
interface Release {
15+
tag_name: string;
16+
published_at: string;
17+
assets: Asset[];
18+
}
19+
20+
interface ReleaseStats {
21+
version: string;
22+
snapshotDate: string;
23+
dateCaptured: string;
24+
debDownloads: number;
25+
appimageDownloads: number;
26+
rpmDownloads: number;
27+
dmgArm64Downloads: number;
28+
dmgX64Downloads: number;
29+
zipArm64Downloads: number;
30+
zipX64Downloads: number;
31+
exeDownloads: number;
32+
}
33+
34+
// Function to make HTTPS requests
35+
function fetchJSON(url: string): Promise<Release[]> {
36+
return new Promise((resolve, reject) => {
37+
https
38+
.get(
39+
url,
40+
{
41+
headers: {
42+
'User-Agent': 'Node.js Script',
43+
Accept: 'application/vnd.github.v3+json',
44+
},
45+
},
46+
(res) => {
47+
let data = '';
48+
49+
res.on('data', (chunk: Buffer) => {
50+
data += chunk.toString();
51+
});
52+
53+
res.on('end', () => {
54+
if (res.statusCode === 200) {
55+
resolve(JSON.parse(data));
56+
} else {
57+
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
58+
}
59+
});
60+
}
61+
)
62+
.on('error', reject);
63+
});
64+
}
65+
66+
// Function to count downloads by file extension
67+
function countDownloadsByExtension(assets: Asset[], extension: string): number {
68+
return assets
69+
.filter((asset) => asset.name.endsWith(extension))
70+
.reduce((sum, asset) => sum + asset.download_count, 0);
71+
}
72+
73+
// Function to count downloads by extension and pattern
74+
function countDownloadsByPattern(
75+
assets: Asset[],
76+
extension: string,
77+
pattern: string
78+
): number {
79+
return assets
80+
.filter(
81+
(asset) => asset.name.endsWith(extension) && asset.name.includes(pattern)
82+
)
83+
.reduce((sum, asset) => sum + asset.download_count, 0);
84+
}
85+
86+
// Function to get current date in YYYY-MM-DD format
87+
function getCurrentDate(): string {
88+
const now = new Date();
89+
return now.toISOString().split('T')[0];
90+
}
91+
92+
// Main function
93+
async function generateReleaseStatsCSV(): Promise<void> {
94+
try {
95+
console.log('Fetching releases from GitHub...');
96+
const releases = await fetchJSON(API_URL);
97+
98+
// Take only the last 10 releases
99+
const last10Releases = releases.slice(0, 10);
100+
101+
console.log(`Processing ${last10Releases.length} releases...`);
102+
103+
const snapshotDate = getCurrentDate();
104+
console.log(`Snapshot date: ${snapshotDate}`);
105+
106+
// Build CSV data
107+
const csvRows: string[] = [];
108+
109+
// Header row
110+
csvRows.push(
111+
'version,snapshot_date,release_date,.deb,.appimage,.rpm,.dmg_arm64,.dmg_x64,.zip_arm64,.zip_x64,.exe'
112+
);
113+
114+
// Data rows
115+
for (const release of last10Releases) {
116+
const stats: ReleaseStats = {
117+
version: release.tag_name.replace(/^v/, ''), // Remove 'v' prefix
118+
snapshotDate: snapshotDate,
119+
dateCaptured: release.published_at.split('T')[0], // Get YYYY-MM-DD
120+
debDownloads: countDownloadsByExtension(release.assets, '.deb'),
121+
appimageDownloads: countDownloadsByExtension(
122+
release.assets,
123+
'.AppImage'
124+
),
125+
rpmDownloads: countDownloadsByExtension(release.assets, '.rpm'),
126+
dmgArm64Downloads: countDownloadsByPattern(
127+
release.assets,
128+
'.dmg',
129+
'arm64'
130+
),
131+
dmgX64Downloads: countDownloadsByPattern(release.assets, '.dmg', 'x64'),
132+
zipArm64Downloads: countDownloadsByPattern(
133+
release.assets,
134+
'.zip',
135+
'arm64'
136+
),
137+
zipX64Downloads: countDownloadsByPattern(release.assets, '.zip', 'x64'),
138+
exeDownloads: countDownloadsByExtension(release.assets, '.exe'),
139+
};
140+
141+
csvRows.push(
142+
[
143+
stats.version,
144+
stats.snapshotDate,
145+
stats.dateCaptured,
146+
stats.debDownloads,
147+
stats.appimageDownloads,
148+
stats.rpmDownloads,
149+
stats.dmgArm64Downloads,
150+
stats.dmgX64Downloads,
151+
stats.zipArm64Downloads,
152+
stats.zipX64Downloads,
153+
stats.exeDownloads,
154+
].join(',')
155+
);
156+
}
157+
158+
// Write to file
159+
const csvContent = csvRows.join('\n');
160+
const filename = 'session-desktop-release-stats.csv';
161+
162+
fs.writeFileSync(filename, csvContent);
163+
164+
console.log(`\nCSV file generated: ${filename}`);
165+
console.log(`Total releases processed: ${last10Releases.length}`);
166+
console.log('\nFull content:');
167+
console.log(csvRows.join('\n'));
168+
} catch (error) {
169+
console.error(
170+
'Error:',
171+
error instanceof Error ? error.message : 'Unknown error'
172+
);
173+
process.exit(1);
174+
}
175+
}
176+
177+
// Run the script
178+
generateReleaseStatsCSV();

release_stats/tsconfig.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"module": "commonjs",
5+
"esModuleInterop": true,
6+
"strict": true,
7+
"outDir": "./dist"
8+
},
9+
"include": ["*.ts"]
10+
}

0 commit comments

Comments
 (0)