diff --git a/src/utils/hash-utils.ts b/src/utils/hash-utils.ts new file mode 100644 index 0000000..72a6b6f --- /dev/null +++ b/src/utils/hash-utils.ts @@ -0,0 +1,7 @@ +import crypto from "crypto"; +import fs from "fs"; + +export function computeHash(path: string, algorithm: string): Promise { + const hash = crypto.createHash(algorithm); + return new Promise(resolve => fs.createReadStream(path).on("data", data => hash.update(data)).on("end", () => resolve(hash))); +} diff --git a/src/utils/modrinth-utils.ts b/src/utils/modrinth-utils.ts index f2e9903..9af2d32 100644 --- a/src/utils/modrinth-utils.ts +++ b/src/utils/modrinth-utils.ts @@ -2,12 +2,13 @@ import { FormData } from "formdata-node"; import { fileFromPath } from "formdata-node/file-from-path"; import fetch from "node-fetch"; import { File } from "./file"; +import { computeHash } from "./hash-utils"; -export async function createVersion(id: string, data: Record, files: File[], token: string): Promise { +export async function createVersion(modId: string, data: Record, files: File[], token: string): Promise { data = { dependencies: [], ...data, - mod_id: id, + mod_id: modId, file_parts: files.map((_, i) => i.toString()) }; @@ -32,5 +33,27 @@ export async function createVersion(id: string, data: Record, files throw new Error(`Failed to upload file: ${response.status} (${errorText})`); } - return (<{ id: string }>await response.json()).id; -} \ No newline at end of file + const versionId = (<{ id: string }>await response.json()).id; + const primaryFile = files[0]; + if (primaryFile) { + await makeFilePrimary(versionId, primaryFile.path, token); + } + return versionId; +} + +export async function makeFilePrimary(versionId: string, filePath: string, token: string): Promise { + const algorithm = "sha1"; + const hash = (await computeHash(filePath, algorithm)).digest("hex"); + + const response = await fetch(`https://api.modrinth.com/api/v1/version/${versionId}`, { + method: "PATCH", + headers: { + "Authorization": token, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + primary_file: [algorithm, hash] + }) + }); + return response.ok; +} diff --git a/test/hast-utils.test.ts b/test/hast-utils.test.ts new file mode 100644 index 0000000..e733af0 --- /dev/null +++ b/test/hast-utils.test.ts @@ -0,0 +1,22 @@ +import { describe, test, expect } from "@jest/globals"; +import { computeHash } from "../src/utils/hash-utils"; + +describe("computeHash", () => { + test("sha1 is supported", async () => { + const algorithm = "sha1"; + expect((await computeHash("./test/content/fabric.mod.json", algorithm)).digest("hex")).toBe("be90f16aa5c806e2bbcf151efee4ebce7899256b"); + expect((await computeHash("./test/content/mods.toml", algorithm)).digest("hex")).toBe("bf13f9ca36d5a5ebd55a3bda0b432ab4a007791a"); + }); + + test("sha256 is supported", async () => { + const algorithm = "sha256"; + expect((await computeHash("./test/content/fabric.mod.json", algorithm)).digest("hex")).toBe("583334cd90510cdc5c5068dd7cc3423adc9674abf8e200b3a165216bb0f6346d"); + expect((await computeHash("./test/content/mods.toml", algorithm)).digest("hex")).toBe("c420fd754f32553dde2a5118c27ca83b644ba73d230baf723fa00d5cb5b1aaac"); + }); + + test("sha512 is supported", async () => { + const algorithm = "sha512"; + expect((await computeHash("./test/content/fabric.mod.json", algorithm)).digest("hex")).toBe("18582ba1fbec5ab05d5b5679f6d5fa157792959fd22ac091f1be264fc709996d900c8514265f0d24e533c1174da2919fdd7441d1215bbf6866e68f71114979af"); + expect((await computeHash("./test/content/mods.toml", algorithm)).digest("hex")).toBe("b15c84290a1f9fad17fa4f3429fbb67d2555feafd617a5399ba95d5b6745659bc50e16e810d3bfe5bbdd4b66720b85daafd5ad78ece9a484c356c358d7b295ec"); + }); +});