Skip to content

Commit 9590659

Browse files
feat: initial version (#2)
1 parent f6ea473 commit 9590659

10 files changed

Lines changed: 889 additions & 2 deletions

File tree

.github/scripts/download-utils.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Download Utilities
3+
* Handles downloading assets from GitHub releases
4+
*/
5+
const { writeFile } = require('./file-utils');
6+
7+
/**
8+
* Download file from URL with authentication
9+
*/
10+
async function downloadFile(url, filePath, token) {
11+
const response = await fetch(url, {
12+
headers: {
13+
'Authorization': `token ${token}`,
14+
'User-Agent': 'GitHub Actions'
15+
}
16+
});
17+
18+
if (!response.ok) {
19+
throw new Error(`Failed to download: ${response.statusText}`);
20+
}
21+
22+
const buffer = await response.arrayBuffer();
23+
writeFile(filePath, Buffer.from(buffer));
24+
}
25+
26+
/**
27+
* Download asset with retry logic
28+
*/
29+
async function downloadAssetWithRetry(url, filePath, token, maxRetries = 3) {
30+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
31+
try {
32+
await downloadFile(url, filePath, token);
33+
return true;
34+
} catch (error) {
35+
console.error(`Download attempt ${attempt} failed: ${error.message}`);
36+
if (attempt === maxRetries) {
37+
throw error;
38+
}
39+
// Wait before retry (exponential backoff)
40+
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt - 1)));
41+
}
42+
}
43+
return false;
44+
}
45+
46+
module.exports = {
47+
downloadFile,
48+
downloadAssetWithRetry
49+
};

.github/scripts/file-utils.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* File System Utilities
3+
* Handles directory creation and file operations
4+
*/
5+
const fs = require('fs');
6+
const path = require('path');
7+
8+
/**
9+
* Ensure directory exists, create if it doesn't
10+
*/
11+
function ensureDir(dirPath) {
12+
if (!fs.existsSync(dirPath)) {
13+
fs.mkdirSync(dirPath, { recursive: true });
14+
}
15+
}
16+
17+
/**
18+
* Check if file exists
19+
*/
20+
function fileExists(filePath) {
21+
return fs.existsSync(filePath);
22+
}
23+
24+
/**
25+
* Write file safely
26+
*/
27+
function writeFile(filePath, content) {
28+
fs.writeFileSync(filePath, content);
29+
}
30+
31+
/**
32+
* Read file safely
33+
*/
34+
function readFile(filePath) {
35+
return fs.readFileSync(filePath);
36+
}
37+
38+
module.exports = {
39+
ensureDir,
40+
fileExists,
41+
writeFile,
42+
readFile
43+
};

.github/scripts/hash-utils.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Hash Generation Utilities
3+
* Handles generating hash files for downloaded assets
4+
*/
5+
const crypto = require('crypto');
6+
const { readFile, writeFile } = require('./file-utils');
7+
8+
/**
9+
* Generate hash for a file using specified algorithm
10+
*/
11+
function generateHash(filePath, algorithm) {
12+
const fileBuffer = readFile(filePath);
13+
const hash = crypto.createHash(algorithm);
14+
hash.update(fileBuffer);
15+
return hash.digest('hex');
16+
}
17+
18+
/**
19+
* Generate all hash files for an asset
20+
*/
21+
function generateHashFiles(assetPath) {
22+
console.log(`Generating hash files for: ${assetPath}`);
23+
24+
try {
25+
// Generate hashes
26+
const sha256Hash = generateHash(assetPath, 'sha256');
27+
const sha512Hash = generateHash(assetPath, 'sha512');
28+
const md5Hash = generateHash(assetPath, 'md5');
29+
30+
// Write hash files
31+
writeFile(`${assetPath}.sha256`, sha256Hash);
32+
writeFile(`${assetPath}.sha512`, sha512Hash);
33+
writeFile(`${assetPath}.md5`, md5Hash);
34+
35+
console.log(`Hash files created for: ${assetPath}`);
36+
return true;
37+
} catch (error) {
38+
console.error(`Failed to generate hash files for ${assetPath}: ${error.message}`);
39+
return false;
40+
}
41+
}
42+
43+
module.exports = {
44+
generateHash,
45+
generateHashFiles
46+
};

.github/scripts/sync-assets.js

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/**
2+
* Asset Synchronization Script
3+
* Main script for downloading and organizing release assets
4+
*/
5+
const path = require('path');
6+
const { ensureDir, fileExists, writeFile } = require('./file-utils');
7+
const { generateHashFiles } = require('./hash-utils');
8+
const { downloadAssetWithRetry } = require('./download-utils');
9+
10+
/**
11+
* Process a single repository and download its release assets
12+
*/
13+
async function processRepository(github, context, repo, repositoryData, totalAssets, isPullRequest = false, releaseLimit = null) {
14+
console.log(`Processing repository: ${repo.name}`);
15+
16+
let processedReleasesWithAssets = 0;
17+
18+
try {
19+
// Get releases for the repository with pagination
20+
const releases = await github.paginate(github.rest.repos.listReleases, {
21+
owner: context.repo.owner,
22+
repo: repo.name,
23+
per_page: 100
24+
});
25+
26+
// Filter out draft and prerelease
27+
const publishedReleases = releases.filter(release => !release.draft && !release.prerelease);
28+
29+
if (publishedReleases.length === 0) {
30+
console.log(`No published releases found for ${repo.name}`);
31+
return { totalAssets, processedReleases: 0 };
32+
}
33+
34+
const repoData = {
35+
name: repo.name,
36+
releases: []
37+
};
38+
39+
for (const release of publishedReleases) {
40+
// For pull requests, stop after processing the specified number of releases with assets
41+
if (isPullRequest && releaseLimit && processedReleasesWithAssets >= releaseLimit) {
42+
console.log(`PR mode: Reached limit of ${releaseLimit} releases with assets for ${repo.name}`);
43+
break;
44+
}
45+
46+
const assetCount = await processRelease(repo.name, release);
47+
totalAssets += assetCount;
48+
49+
if (assetCount > 0) {
50+
repoData.releases.push({
51+
tag: release.tag_name,
52+
assetCount: assetCount
53+
});
54+
processedReleasesWithAssets++;
55+
}
56+
}
57+
58+
if (repoData.releases.length > 0) {
59+
repositoryData.push(repoData);
60+
}
61+
62+
} catch (error) {
63+
console.error(`Error processing repository ${repo.name}: ${error.message}`);
64+
}
65+
66+
return { totalAssets, processedReleases: processedReleasesWithAssets };
67+
}
68+
69+
/**
70+
* Process a single release and download its assets
71+
*/
72+
async function processRelease(repoName, release) {
73+
console.log(`Processing release: ${release.tag_name}`);
74+
75+
if (release.assets.length === 0) {
76+
console.log(`No assets found for release ${release.tag_name}`);
77+
return 0;
78+
}
79+
80+
// Create directory structure
81+
const releaseDir = path.join(repoName, release.tag_name);
82+
ensureDir(releaseDir);
83+
84+
let assetCount = 0;
85+
86+
for (const asset of release.assets) {
87+
const downloaded = await processAsset(releaseDir, asset);
88+
if (downloaded) {
89+
assetCount++;
90+
}
91+
}
92+
93+
return assetCount;
94+
}
95+
96+
/**
97+
* Process a single asset - download and generate hashes if not exists
98+
*/
99+
async function processAsset(releaseDir, asset) {
100+
const assetPath = path.join(releaseDir, asset.name);
101+
102+
// Skip if asset already exists
103+
if (fileExists(assetPath)) {
104+
console.log(`Asset already exists: ${assetPath}`);
105+
return true;
106+
}
107+
108+
console.log(`Downloading: ${asset.name}`);
109+
110+
try {
111+
await downloadAssetWithRetry(
112+
asset.browser_download_url,
113+
assetPath,
114+
process.env.GITHUB_TOKEN
115+
);
116+
117+
console.log(`Successfully downloaded: ${assetPath}`);
118+
119+
// Generate hash files
120+
const hashSuccess = generateHashFiles(assetPath);
121+
122+
return hashSuccess;
123+
124+
} catch (error) {
125+
console.error(`Failed to download ${asset.name}: ${error.message}`);
126+
return false;
127+
}
128+
}
129+
130+
/**
131+
* Generate repository data JSON for the web interface
132+
*/
133+
function generateRepositoryDataJson(repositoryData, totalAssets) {
134+
const dataJson = {
135+
repositories: repositoryData,
136+
lastUpdated: new Date().toISOString(),
137+
totalRepositories: repositoryData.length,
138+
totalReleases: repositoryData.reduce((sum, repo) => sum + repo.releases.length, 0),
139+
totalAssets: totalAssets
140+
};
141+
142+
writeFile('repository-data.json', JSON.stringify(dataJson, null, 2));
143+
console.log('Generated repository-data.json');
144+
}
145+
146+
/**
147+
* Main function to sync all release assets
148+
*/
149+
async function syncReleaseAssets(github, context, isPullRequest = false) {
150+
console.log('Getting repositories from organization...');
151+
152+
if (isPullRequest) {
153+
console.log('Running in pull request mode - limiting to 2 releases with assets per repository');
154+
}
155+
156+
// Get all repositories with pagination
157+
const repos = await github.paginate(github.rest.repos.listForOrg, {
158+
org: context.repo.owner,
159+
type: 'all',
160+
per_page: 100
161+
});
162+
163+
console.log(`Found ${repos.length} repositories`);
164+
165+
const repositoryData = [];
166+
let totalAssets = 0;
167+
let totalProcessedReleases = 0;
168+
169+
// Process each repository
170+
for (const repo of repos) {
171+
const result = await processRepository(
172+
github,
173+
context,
174+
repo,
175+
repositoryData,
176+
totalAssets,
177+
isPullRequest,
178+
isPullRequest ? 2 : null
179+
);
180+
totalAssets = result.totalAssets;
181+
totalProcessedReleases += result.processedReleases;
182+
}
183+
184+
// Generate repository data JSON for the index.html
185+
generateRepositoryDataJson(repositoryData, totalAssets);
186+
187+
if (isPullRequest) {
188+
console.log(`PR mode: Processed ${repositoryData.length} repositories with ${totalProcessedReleases} releases containing assets`);
189+
} else {
190+
console.log(`Processed ${repositoryData.length} repositories with assets`);
191+
}
192+
}
193+
194+
module.exports = {
195+
syncReleaseAssets
196+
};

0 commit comments

Comments
 (0)