const version = process.argv[2]; if (!version) { console.error("Version not passed to create-gitea-release.js"); process.exit(1); } import fs from "fs-extra"; import path from "path"; import { spawnSync } from "child_process"; import { fileURLToPath } from "url"; import fetch from "node-fetch"; import dotenv from "dotenv"; dotenv.config({ path: "./.env" }); import { createRequire } from "node:module"; const require = createRequire(import.meta.url); const conventionalChangelog = require("conventional-changelog-conventionalcommits").default || require("conventional-changelog-conventionalcommits"); // Resolve the directory of the current script const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Absolute path to BUILD_NUMBER const buildNumberPath = path.resolve(__dirname, "../BUILD_NUMBER"); // Load build number from BUILD_NUMBER file let buildNumber = "0"; try { const rawBuild = fs.readFileSync(buildNumberPath, "utf8"); console.log("Raw build", rawBuild); buildNumber = rawBuild.trim(); } catch (e) { console.log(e); console.warn("BUILD_NUMBER file not found, defaulting to 0"); } const fullVersion = `${version}.${buildNumber}`; const { GITEA_URL, GITEA_USERNAME, GITEA_REPO, GITEA_TOKEN } = process.env; if (!GITEA_URL || !GITEA_USERNAME || !GITEA_REPO || !GITEA_TOKEN) { console.error("Missing required environment variables"); process.exit(1); } // Step 1: Generate or update CHANGELOG.md // console.log("Generating CHANGELOG.md..."); // const result = spawnSync( // "npx", // [ // "conventional-changelog", // "-p", // "conventionalcommits", // "-i", // "CHANGELOG.md", // "-s", // "-r", // "0", // ], // { stdio: "inherit", shell: true } // ); // if (result.status !== 0) { // console.error("Failed to generate changelog"); // process.exit(1); // } // Corrected function to get the latest changelog entry from CHANGELOG.md const getLatestChangelog = async () => { try { const changelogPath = path.resolve(__dirname, "../CHANGELOG.md"); const changelogContent = await fs.readFile(changelogPath, "utf8"); // Trim the content to remove potential leading/trailing whitespace // and split into lines for easier processing. const lines = changelogContent.trim().split(/\r?\n/); // Handles both \n and \r\n let latestReleaseNotes = []; let inLatestRelease = false; let foundFirstHeading = false; for (const line of lines) { // Check for a release heading (e.g., "## [0.0.3-alpha.15] (2025-07-12)") if (line.match(/^## \[.*?\] \(.*?\)$/)) { if (!foundFirstHeading) { // This is the first release heading we encounter, so start capturing inLatestRelease = true; foundFirstHeading = true; // Skip the heading line itself, or capture it if you want it in the notes // For typical release notes, you usually want the content *under* the heading. continue; // Skip the heading line itself from the notes } else { // We've found a second release heading, meaning the latest release notes have ended break; } } // If we are currently inside the latest release block, add the line if (inLatestRelease) { latestReleaseNotes.push(line); } } // Clean up the collected notes const cleanedNotes = latestReleaseNotes.join("\n").trim(); if (cleanedNotes) { console.log("Successfully extracted latest changelog notes."); return cleanedNotes; } else { console.warn( "Could not find any content for the latest changelog entry in CHANGELOG.md." ); return "No changelog notes available."; } } catch (err) { console.error("Error reading or parsing CHANGELOG.md:", err); throw err; } }; const releaseNotes = await getLatestChangelog(); // Step 3: Create or update Gitea release const createOrUpdateRelease = async () => { const tagName = `v${version}`; const apiBase = `https://${GITEA_URL}/api/v1/repos/${GITEA_USERNAME}/${GITEA_REPO}`; const existing = await fetch(`${apiBase}/releases/tags/${tagName}`, { headers: { Authorization: `token ${GITEA_TOKEN}` }, }); let release; if (existing.ok) { const existingRelease = await existing.json(); console.log(`Release ${tagName} already exists. Updating it.`); const updateResponse = await fetch( `${apiBase}/releases/${existingRelease.id}`, { method: "PATCH", headers: { Authorization: `token ${GITEA_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: tagName, body: releaseNotes, draft: false, prerelease: true, }), } ); if (!updateResponse.ok) { const errorText = await updateResponse.text(); throw new Error( `Failed to update release: ${updateResponse.status} - ${errorText}` ); } release = await updateResponse.json(); console.log("Release updated:", release.html_url || release.url); } else if (existing.status === 404) { const createResponse = await fetch(`${apiBase}/releases`, { method: "POST", headers: { Authorization: `token ${GITEA_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ tag_name: tagName, name: `Release ${fullVersion}`, body: releaseNotes, draft: false, prerelease: true, }), }); if (!createResponse.ok) { const errorText = await createResponse.text(); throw new Error( `Failed to create release: ${createResponse.status} - ${errorText}` ); } release = await createResponse.json(); console.log("Release created:", release.html_url || release.url); } else { const errorText = await existing.text(); throw new Error( `Failed to check release: ${existing.status} - ${errorText}` ); } return release; }; const uploadAsset = async (release) => { const apiUrl = `https://${GITEA_URL}/api/v1/repos/${GITEA_USERNAME}/${GITEA_REPO}/releases/assets?tag=${release.tag_name}`; const filePath = `releases/release-${fullVersion}.zip`; if (!(await fs.pathExists(filePath))) { console.warn(`Zip file not found: ${filePath}. Skipping asset upload.`); return; } const FormData = (await import("form-data")).default; const form = new FormData(); form.append("name", `release-${fullVersion}.zip`); form.append("attachment", fs.createReadStream(filePath)); const response = await fetch(apiUrl, { method: "POST", headers: { Authorization: `token ${GITEA_TOKEN}`, ...form.getHeaders(), }, body: form, }); if (!response.ok) { const errorText = await response.text(); throw new Error( `Failed to upload asset: ${response.status} - ${errorText}` ); } const asset = await response.json(); console.log("Asset uploaded:", asset.browser_download_url || asset.url); }; // Run everything (async () => { try { const release = await createOrUpdateRelease(); // await uploadAsset(release); // fix this later and just update the readme. } catch (err) { console.error(err); process.exit(1); } })();