diff --git a/scripts/migrate-releases.js b/scripts/migrate-releases.js new file mode 100644 index 000000000..d8ed53796 --- /dev/null +++ b/scripts/migrate-releases.js @@ -0,0 +1,125 @@ +// migrate-releases.js +const fetch = require('node-fetch').default; +const fs = require('fs'); +const path = require('path'); + +const TOKEN = process.env.GITHUB_TOKEN; +const SOURCE_REPO = 'TriliumNext/Notes'; +const DEST_REPO = 'TriliumNext/trilium'; + +if (!TOKEN) { + console.error('Error: Please set your GITHUB_TOKEN environment variable'); + process.exit(1); +} + +const headers = { + Authorization: `token ${TOKEN}`, + Accept: 'application/vnd.github.v3+json', +}; + +async function getReleases(repo) { + let releases = []; + let page = 1; + + while (true) { + console.log("Got fetch", fetch); + const res = await fetch( + `https://api.github.com/repos/${repo}/releases?per_page=100&page=${page}`, + { headers } + ); + if (!res.ok) throw new Error(`Failed to get releases: ${res.status} ${res.statusText}`); + + const data = await res.json(); + if (data.length === 0) break; + + releases = releases.concat(data); + page++; + } + return releases; +} + +async function createRelease(repo, release) { + // Strip id, url etc. fields to prepare payload + const payload = { + tag_name: release.tag_name, + target_commitish: "main", + name: release.name, + body: release.body, + draft: release.draft, + prerelease: release.prerelease, + }; + + const res = await fetch(`https://api.github.com/repos/${repo}/releases`, { + method: 'POST', + headers: { ...headers, 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + + console.log(`POST to https://api.github.com/repos/${repo}/releases with payload:`, payload); + + if (!res.ok) { + const text = await res.text(); + throw new Error(`Failed to create release: ${res.status} ${res.statusText} - ${text}`); + } + + return await res.json(); +} + +async function downloadAsset(assetUrl, filename) { + const res = await fetch(assetUrl, { headers: { ...headers, Accept: 'application/octet-stream' } }); + if (!res.ok) throw new Error(`Failed to download asset: ${res.status} ${res.statusText}`); + const buffer = await res.buffer(); + fs.writeFileSync(filename, buffer); +} + +async function uploadAsset(uploadUrl, filepath) { + const filename = path.basename(filepath); + const stats = fs.statSync(filepath); + const res = await fetch(`${uploadUrl}?name=${encodeURIComponent(filename)}`, { + method: 'POST', + headers: { + Authorization: `token ${TOKEN}`, + 'Content-Type': 'application/octet-stream', + 'Content-Length': stats.size, + }, + body: fs.createReadStream(filepath), + }); + + if (!res.ok) { + const text = await res.text(); + throw new Error(`Failed to upload asset: ${res.status} ${res.statusText} - ${text}`); + } + + return await res.json(); +} + +async function migrate() { + console.log(`Fetching releases from ${SOURCE_REPO}...`); + const releases = await getReleases(SOURCE_REPO); + console.log(`Found ${releases.length} releases.`); + + releases.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); + + for (const release of releases) { + console.log(`Migrating release: ${release.name} (${release.tag_name})`); + const newRelease = await createRelease(DEST_REPO, release); + + // Download and upload assets if any + for (const asset of release.assets) { + const tempFile = path.join(__dirname, asset.name); + console.log(`Downloading asset ${asset.name}...`); + await downloadAsset(asset.url, tempFile); + + console.log(`Uploading asset ${asset.name}...`); + await uploadAsset(newRelease.upload_url.replace('{?name,label}', ''), tempFile); + + fs.unlinkSync(tempFile); // Clean up temp file + } + } + console.log('Migration complete!'); +} + +migrate().catch((err) => { + console.error('Migration failed:', err); + process.exit(1); +});