Files
logistics_support_tool/scripts/create-gitea-release.js
2025-07-12 17:30:12 -05:00

260 lines
8.5 KiB
JavaScript

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");
console.log(`Attempting to read changelog from: ${changelogPath}`); // Debugging line
const changelogContent = await fs.readFile(changelogPath, "utf8");
console.log(
"Changelog content read successfully (first 200 chars):",
changelogContent.substring(0, 200)
); // Debugging line
const lines = changelogContent.trim().split(/\r?\n/);
let latestReleaseNotes = [];
let inLatestRelease = false;
let foundFirstHeading = false;
// Regex to match a conventional changelog heading format: "## [version](url) (date)"
// The key is to correctly parse the URL part in parentheses.
const releaseHeadingRegex = /^## \[.*?\]\(.*?\)\s\(.*\)$/;
for (const line of lines) {
// Trim each line to handle potential leading/trailing spaces when matching
const trimmedLine = line.trim();
if (trimmedLine.match(releaseHeadingRegex)) {
if (!foundFirstHeading) {
// This is the first (latest) release heading we encounter
inLatestRelease = true;
foundFirstHeading = true;
// We skip the heading line itself from the notes body
continue;
} else {
// This is a subsequent release heading, meaning we've passed the latest release's content
break;
}
}
// If we are currently inside the latest release block, add the line
if (inLatestRelease) {
latestReleaseNotes.push(trimmedLine); // Use trimmedLine here
}
}
// Clean up the collected notes by filtering out empty lines if they are not meaningful content
// and joining them back, then trimming.
const cleanedNotes = latestReleaseNotes
.filter((line) => line !== "") // Remove truly empty lines
.join("\n")
.trim();
if (cleanedNotes) {
console.log(
"Successfully extracted latest changelog notes:\n",
cleanedNotes
);
return cleanedNotes;
} else {
console.warn(
"Could not find any content for the latest changelog entry in CHANGELOG.md. This might mean the file is empty, or the regex for headings is incorrect, or there's no content after the heading."
);
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);
}
})();