Sync Modrinth Releases to GitHub #92
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Sync Modrinth Releases to GitHub | |
| on: | |
| schedule: | |
| - cron: '0 0 * * *' | |
| workflow_dispatch: | |
| jobs: | |
| sync-to-github: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v3 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v3 | |
| with: | |
| node-version: '18' | |
| - name: Create sync script | |
| run: | | |
| cat > sync-modrinth.js << 'EOF' | |
| const fs = require('fs'); | |
| const https = require('https'); | |
| const path = require('path'); | |
| const { execSync } = require('child_process'); | |
| // Constants | |
| const MODRINTH_PROJECT_ID = process.env.MODRINTH_PROJECT_ID; | |
| const GITHUB_TOKEN = process.env.GITHUB_TOKEN; | |
| const GITHUB_REPO = process.env.GITHUB_REPOSITORY; | |
| // Get existing GitHub releases | |
| function getGitHubReleases() { | |
| return new Promise((resolve, reject) => { | |
| const options = { | |
| hostname: 'api.github.com', | |
| path: `/repos/${GITHUB_REPO}/releases`, | |
| method: 'GET', | |
| headers: { | |
| 'User-Agent': 'ModrinthSync', | |
| 'Authorization': `token ${GITHUB_TOKEN}`, | |
| 'Accept': 'application/vnd.github.v3+json' | |
| } | |
| }; | |
| const req = https.request(options, (res) => { | |
| let data = ''; | |
| res.on('data', (chunk) => { | |
| data += chunk; | |
| }); | |
| res.on('end', () => { | |
| if (res.statusCode >= 200 && res.statusCode < 300) { | |
| resolve(JSON.parse(data)); | |
| } else { | |
| reject(new Error(`Status Code: ${res.statusCode}, Body: ${data}`)); | |
| } | |
| }); | |
| }); | |
| req.on('error', (error) => { | |
| reject(error); | |
| }); | |
| req.end(); | |
| }); | |
| } | |
| // Get Modrinth versions | |
| function getModrinthVersions() { | |
| return new Promise((resolve, reject) => { | |
| const options = { | |
| hostname: 'api.modrinth.com', | |
| path: `/v2/project/${MODRINTH_PROJECT_ID}/version`, | |
| method: 'GET', | |
| headers: { | |
| 'User-Agent': 'ModrinthSync/1.0' | |
| } | |
| }; | |
| const req = https.request(options, (res) => { | |
| let data = ''; | |
| res.on('data', (chunk) => { | |
| data += chunk; | |
| }); | |
| res.on('end', () => { | |
| if (res.statusCode >= 200 && res.statusCode < 300) { | |
| resolve(JSON.parse(data)); | |
| } else { | |
| reject(new Error(`Status Code: ${res.statusCode}, Body: ${data}`)); | |
| } | |
| }); | |
| }); | |
| req.on('error', (error) => { | |
| reject(error); | |
| }); | |
| req.end(); | |
| }); | |
| } | |
| // Download file from URL | |
| function downloadFile(url, destination) { | |
| return new Promise((resolve, reject) => { | |
| const file = fs.createWriteStream(destination); | |
| https.get(url, (response) => { | |
| response.pipe(file); | |
| file.on('finish', () => { | |
| file.close(); | |
| resolve(); | |
| }); | |
| }).on('error', (err) => { | |
| fs.unlink(destination); | |
| reject(err); | |
| }); | |
| }); | |
| } | |
| // Create GitHub release | |
| function createGitHubRelease(version) { | |
| return new Promise(async (resolve, reject) => { | |
| try { | |
| // Download the file first | |
| const fileName = path.basename(version.files[0].url); | |
| const filePath = `./${fileName}`; | |
| console.log(`Downloading file from ${version.files[0].url} to ${filePath}`); | |
| await downloadFile(version.files[0].url, filePath); | |
| // Create tag if it doesn't exist | |
| try { | |
| console.log(`Checking if tag ${version.version_number} exists...`); | |
| execSync(`git ls-remote --tags origin refs/tags/${version.version_number}`); | |
| console.log(`Tag ${version.version_number} exists.`); | |
| } catch (err) { | |
| console.log(`Creating tag ${version.version_number}...`); | |
| execSync(`git tag ${version.version_number}`); | |
| execSync(`git push origin ${version.version_number}`); | |
| console.log(`Tag ${version.version_number} created.`); | |
| } | |
| // Prepare release data | |
| const isPrerelease = version.version_type === 'alpha' || version.version_type === 'beta'; | |
| const releaseData = { | |
| tag_name: version.version_number, | |
| name: version.name || `Version ${version.version_number}`, | |
| body: `${version.changelog || 'No changelog provided.'}`, | |
| draft: false, | |
| prerelease: isPrerelease | |
| }; | |
| // Create the release | |
| const options = { | |
| hostname: 'api.github.com', | |
| path: `/repos/${GITHUB_REPO}/releases`, | |
| method: 'POST', | |
| headers: { | |
| 'User-Agent': 'ModrinthSync', | |
| 'Authorization': `token ${GITHUB_TOKEN}`, | |
| 'Content-Type': 'application/json', | |
| 'Accept': 'application/vnd.github.v3+json' | |
| } | |
| }; | |
| const req = https.request(options, (res) => { | |
| let data = ''; | |
| res.on('data', (chunk) => { | |
| data += chunk; | |
| }); | |
| res.on('end', () => { | |
| if (res.statusCode >= 200 && res.statusCode < 300) { | |
| const release = JSON.parse(data); | |
| // Upload the asset | |
| uploadAsset(release.upload_url.replace(/{.*}/, ''), filePath, fileName) | |
| .then(() => resolve(release)) | |
| .catch(reject); | |
| } else { | |
| reject(new Error(`Status Code: ${res.statusCode}, Body: ${data}`)); | |
| } | |
| }); | |
| }); | |
| req.on('error', (error) => { | |
| reject(error); | |
| }); | |
| req.write(JSON.stringify(releaseData)); | |
| req.end(); | |
| } catch (error) { | |
| reject(error); | |
| } | |
| }); | |
| } | |
| // Upload asset to GitHub release | |
| function uploadAsset(uploadUrl, filePath, fileName) { | |
| return new Promise((resolve, reject) => { | |
| const stats = fs.statSync(filePath); | |
| const fileSize = stats.size; | |
| const fileStream = fs.createReadStream(filePath); | |
| const options = { | |
| hostname: new URL(uploadUrl).hostname, | |
| path: `${new URL(uploadUrl).pathname}?name=${encodeURIComponent(fileName)}`, | |
| method: 'POST', | |
| headers: { | |
| 'User-Agent': 'ModrinthSync', | |
| 'Authorization': `token ${GITHUB_TOKEN}`, | |
| 'Content-Type': 'application/java-archive', | |
| 'Content-Length': fileSize, | |
| 'Accept': 'application/vnd.github.v3+json' | |
| } | |
| }; | |
| const req = https.request(options, (res) => { | |
| let data = ''; | |
| res.on('data', (chunk) => { | |
| data += chunk; | |
| }); | |
| res.on('end', () => { | |
| if (res.statusCode >= 200 && res.statusCode < 300) { | |
| fs.unlinkSync(filePath); // Clean up | |
| resolve(JSON.parse(data)); | |
| } else { | |
| reject(new Error(`Status Code: ${res.statusCode}, Body: ${data}`)); | |
| } | |
| }); | |
| }); | |
| req.on('error', (error) => { | |
| reject(error); | |
| }); | |
| fileStream.pipe(req); | |
| }); | |
| } | |
| // Main function | |
| async function syncModrinthToGitHub() { | |
| try { | |
| console.log('Starting Modrinth to GitHub sync...'); | |
| // Get existing GitHub releases | |
| const githubReleases = await getGitHubReleases(); | |
| const existingTags = githubReleases.map(release => release.tag_name); | |
| console.log(`Found ${existingTags.length} existing GitHub releases.`); | |
| // Get Modrinth versions | |
| const modrinthVersions = await getModrinthVersions(); | |
| console.log(`Found ${modrinthVersions.length} versions on Modrinth.`); | |
| // Find versions that don't exist on GitHub and sort by date (oldest first) | |
| const newVersions = modrinthVersions | |
| .filter(version => !existingTags.includes(version.version_number)) | |
| .sort((a, b) => new Date(a.date_published) - new Date(b.date_published)); | |
| console.log(`Found ${newVersions.length} new versions to sync to GitHub.`); | |
| // Create GitHub releases for new versions | |
| for (const version of newVersions) { | |
| try { | |
| console.log(`Creating GitHub release for version ${version.version_number} (published ${new Date(version.date_published).toISOString()})...`); | |
| await createGitHubRelease(version); | |
| console.log(`Successfully created GitHub release for version ${version.version_number}.`); | |
| } catch (err) { | |
| console.error(`Error creating release for ${version.version_number}:`, err.message); | |
| } | |
| } | |
| console.log('Sync completed.'); | |
| } catch (error) { | |
| console.error('Sync failed:', error.message); | |
| process.exit(1); | |
| } | |
| } | |
| // Execute | |
| syncModrinthToGitHub(); | |
| EOF | |
| - name: Run sync script | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| MODRINTH_PROJECT_ID: "9tQwxSFr" | |
| run: node sync-modrinth.js |