/* Category: */ [INTERNAL] /* Fixup for previous commits' bad syntax: */ [FIXUP] [[fixes]] sha = "4f966073890315ae0eb103b6011cdac7e6e960c0" newTitle = "Update GregTech CEu to v2.7.4" newBody = """ [COMBINE] commits = ["bd58b9072f45d647734ae66168cbd27bf9b2f220"] [COMBINE] """ [[fixes]] sha = "bd58b9072f45d647734ae66168cbd27bf9b2f220" newTitle = "Update GT and Related Mods for 1.7" newBody = """ [EXPAND] [[messages]] messageTitle = \"Update GT to 2.7.4\" messageBody = \"\"\" [BREAKING] [DETAILS] details = [ \\"**Lots of Recipes have been moved to the Assembly Line, and now require Assembly Line Research.**\\", \\"Adds Assembly Line Research\\", \\"Adds ME Hatches and Buses, for combining Multiblocks with AE Networks\\", \\"Adds EU Multiblock Power Storage\\", \\"Adds Multiblock Transformer and Laser Power Transfer\\", \\"Adds Long Distance Pipes\\", \\"Reworked & Improved Multiblock UIs\\", \\"Creating Waypoints in the Prospector\\", \\"Fixing many bugs\\", \\"And many more!\\" ] [DETAILS] \"\"\" [EXPAND] """ [[fixes]] sha = "1a40bd87f9c648429059c77a90470e55e0e39c5e" newTitle = "Update QB Jsons and Lang" newBody = "[SKIP]" [[fixes]] sha = "bcae6f4eebf5616766edcc3e735df7cbf6276440" newTitle = "Russian Content Tweaker Translation (#429)" newBody = "[FEATURE]" [[fixes]] sha = "ca64e658083d5ff41f15ce37fe817842018031d3" newTitle = "Update NAE2 to v1.3.1 (#424)" newBody = "[BUG]" [[fixes]] sha = "1a35ae82d9830c5444c5634d93268e3b857f07bd" newTitle = "Fix Description of The First Tier Two Circuits Quest" newBody = "[QB]" [[fixes]] sha = "c66f5428818e02051b77d84fa01792bf2a6d9dcf" newTitle = "Make Neeve Quest Ignore NBT" newBody = "[QB]" [[fixes]] sha = "5b5d13710abe0f96c41ba4a3505969c0a98e23bc" newTitle = "Update and Rewrite Mixer Quest" newBody = "[QB]\n[HM]" [[fixes]] sha = "342d988e322a45e0d141227c9aa34ec8e53663e7" newTitle = "Remove Mention of HV Batteries in Iron Quest" newBody = "[QB]\n[HM]" [[fixes]] sha = "d037ee5f466cec066fac1d14a762a377426a032d" newTitle = "Remove Reward for Bending Machine Quest" newBody = "[QB]\n[HM]" [[fixes]] sha = "03d61c8d5fbaa0439fc823b5aef3a4859b17a444" newTitle = "Move Multiblock Machine Previews Quest" newBody = """ [QB] [DETAILS] details = [\"Moved From `The Beginning` to `Genesis`\"] [DETAILS] """ [[fixes]] sha = "a52b91d97ac6849fe74623e3cc08362eb3ad99fa" newTitle = "Change Mold Quest Task from Ball Mold -> Rotor Mold" newBody = "[QB]\n[HM]" [[fixes]] sha = "fcb48d4bc9f27058c6a2dcca7bf813f3d7f6a6d9" newTitle = "Mention how to Mute Machines in Steam Machines Quest" newBody = "[QB]\n[HM]" [[fixes]] sha = "f59eecad41745f65348da88a3f4c057300459782" newTitle = "Adjust Steam Dynamo Quest" newBody = "[QB]\n[HM]" [[fixes]] sha = "02eaf67bd168eb11a7a71f4d346eb591841d0af3" newTitle = "Specify that Alloy Smelter is used to make Rubber Sheets" newBody = "[QB]\n[HM]" [[fixes]] sha = "a988d2df30e4d01b973870cc85350962a9125f5c" newTitle = "Fix Grammar Errors in 'From Ingots to Wires' Quest" newBody = "[QB]\n[HM]" [[fixes]] sha = "8df9f6e3180323946cc6553cd0fb5e1e96f0ac03" newTitle = "Add Molds Quest to Genesis, Make Glass Quest" newBody = "[QB]\n[HM]" [[fixes]] sha = "5d51fda1c6740edc5021657f90f6ecd1de954d29" newTitle = "Make Drawer Quest Accept Either Task" newBody = "[QB]\n[HM]" [[fixes]] sha = "a02ef21fbae24df59e5fa9a5ef4640752baf835f" newTitle = "New Data Textures (#419)" newBody = "[FEATURE]" [[fixes]] sha = "758f7e704ca8b13031f7ca859e6bc9c0f0a9e786" newTitle = "Fix DML Multiblock Recipes" newBody = "[BUG]" [[fixes]] sha = "5671bca7fe7b685ac3495ef2497c3711a0baa2a0" newTitle = "Improve Mac/Linux Pack Mode Switcher" newBody = "[INTERNAL]" [[fixes]] sha = "5538fe4524dcb0b64a333b756bfeeec34e12661c" newTitle = "Enable shufflemode on main menu (#414)" newBody = "[FEATURE]" [[fixes]] sha = "644491a9f53c0ee300dbaf345f23e5b3f08b3c8a" newTitle = "Release 1.6.1a" newBody = "[NO CATEGORY]" [FIXUP] /* Description: */ TODO: - [x] Changelog Generation - [x] Decomp Expand - [x] Decomp Details - [x] Add Commit Details to Mod Changes - [x] Allow changing of dir - [x] Catch parsing errors - [x] Move to TOML instead of YAML - [x] GHA Workflows - [x] Split workflows into called and callers (See https://docs.github.com/en/actions/using-workflows/reusing-workflows) - [x] Create workflow to generate Release Commit + Changelog, changelog in another branch - [x] Create workflow to grab Changelog from another branch, then deploy - [x] Create workflow to combine previous two - [x] Create workflow to test new commits' body syntax - [x] Split workflows into jobs - [x] Cleanup Code - [x] Split code into multiple files - [x] Document Code (JS Docs) - [x] CONTRIBUTING.md Documentation on workflows, and on templates - [x] Remove changelog files New TODO: - [x] Make workflow to update modlist - [x] Add modlist updating to release commit workflow, split into jobs - [x] https://discord.com/channels/927050775073534012/1131899853052575785/1162897825173086299 - [ ] Update Documentation /* Commits: */ * Create releasecommit.yml * Update releasecommit.yml * Update setup-node & various tasks to v3, improve .gitignore * Working method to add version to Issue Templates Produces weird artifacts... * Revert "Working method to add version to Issue Templates" This reverts commit f63480e2eba4b8ef5269ebc0a26cebd146fbe45e. * WIP method to update release content, using templates * Finish Release Commit Npx Gulp Task * Update Files * Allow for non-release commits * Actually update version.txt * Remove unneded env in updateqb.yml * Fix releasecommit.yml * Remove random patches config file transforming in `version.ts` * Remove note about regex * Add tag to release commits * Allow changelog task to compare with any commit, seperate changelog task * Add generation for CF changelog, begin detailed changelog * Add new formatted commitList * test change to CHANGELOG.md * Fix HTML file formatting; test new MD file formatting * Another HTML File formatting fix * New formatting logic * cleanup quest transform npx task * Cleanup * Progress * Small Fixes * Fix formatting * Fix commits appearing twice * Changelog Categories * Remove Tags from Commit Messages * New Storage System for ChangelogSections * Fix Formatting * Progress of version info for changed mods * Allow external deps * Cleanup Version, Fix Crash, temp change compare to 1.5.2...1.6 * Cleanup code * Cleanup Code, use templates instead of ifs * Expand Decomp, fix some commits not being added * Testing time * What the changelog looks like * Test * Sort logs, with newest on top. Sort Commit Log * Transform markdown entries into HTML at end * Remove section matter npm package * Test * Update changelog * Test nested Details * update changelog files * Begin Commit SHAs of Mod Changes Section Just need to add support, in formatting funct, for multi commits * Test new formatting * Formatting Test * Allow for multi commits in changelog messages * Fix not showing multi commits * Fix formatting * formatting * Add Performance Category * Remove debug messages + date in changelog messages * sort multi authors * Allow for commits to go in multi categories * Start Cleanup Code * Allow changing of output dir * More Cleanup + Create Post 1.7 Version * Catch parsing errors of manifest.json View the magical changelog, from the first file commit, to this commit! * Catch errors in Expand Task, more cleanup * Catch parsing errors in Details task * Make UpdateQB Task not return an error if no changes * Remove Comma Expressions * Catch edge case parsing errors, improve error descriptions * Cleanup parsing errors code, display original body * TOML Test * TOML * Delete test.txt * Update Post 1.7 Version * Test no error qb task * Test * cleanup updateqb.yml * Formatting * Update Changelogs (after rebase to TOML syntax) * Fix Deploy GH Task * Remove uneeded code * Allow for fetching/making changelog in build task * Fix Changelog Fetching * Change Changelog Output Dir to build root dir * Add new features to Github Release Task * Combine Version & GitHub_TAG * Create GHA files, improve TS code (#446) * Setup Action * test * test again * Update releasecommit.yml * Move setup.yml * Update releasecommit.yml * Update releasecommit.yml * Update setup.yml * Update releasecommit.yml * Update deploygh.yml * Delete .github/workflows/setup.yml * Create build-pack.yml * Update build-pack.yml * test * Update build-pack.yml * more test * Stupid me using download job for upload * more testing... * Update build-pack.yml * Update build-pack.yml * Update build-pack.yml * Update build-pack.yml * Create build-pack-manual.yml * Move file * Revert "Move file" This reverts commit 506a523aed2d5d8af21094b943aa2b7e2b7ee688. * Update build-pack-manual.yml * Update build-pack-manual.yml * Create deploy.yml * Delete .github/workflows/deploygh.yml * Rename deploy.yml to deploygh.yml * Update deploygh.yml * Create deploy.yml * Update deploy.yml * Update deploy.yml * Update deploy.yml * Update deploygh.yml * Remove debug logging * Update build-pack.yml * Delete .github/workflows/build-pack-manual.yml * Update deploygh.yml * Update build-pack.yml * Update deploy.yml * Update deploygh.yml * Update deploygh.yml * Update build-pack.yml * Update build-pack.yml * Update deploy.yml * Update build-pack.yml * Update deploygh.yml * Update deploy.yml * Update releasecommit.yml * Release 5.0 * Update releasecommit.yml * Release 6.0 * Update Issue, Server and RP Config Files from Templates * Update releasecommit.yml * Release 7.0 * Add release title in changelog, remove more code * Add link to full changelog, format release name * Release 7.1 * Update deploy.yml * Alpha Release 7.2 * Fix tags excaping, improve full changelog * Release 7.3 * Add to CF Release, Cleanup Code * Delete .github/workflows/deploycfrc.yml * Update deploycf.yml * Update deploy.yml * Update gulpfile * Alpha Release 8.0-alpha-1 * Update deploycf.yml * Update deploycf.yml * Fix Curseforge Changelog * Beta Release 8.0-beta-2 * Update releasecommit.yml * Rename build-pack.yml to buildpack.yml * Update deploy.yml * Add CF redirect * Fix existing version detection * Create releasedeploy.yml * Update releasedeploy.yml * Update releasedeploy.yml * Release 8.0 * Update releasedeploy.yml * Create test.yml * Delete .github/workflows/test.yml * Create makechangelog.yml * Update makechangelog.yml * Update and rename makechangelog.yml to createchangelog.yml * Rename: MakeChangelog -> CreateChangelog * Update createchangelog.yml * Create releasechangelog.yml * Update releasechangelog.yml * Update createchangelog.yml * Beta Release 9.0-beta-1 * Release 9.0 * Release 9.1 * Update releasechangelog.yml * Alpha Release 9.1-alpha-1 * Remove extra versions * Fix more changed version numbers * Update Issue, Server and RP Config Files from Templates * Update versions.txt * Update Issue, Server and RP Config Files from Templates * Update Post 1.7 Version * Start writing more in CONTRIBUTING * Add basic Table Of Contents structure, qb contributing * test * Fix wrong html tag * Final Formatting * Script contributing information + cleanup * More Contribution Information * Begin Maintainer Information * Improve documentation, make submessages not include author/sha * Finish Changelog Workflow Documentation * Create structure * Write section for create release commit workflow * Provide a overview table of all keys * Documentation on Deploy Task * Artifacts information + start build pack documentation * test new way to center elements * Allow for Cutting Edge Build in build task * Actually use release_type input in build task * Finish Contributing information * Remove changelog files * JS documentation * MMC zips + allow for build pack task to upload seperately * test * test selective artifact method, allow for selectively deploying * Finish Buildscript (#457) * mention contributing stuff in wiki * Transform PR Tags in Message into links
436 lines
13 KiB
TypeScript
436 lines
13 KiB
TypeScript
import sha1 from "sha1";
|
|
import { FileDef } from "../types/fileDef";
|
|
import fs from "fs";
|
|
import buildConfig from "../buildConfig";
|
|
import upath from "upath";
|
|
import requestretry from "requestretry";
|
|
import request from "requestretry";
|
|
import http from "http";
|
|
import { compareBufferToHashDef } from "./hashes";
|
|
import { execSync } from "child_process";
|
|
import { ExternalDependency, ModpackManifest, ModpackManifestFile } from "../types/modpackManifest";
|
|
import { fetchFileInfo, fetchProject, fetchProjectsBulk } from "./curseForgeAPI";
|
|
import Bluebird from "bluebird";
|
|
import { VersionManifest } from "../types/versionManifest";
|
|
import { VersionsManifest } from "../types/versionsManifest";
|
|
import log from "fancy-log";
|
|
import { pathspec, SimpleGit, simpleGit } from "simple-git";
|
|
import { Commit, ModChangeInfo } from "../types/changelogTypes";
|
|
import { rootDirectory } from "../globals";
|
|
|
|
const LIBRARY_REG = /^(.+?):(.+?):(.+?)$/;
|
|
|
|
// Make git commands run in root dir
|
|
const git: SimpleGit = simpleGit(rootDirectory);
|
|
|
|
/**
|
|
* Parses the library name into path following the standard package naming convention.
|
|
*
|
|
* Turns `package:name:version` into `package/name/version/name-version`.
|
|
*/
|
|
export const libraryToPath = (library: string): string => {
|
|
const parsedLibrary = LIBRARY_REG.exec(library);
|
|
if (parsedLibrary) {
|
|
const pkg = parsedLibrary[1].replace(/\./g, "/");
|
|
const name = parsedLibrary[2];
|
|
const version = parsedLibrary[3];
|
|
|
|
return `${pkg}/${name}/${version}/${name}-${version}`;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Checks if given environmental variables are set. Throws otherwise.
|
|
*/
|
|
export const checkEnvironmentalVariables = (vars: string[]): void => {
|
|
vars.forEach((vari) => {
|
|
if (!isEnvVariableSet(vari)) {
|
|
throw new Error(`Environmental variable ${vari} is unset.`);
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Returns true if given variable set, false otherwise.
|
|
*/
|
|
export const isEnvVariableSet = (env: string): boolean => {
|
|
return process.env[env] && process.env[env] != "";
|
|
};
|
|
|
|
/**
|
|
* Check if given git tag exists. Throws otherwise.
|
|
*/
|
|
export const checkGitTag = (tag: string): void => {
|
|
// The below command returns an empty buffer if the given tag does not exist.
|
|
const tagBuffer = execSync(`git tag --list ${tag}`);
|
|
|
|
if (!tagBuffer || tagBuffer.toString().trim() != tag) {
|
|
throw new Error(`Tag ${tag} could not be found.`);
|
|
}
|
|
};
|
|
|
|
export enum RetrievedFileDefReason {
|
|
Downloaded,
|
|
CacheHit,
|
|
}
|
|
|
|
export interface RetrievedFileDef {
|
|
reason: RetrievedFileDefReason;
|
|
cachePath: string;
|
|
}
|
|
|
|
/**
|
|
* Downloads/fetches files from the Interwebs.
|
|
*
|
|
* Internally hashes the URL of the provided FileDef and looks it up in the cache directory.
|
|
* In case of no cache hit, downloads the file and stores within the cache directory for later use.
|
|
* <p>
|
|
* @param fileDef The file def to download or retrieve.
|
|
*/
|
|
export async function downloadOrRetrieveFileDef(fileDef: FileDef): Promise<RetrievedFileDef> {
|
|
const fileNameSha = sha1(fileDef.url);
|
|
|
|
const cachedFilePath = upath.join(buildConfig.downloaderCacheDirectory, fileNameSha);
|
|
if (fs.existsSync(cachedFilePath)) {
|
|
const file = await fs.promises.readFile(cachedFilePath);
|
|
|
|
if (file.length !== 0) {
|
|
const rFileDef = {
|
|
reason: RetrievedFileDefReason.CacheHit,
|
|
cachePath: cachedFilePath,
|
|
};
|
|
|
|
// Check hashes.
|
|
if (fileDef.hashes) {
|
|
if (
|
|
fileDef.hashes.every((hashDef) => {
|
|
return compareBufferToHashDef(file, hashDef);
|
|
})
|
|
) {
|
|
return rFileDef;
|
|
}
|
|
} else {
|
|
return rFileDef;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fs.existsSync(buildConfig.downloaderCacheDirectory)) {
|
|
await fs.promises.mkdir(buildConfig.downloaderCacheDirectory, { recursive: true });
|
|
}
|
|
|
|
let handle: fs.promises.FileHandle;
|
|
try {
|
|
handle = await fs.promises.open(cachedFilePath, "w");
|
|
|
|
await handle.write(await downloadFileDef(fileDef));
|
|
await handle.close();
|
|
|
|
return {
|
|
reason: RetrievedFileDefReason.Downloaded,
|
|
cachePath: cachedFilePath,
|
|
};
|
|
} catch (err) {
|
|
if (handle && (await handle.stat()).isFile()) {
|
|
log(`Couldn't download ${upath.basename(fileDef.url)}, cleaning up ${fileNameSha}...`);
|
|
|
|
await handle.close();
|
|
await fs.promises.unlink(cachedFilePath);
|
|
}
|
|
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Similar to downloadOrRetrieveFileDef, but does not check cache.
|
|
*/
|
|
export async function downloadFileDef(fileDef: FileDef): Promise<Buffer> {
|
|
let hashFailed = false;
|
|
const retryStrategy = (err: Error, response: http.IncomingMessage, body: unknown) => {
|
|
if (response.statusCode === 404) {
|
|
throw new Error(`URL ${fileDef.url} returned status 404.`);
|
|
}
|
|
// Verify hashes.
|
|
if (!err && fileDef.hashes && body) {
|
|
const success = fileDef.hashes.every((hashDef) => {
|
|
return compareBufferToHashDef(body as Buffer, hashDef);
|
|
});
|
|
|
|
if (!success) {
|
|
if (hashFailed) {
|
|
throw new Error(`Couldn't verify checksums of ${upath.basename(fileDef.url)}`);
|
|
}
|
|
|
|
hashFailed = true;
|
|
return true;
|
|
}
|
|
}
|
|
return requestretry.RetryStrategies.HTTPOrNetworkError(err, response, body);
|
|
};
|
|
|
|
return Buffer.from(
|
|
await requestretry({
|
|
url: fileDef.url,
|
|
fullResponse: false,
|
|
encoding: null,
|
|
retryStrategy: retryStrategy,
|
|
maxAttempts: 5,
|
|
}),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns artifact name body depending on environment variables.
|
|
* Mostly intended to be called by CI/CD.
|
|
*/
|
|
export function makeArtifactNameBody(baseName: string): string {
|
|
// If the tag is provided by CI, simply just glue it to the base name.
|
|
if (process.env.GITHUB_TAG) {
|
|
return `${baseName}-${process.env.GITHUB_TAG}`;
|
|
}
|
|
// If SHA is provided and the build isn't tagged, append both the branch and short SHA.
|
|
else if (process.env.GITHUB_SHA && process.env.GITHUB_REF && process.env.GITHUB_REF.startsWith("refs/heads/")) {
|
|
const shortCommit = process.env.GITHUB_SHA.substring(0, 7);
|
|
const branch = /refs\/heads\/(.+)/.exec(process.env.GITHUB_REF);
|
|
return `${baseName}-${branch[1]}-${shortCommit}`;
|
|
} else {
|
|
return baseName;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the COMPARE_TAG env if set, else fetches the last tag known to Git using the current branch.
|
|
* @param before Tag to get the tag before.
|
|
* @returns string Git tag.
|
|
* @throws
|
|
*/
|
|
export function getLastGitTag(before?: string): string {
|
|
if (isEnvVariableSet("COMPARE_TAG")) {
|
|
checkGitTag(process.env["COMPARE_TAG"]);
|
|
|
|
return process.env["COMPARE_TAG"];
|
|
}
|
|
|
|
if (before) {
|
|
before = `"${before}^"`;
|
|
}
|
|
|
|
return execSync(`git describe --abbrev=0 --tags ${before || ""}`)
|
|
.toString()
|
|
.trim();
|
|
}
|
|
|
|
/**
|
|
* Generates a changelog based on the two provided Git refs.
|
|
* @param since Lower boundary Git ref.
|
|
* @param to Upper boundary Git ref.
|
|
* @param dirs Optional scopes. These are of the perspective of the root dir.
|
|
* @returns changelog Object Array of Changelog
|
|
*/
|
|
export async function getChangelog(since = "HEAD", to = "HEAD", dirs: string[] = undefined): Promise<Commit[]> {
|
|
const options: string[] = ["--no-merges", `${since}..${to}`];
|
|
if (dirs) {
|
|
dirs.forEach((dir) => {
|
|
options.push(pathspec(dir));
|
|
});
|
|
}
|
|
|
|
const commitList: Commit[] = [];
|
|
await git.log(options, (err, output) => {
|
|
if (err) {
|
|
console.error(err);
|
|
throw new Error();
|
|
}
|
|
|
|
// Cannot simply set commitList as output.all as is read only, must do this
|
|
output.all.forEach((commit) => commitList.push(commit));
|
|
});
|
|
|
|
return commitList;
|
|
}
|
|
|
|
/**
|
|
* Gets the file at a certain point in time.
|
|
* @param path The path to the file
|
|
* @param revision The git ref point. Can also be a commit SHA
|
|
*/
|
|
export function getFileAtRevision(path: string, revision = "HEAD"): string {
|
|
return execSync(`git show ${revision}:"${path}"`).toString().trim();
|
|
}
|
|
|
|
export interface ManifestFileListComparisonResult {
|
|
removed: ModChangeInfo[];
|
|
modified: ModChangeInfo[];
|
|
added: ModChangeInfo[];
|
|
}
|
|
|
|
export async function compareAndExpandManifestDependencies(
|
|
oldFiles: ModpackManifest,
|
|
newFiles: ModpackManifest,
|
|
): Promise<ManifestFileListComparisonResult> {
|
|
// Map inputs for efficient joining.
|
|
const oldFileMap: { [key: number]: ModpackManifestFile } = oldFiles.files.reduce((map, file) => {
|
|
map[file.projectID] = file;
|
|
return map;
|
|
}, {});
|
|
const newFileMap: { [key: number]: ModpackManifestFile } = newFiles.files.reduce((map, file) => {
|
|
map[file.projectID] = file;
|
|
return map;
|
|
}, {});
|
|
|
|
const removed: ModChangeInfo[] = [],
|
|
modified: ModChangeInfo[] = [],
|
|
added: ModChangeInfo[] = [];
|
|
|
|
// Create a distinct map of project IDs.
|
|
const projectIDs = Array.from(
|
|
new Set([...oldFiles.files.map((f) => f.projectID), ...newFiles.files.map((f) => f.projectID)]),
|
|
);
|
|
|
|
// Fetch projects in bulk and discard the result.
|
|
// Future calls to fetchProject() and fetchProjectsBulk() will hit the cache.
|
|
await fetchProjectsBulk(projectIDs);
|
|
|
|
await Bluebird.map(
|
|
projectIDs,
|
|
async (projectID) => {
|
|
const oldFileInfo = oldFileMap[projectID];
|
|
const newFileInfo = newFileMap[projectID];
|
|
|
|
// Doesn't exist in new, but exists in old. Removed. Left outer join.
|
|
if (!newFileInfo && oldFileInfo) {
|
|
removed.push({
|
|
modName: (await fetchProject(oldFileInfo.projectID)).name,
|
|
projectID: projectID,
|
|
oldVersion: (await fetchFileInfo(oldFileInfo.projectID, oldFileInfo.fileID)).displayName,
|
|
});
|
|
}
|
|
// Doesn't exist in old, but exists in new. Added. Right outer join.
|
|
else if (newFileMap[projectID] && !oldFileMap[projectID]) {
|
|
added.push({
|
|
modName: (await fetchProject(newFileInfo.projectID)).name,
|
|
projectID: projectID,
|
|
newVersion: (await fetchFileInfo(newFileInfo.projectID, newFileInfo.fileID)).displayName,
|
|
});
|
|
}
|
|
// Exists in both. Modified? Inner join.
|
|
else if (oldFileInfo.fileID != newFileInfo.fileID) {
|
|
modified.push({
|
|
modName: (await fetchProject(newFileInfo.projectID)).name,
|
|
projectID: projectID,
|
|
oldVersion: (await fetchFileInfo(newFileInfo.projectID, oldFileInfo.fileID)).displayName,
|
|
newVersion: (await fetchFileInfo(newFileInfo.projectID, newFileInfo.fileID)).displayName,
|
|
});
|
|
}
|
|
},
|
|
{ concurrency: buildConfig.downloaderConcurrency },
|
|
);
|
|
|
|
// Compare external dependencies the same way.
|
|
const oldExternalMap: { [key: string]: ExternalDependency } = (oldFiles.externalDependencies || []).reduce(
|
|
(map, file) => {
|
|
map[file.name] = file;
|
|
return map;
|
|
},
|
|
{},
|
|
);
|
|
const newExternalMap: { [key: string]: ExternalDependency } = (newFiles.externalDependencies || []).reduce(
|
|
(map, file) => {
|
|
map[file.name] = file;
|
|
return map;
|
|
},
|
|
{},
|
|
);
|
|
|
|
const externalNames = Array.from(
|
|
new Set([
|
|
...(oldFiles.externalDependencies || []).map((dep) => dep.name),
|
|
...(newFiles.externalDependencies || []).map((dep) => dep.name),
|
|
]),
|
|
);
|
|
|
|
externalNames.forEach((name) => {
|
|
const oldDep = oldExternalMap[name];
|
|
const newDep = newExternalMap[name];
|
|
|
|
// Doesn't exist in new, but exists in old. Removed. Left outer join.
|
|
if (!newDep && oldDep) {
|
|
removed.push({ modName: oldDep.name });
|
|
}
|
|
// Doesn't exist in old, but exists in new. Added. Right outer join.
|
|
else if (newDep && !oldDep) {
|
|
added.push({ modName: newDep.name });
|
|
}
|
|
// Exists in both. Modified? Inner join.
|
|
else if (oldDep.url != newDep.url || oldDep.name != newDep.name) {
|
|
modified.push({ modName: newDep.name });
|
|
}
|
|
});
|
|
|
|
return {
|
|
removed: removed,
|
|
modified: modified,
|
|
added: added,
|
|
};
|
|
}
|
|
|
|
const LAUNCHERMETA_VERSION_MANIFEST = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
|
|
|
|
/**
|
|
* Fetches the version manifest associated with the provided Minecraft version.
|
|
*
|
|
* @param minecraftVersion Minecraft version. (e. g., "1.12.2")
|
|
*/
|
|
export async function getVersionManifest(minecraftVersion: string): Promise<VersionManifest> {
|
|
/**
|
|
* Fetch the manifest file of all Minecraft versions.
|
|
*/
|
|
const manifest: VersionsManifest = await request({
|
|
uri: LAUNCHERMETA_VERSION_MANIFEST,
|
|
json: true,
|
|
fullResponse: false,
|
|
maxAttempts: 5,
|
|
});
|
|
|
|
const version = manifest.versions.find((x) => x.id == minecraftVersion);
|
|
if (!version) {
|
|
return null;
|
|
}
|
|
|
|
return request({
|
|
uri: version.url,
|
|
json: true,
|
|
fullResponse: false,
|
|
maxAttempts: 5,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns a relative posix path from the first argument to the second.
|
|
*/
|
|
export function relative(from: string, to: string): string {
|
|
const broken = [from.split(upath.sep), to.split(upath.sep)];
|
|
|
|
while (broken.every((x) => x.length > 0) && broken[0][0] == broken[1][0]) {
|
|
broken.forEach((x) => x.shift());
|
|
}
|
|
|
|
if (broken.some((x) => x.length === 0)) {
|
|
throw new Error("Paths are not relative.");
|
|
}
|
|
|
|
return upath.join(...Array(broken[0].length - 1).fill(".."), ...broken[1]);
|
|
}
|
|
|
|
/**
|
|
* Cleans up a file's display name, and returns the version. Works for all tested mods!
|
|
* @param version The filename/version to cleanup.
|
|
*/
|
|
export function cleanupVersion(version: string): string {
|
|
if (!version) return "";
|
|
version = version.replace(/1\.12\.2|1\.12|\.jar/g, "");
|
|
const list = version.match(/[\d+.?]+/g);
|
|
return list[list.length - 1];
|
|
}
|