const { appendFile, readFile, readdir, writeFile } = require('node:fs/promises'); const path = require('node:path'); function releaseEnvironment(context) { const env = context.env ?? process.env; const serverUrl = env.GITEA_URL?.replace(/\/$/, ''); const repository = env.GITEA_REPOSITORY; const token = env.GITEA_TOKEN; if (!serverUrl || !repository || !token) { throw new Error('GITEA_URL, GITEA_REPOSITORY, and GITEA_TOKEN are required'); } if (!/^https?:\/\//.test(serverUrl)) { throw new Error('GITEA_URL must be an HTTP(S) URL'); } if (!/^[^/]+\/[^/]+$/.test(repository)) { throw new Error('GITEA_REPOSITORY must use the owner/repository format'); } return { serverUrl, repository, token }; } async function giteaRequest(url, token, options = {}) { const response = await fetch(url, { ...options, headers: { Authorization: `token ${token}`, ...options.headers, }, }); if (!response.ok) { const body = (await response.text()).slice(0, 2_000); throw new Error(`Gitea API request failed (${response.status} ${response.statusText}): ${body}`); } return response; } module.exports.verifyConditions = async (_pluginConfig, context) => { releaseEnvironment(context); }; module.exports.prepare = async (pluginConfig, context) => { const { nextRelease, cwd } = context; const notes = nextRelease.notes?.trim(); if (!notes) return; const { serverUrl, repository } = releaseEnvironment(context); const changelogPath = path.resolve(cwd, pluginConfig.changelogFile ?? 'CHANGELOG.md'); const releaseJsonPath = path.resolve(cwd, pluginConfig.releaseJsonPath ?? 'patches-bundle.json'); let currentChangelog = ''; try { currentChangelog = (await readFile(changelogPath, 'utf8')).trim(); } catch (error) { if (error.code !== 'ENOENT') throw error; } await writeFile( changelogPath, `${notes}\n${currentChangelog ? `\n${currentChangelog}\n` : ''}`, ); const version = nextRelease.version; const assetName = `patches-${version}.mpp`; const releaseData = { created_at: new Date().toISOString().replace(/\.\d{3}Z$/, ''), description: notes, download_url: `${serverUrl}/${repository}/releases/download/${nextRelease.gitTag}/${assetName}`, signature_download_url: '', version, }; await writeFile(releaseJsonPath, `${JSON.stringify(releaseData, null, 2)}\n`); }; module.exports.publish = async (pluginConfig, context) => { const { serverUrl, repository, token } = releaseEnvironment(context); const { branch, cwd, nextRelease } = context; const repositoryPath = repository.split('/').map(encodeURIComponent).join('/'); const apiUrl = `${serverUrl}/api/v1/repos/${repositoryPath}`; const assetDirectory = path.resolve( cwd, pluginConfig.assetDirectory ?? 'patches/build/libs', ); const assetNames = (await readdir(assetDirectory)) .filter((name) => /\.mpp(?:\.asc)?$/.test(name)) .filter((name) => !/(?:sources|javadoc)/.test(name)) .sort(); if (assetNames.length === 0) { throw new Error(`No release assets found in ${assetDirectory}`); } const releaseResponse = await giteaRequest(`${apiUrl}/releases`, token, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ body: nextRelease.notes, draft: false, name: nextRelease.name, prerelease: Boolean(branch.prerelease), tag_name: nextRelease.gitTag, target_commitish: branch.name, }), }); const release = await releaseResponse.json(); for (const assetName of assetNames) { const contents = await readFile(path.join(assetDirectory, assetName)); const form = new FormData(); form.append('attachment', new Blob([contents]), assetName); await giteaRequest( `${apiUrl}/releases/${release.id}/assets?name=${encodeURIComponent(assetName)}`, token, { method: 'POST', body: form }, ); } if (process.env.GITHUB_OUTPUT) { await appendFile( process.env.GITHUB_OUTPUT, `new_release_published=true\nnew_release_git_tag=${nextRelease.gitTag}\n`, ); } return { name: release.name, url: release.html_url }; };