modded7/buildtools/tasks/deploy/curseforge.ts
Exaxxion d3c771d94f
Update buildscripts (#335)
Merged changes from https://github.com/Nomifactory/Nomifactory/tree/dev/buildtools made after February 2022 (when I helped @tracer4b get the build scripts at the time set up).

CurseForge subsequently did its API changes so the old scripts don't work anymore.

You will need to create a Secret called CFCORE_API_TOKEN which contains the CurseForge API authentication token you wish to use (for https://api.curseforge.com). When running build scripts locally, you need to have an environment variable set with the same name and value.

This token allows the script to download mod jars required for building the server zip. Without this token, at the very least mods that disallow third-party downloads would return blank download links and that build target would fail. It might be needed to use the API at all, but @NotMyWing is our CICD author and resident expert, so I would defer to him on details.

/* Commits */

* Switch API calls to CFCore (#914)

Necessary to continue using the CurseForge API.

* Propagate the CFCore token to GHA

* Move download URL forging to fetchFileInfo

* Add CurseForge Beta deployment workflow (#944)

---------

Co-authored-by: Neeve <winwyv@gmail.com>
2023-04-17 18:21:45 +10:00

168 lines
4.6 KiB
TypeScript

import { modpackManifest, sharedDestDirectory } from "../../globals";
import request from "requestretry";
import fs from "fs";
import log from "fancy-log";
import upath from "upath";
import buildConfig from "../../buildConfig";
import { makeArtifactNameBody } from "../../util/util";
import sanitize from "sanitize-filename";
const CURSEFORGE_LEGACY_ENDPOINT = "https://minecraft.curseforge.com/";
const variablesToCheck = ["CURSEFORGE_API_TOKEN", "CURSEFORGE_PROJECT_ID"];
interface CFUploadOptions {
releaseType?: "release" | "beta";
}
/**
* Uploads beta artifacts to CurseForge.
*/
export async function deployCurseForgeBeta(): Promise<void> {
/**
* Obligatory variable check.
*/
["RC_VERSION", ...variablesToCheck].forEach((vari) => {
if (!process.env[vari]) {
throw new Error(`Environmental variable ${vari} is unset.`);
}
});
const version = process.env.RC_VERSION;
const flavorTitle = process.env.BUILD_FLAVOR_TITLE;
const displayName = [modpackManifest.name, [version.replace(/^v/, ""), "Release Candidate"].join(" "), flavorTitle]
.filter(Boolean)
.join(" - ");
const files = [
{
name: sanitize((makeArtifactNameBody(modpackManifest.name) + "-client.zip").toLowerCase()),
displayName: displayName,
},
{
name: sanitize((makeArtifactNameBody(modpackManifest.name) + "-server.zip").toLowerCase()),
displayName: `${displayName} Server`,
},
];
/**
* Obligatory file check.
*/
await upload(files, {
releaseType: "beta",
});
}
async function upload(files: { name: string; displayName: string }[], opts?: CFUploadOptions) {
opts = opts || {};
files.forEach((file) => {
const path = upath.join(buildConfig.buildDestinationDirectory, file.name);
if (!fs.existsSync(path)) {
throw new Error(`File ${path} doesn't exist!`);
}
});
// Since we've built everything beforehand, the changelog must be available in the shared directory.
const changelog = await (await fs.promises.readFile(upath.join(sharedDestDirectory, "CHANGELOG.md")))
.toString()
.replace(/\n/g, " \n")
.replace(/\n\*/g, "\n•");
const tokenHeaders = {
"X-Api-Token": process.env.CURSEFORGE_API_TOKEN,
};
// Fetch the list of Minecraft versions from CurseForge.
log("Fetching CurseForge version manifest...");
const versionsManifest =
(await request({
uri: CURSEFORGE_LEGACY_ENDPOINT + "api/game/versions",
headers: tokenHeaders,
method: "GET",
json: true,
fullResponse: false,
maxAttempts: 5,
})) || [];
if (!versionsManifest) {
throw new Error("Failed to fetch CurseForge version manifest.");
}
const version = versionsManifest.find((m) => m.name == modpackManifest.minecraft.version);
if (!version) {
throw new Error(`Version ${modpackManifest.minecraft.version} not found on CurseForge.`);
}
let clientFileID: number | null;
// Upload artifacts.
for (const file of files) {
const options = {
uri: CURSEFORGE_LEGACY_ENDPOINT + `api/projects/${process.env.CURSEFORGE_PROJECT_ID}/upload-file`,
method: "POST",
headers: {
...tokenHeaders,
"Content-Type": "multipart/form-data",
},
formData: {
metadata: JSON.stringify({
changelog: changelog,
changelogType: "markdown",
releaseType: opts.releaseType || "release",
parentFileID: clientFileID,
gameVersions: clientFileID ? undefined : [version.id],
displayName: file.displayName,
}),
file: fs.createReadStream(upath.join(buildConfig.buildDestinationDirectory, file.name)),
},
json: true,
fullResponse: false,
};
log(`Uploading ${file.name} to CurseForge...` + (clientFileID ? `(child of ${clientFileID})` : ""));
const response = await request(options);
if (response && response.id) {
if (!clientFileID) {
clientFileID = response.id;
}
} else {
throw new Error(`Failed to upload ${file.name}: Invalid Response.`);
}
}
}
/**
* Uploads build artifacts to CurseForge.
*/
export async function deployCurseForge(): Promise<void> {
/**
* Obligatory variable check.
*/
["GITHUB_TAG", ...variablesToCheck].forEach((vari) => {
if (!process.env[vari]) {
throw new Error(`Environmental variable ${vari} is unset.`);
}
});
const tag = process.env.GITHUB_TAG;
const flavorTitle = process.env.BUILD_FLAVOR_TITLE;
const displayName = [modpackManifest.name, tag.replace(/^v/, ""), flavorTitle].filter(Boolean).join(" - ");
const files = [
{
name: sanitize((makeArtifactNameBody(modpackManifest.name) + "-client.zip").toLowerCase()),
displayName: displayName,
},
{
name: sanitize((makeArtifactNameBody(modpackManifest.name) + "-server.zip").toLowerCase()),
displayName: `${displayName} Server`,
},
];
upload(files);
}