Allow Adding Info to Mod Changes & Refactor (#527)

Allows adding info/details to mod changes section of the changelog.

Because this uses numbers, the TOML parser has been changed. The old parser produced weird artifacts when processing numbers.

Refactor:
For most tags (except for ignore), allows for optional lists.

[MOD INFO]
[[infos]]
projectID = 620262
info = "Replaced by Alfheim Lighting Engine"

[[infos]]
projectID = 230976
info = "Replaced by Universal Tweaks"

[[infos]]
projectID = 271492
info = "Replaced by Universal Tweaks"

[[infos]]
projectID = 271740 
info = "Replaced by Universal Tweaks"

[[infos]]
projectID = 278385
info = "Replaced by Universal Tweaks"

[[infos]]
projectID = 278494
info = "Replaced by Vintage Fix"

[[infos]]
projectID = 280510
info = "Replaced by Universal Tweaks"

[[infos]]
projectID = 284904
info = "Replaced by Universal Tweaks"

[[infos]]
projectID = 288885
info = "Replaced by Universal Tweaks"
[MOD INFO]
This commit is contained in:
Integer Limit 2023-11-18 16:07:42 +11:00 committed by GitHub
parent 15ff662307
commit 65bd92d9fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 353 additions and 90 deletions

1
tools/.gitignore vendored
View File

@ -1 +1,2 @@
node_modules
.idea/

View File

@ -10,7 +10,8 @@
"license": "LGPL-3.0",
"dependencies": {
"@egjs/list-differ": "^1.0.1",
"@ltd/j-toml": "^1.38.0",
"@iarna/toml": "^2.2.5",
"@types/iarna__toml": "^2.0.5",
"@types/json-stable-stringify-without-jsonify": "^1.0.1",
"dedent-js": "^1.0.1",
"json-stable-stringify-without-jsonify": "^1.0.1"
@ -229,6 +230,11 @@
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true
},
"node_modules/@iarna/toml": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
@ -269,11 +275,6 @@
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
"dev": true
},
"node_modules/@ltd/j-toml": {
"version": "1.38.0",
"resolved": "https://registry.npmjs.org/@ltd/j-toml/-/j-toml-1.38.0.tgz",
"integrity": "sha512-lYtBcmvHustHQtg4X7TXUu1Xa/tbLC3p2wLvgQI+fWVySguVZJF60Snxijw5EiohumxZbR10kWYFFebh1zotiw=="
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -563,6 +564,14 @@
"@types/node": "*"
}
},
"node_modules/@types/iarna__toml": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/iarna__toml/-/iarna__toml-2.0.5.tgz",
"integrity": "sha512-I55y+SxI0ayM4MBU6yfGJGmi4wRll6wtSeKiFYAZj+Z5Q1DVbMgBSVDYY+xQZbjIlLs/pN4fidnvR8faDrmxPg==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/imagemin": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@types/imagemin/-/imagemin-8.0.0.tgz",
@ -650,8 +659,7 @@
"node_modules/@types/node": {
"version": "18.11.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
"dev": true
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg=="
},
"node_modules/@types/q": {
"version": "1.5.5",
@ -11946,6 +11954,11 @@
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true
},
"@iarna/toml": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="
},
"@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
@ -11983,11 +11996,6 @@
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
"dev": true
},
"@ltd/j-toml": {
"version": "1.38.0",
"resolved": "https://registry.npmjs.org/@ltd/j-toml/-/j-toml-1.38.0.tgz",
"integrity": "sha512-lYtBcmvHustHQtg4X7TXUu1Xa/tbLC3p2wLvgQI+fWVySguVZJF60Snxijw5EiohumxZbR10kWYFFebh1zotiw=="
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -12257,6 +12265,14 @@
"@types/node": "*"
}
},
"@types/iarna__toml": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/iarna__toml/-/iarna__toml-2.0.5.tgz",
"integrity": "sha512-I55y+SxI0ayM4MBU6yfGJGmi4wRll6wtSeKiFYAZj+Z5Q1DVbMgBSVDYY+xQZbjIlLs/pN4fidnvR8faDrmxPg==",
"requires": {
"@types/node": "*"
}
},
"@types/imagemin": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@types/imagemin/-/imagemin-8.0.0.tgz",
@ -12344,8 +12360,7 @@
"@types/node": {
"version": "18.11.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
"dev": true
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg=="
},
"@types/q": {
"version": "1.5.5",

View File

@ -51,7 +51,8 @@
},
"dependencies": {
"@egjs/list-differ": "^1.0.1",
"@ltd/j-toml": "^1.38.0",
"@iarna/toml": "^2.2.5",
"@types/iarna__toml": "^2.0.5",
"@types/json-stable-stringify-without-jsonify": "^1.0.1",
"dedent-js": "^1.0.1",
"json-stable-stringify-without-jsonify": "^1.0.1"

View File

@ -1,4 +1,4 @@
import { Commit, FixUpInfo, InputReleaseType } from "../../types/changelogTypes";
import { Commit, FixUpInfo, InputReleaseType, ParsedModInfo } from "../../types/changelogTypes";
import { getLastGitTag, getTags, isEnvVariableSet } from "../../util/util";
export default class ChangelogData {
@ -19,6 +19,9 @@ export default class ChangelogData {
// Set of tags
tags: Set<string>;
// Map of project IDs to info text and/or details
modInfoList: Map<number, ParsedModInfo>;
/**
* A normal initialisation.
*/
@ -56,6 +59,8 @@ export default class ChangelogData {
this.combineList = new Map<string, Commit[]>();
this.tags = new Set<string>(await getTags(this.to));
this.modInfoList = new Map<number, ParsedModInfo>();
}
shouldIterate(): boolean {
@ -89,6 +94,8 @@ export default class ChangelogData {
this.shaList = new Set<string>();
this.combineList = new Map<string, Commit[]>();
this.modInfoList = new Map<number, ParsedModInfo>();
// Tags list is fine because the `to` stays the same
}
}

View File

@ -11,7 +11,6 @@ import { specialParserSetup } from "./specialParser";
import generateModChanges from "./generateModChanges";
import pushAll, { pushChangelog, pushSeperator, pushTitle } from "./pusher";
import log from "fancy-log";
import * as util from "util";
/**
* Generates a changelog based on environmental variables, and saves it a changelog data class.

View File

@ -21,12 +21,16 @@ export const expandKey = "[EXPAND]";
export const expandList = "messages";
export const detailsKey = "[DETAILS]";
export const detailsList = "details";
export const detailsRoot = "detail";
export const noCategoryKey = "[NO CATEGORY]";
export const combineKey = "[COMBINE]";
export const combineList = "commits";
export const combineRoot = "commit";
export const fixUpKey = "[FIXUP]";
export const fixUpList = "fixes";
export const ignoreKey = "[IGNORE]";
export const modInfoKey = "[MOD INFO]";
export const modInfoList = "infos";
/* Sub Category Keys */
// Mode Category Keys
@ -201,21 +205,22 @@ export interface ModChangesAllocation {
template: string;
}
// These templates must be triple bracketed, because we don't want these to be html safe
export const modChangesAllocations: Record<ModChangesType, ModChangesAllocation> = {
added: {
category: generalCategory,
subCategory: modAdditions,
template: "{{ modName }}: *v{{ newVersion }}*",
template: "{{{ modName }}}: *v{{{ newVersion }}}*",
},
updated: {
category: generalCategory,
subCategory: modUpdates,
template: "{{ modName }}: *v{{ oldVersion }} ⇥ v{{ newVersion }}*",
template: "{{{ modName }}}: *v{{{ oldVersion }}} ⇥ v{{{ newVersion }}}*",
},
removed: {
category: generalCategory,
subCategory: modRemovals,
template: "{{ modName }}: *v{{ oldVersion }}*",
template: "{{{ modName }}}: *v{{{ oldVersion }}}*",
},
};

View File

@ -1,10 +1,10 @@
import { cleanupVersion, compareAndExpandManifestDependencies, getChangelog, getFileAtRevision } from "../../util/util";
import { ModpackManifest, ModpackManifestFile } from "../../types/modpackManifest";
import { Commit, ModChangeInfo } from "../../types/changelogTypes";
import { ChangelogMessage, Commit, ModChangeInfo } from "../../types/changelogTypes";
import ListDiffer, { DiffResult } from "@egjs/list-differ";
import dedent from "dedent-js";
import mustache from "mustache";
import { defaultIndentation, modChangesAllocations, repoLink } from "./definitions";
import { modChangesAllocations, repoLink } from "./definitions";
import ChangelogData from "./changelogData";
import { SpecialChangelogFormatting } from "../../types/changelogTypes";
import { sortCommitListReverse } from "./pusher";
@ -15,9 +15,11 @@ import { error } from "fancy-log";
*/
const getModChangesFormatting: (commits: Commit[]) => SpecialChangelogFormatting<Commit[]> = (commits) => {
return {
formatting: (changelogMessage, commits) => {
const indentation = changelogMessage.indentation == undefined ? defaultIndentation : changelogMessage.indentation;
const message = changelogMessage.commitMessage.trim();
formatting: (message, subMessage, indentation, commits) => {
// Sub messages are details, so make them bold & italic
if (subMessage) {
return `${indentation}* ***${message}***`;
}
if (commits.length > 1) {
const authors: string[] = [];
const formattedCommits: string[] = [];
@ -89,32 +91,60 @@ export default async function generateModChanges(data: ChangelogData): Promise<v
// Sort array so newest commits appear at end instead of start of commit string
sortCommitListReverse(commits);
}
block.allocation.category.changelogSection.get(block.allocation.subCategory).push({
commitMessage: getModChangeMessage(info, block.allocation.template),
specialFormatting: getModChangesFormatting(commits),
});
block.allocation.category.changelogSection
.get(block.allocation.subCategory)
.push(getModChangeMessage(info, block.allocation.template, data, commits));
});
});
}
/**
* Returns the message, determined by the parameters below.
* Returns the changelog message, determined by the parameters below.
* @param info The mod change info, containing the mod name and versions.
* @param template The message template to replace in.
* @param data The changelog data
* @param commits The commits
*/
function getModChangeMessage(info: ModChangeInfo, template: string): string {
function getModChangeMessage(
info: ModChangeInfo,
template: string,
data: ChangelogData,
commits: Commit[],
): ChangelogMessage {
const oldVersion = cleanupVersion(info.oldVersion);
const newVersion = cleanupVersion(info.newVersion);
// If not provided with either version, return just mod name
if (!oldVersion && !newVersion) return info.modName;
if (!oldVersion && !newVersion)
return {
commitMessage: info.modName,
specialFormatting: getModChangesFormatting(commits),
};
// Replace in template
return mustache.render(template, {
let text = mustache.render(template, {
modName: info.modName,
oldVersion: oldVersion,
newVersion: newVersion,
});
// Parse Info
let subMessages: ChangelogMessage[] = undefined;
if (data.modInfoList.has(info.projectID)) {
const modInfo = data.modInfoList.get(info.projectID);
if (modInfo.info) text = `${text} ***(${modInfo.info})***`;
if (modInfo.details)
subMessages = modInfo.details.map((detail) => {
detail.specialFormatting = getModChangesFormatting(commits);
return detail;
});
}
return {
commitMessage: text,
specialFormatting: getModChangesFormatting(commits),
subChangelogMessages: subMessages,
};
}
/**

View File

@ -6,9 +6,10 @@ import {
detailsKey,
expandKey,
ignoreKey,
modInfoKey,
noCategoryKey,
} from "./definitions";
import { parseCombine, parseDetails, parseExpand, parseIgnore } from "./specialParser";
import { parseCombine, parseDetails, parseExpand, parseIgnore, parseModInfo } from "./specialParser";
import { getChangelog } from "../../util/util";
import ChangelogData from "./changelogData";
@ -68,6 +69,7 @@ export async function parseCommitBody(
// Only return if ignore is not undefined
if (ignore) return ignore;
}
if (commitBody.includes(modInfoKey)) await parseModInfo(commitBody, commitObject);
if (commitBody.includes(detailsKey)) {
await parseDetails(commitMessage, commitBody, commitObject, parser);
return true;

View File

@ -174,15 +174,20 @@ export function sortCommitListReverse(list: Commit[]): void {
* @return string Formatted Changelog Message
*/
async function formatChangelogMessage(changelogMessage: ChangelogMessage, subMessage = false): Promise<string> {
if (changelogMessage.specialFormatting)
return changelogMessage.specialFormatting.formatting(changelogMessage, changelogMessage.specialFormatting.storage);
const indentation = changelogMessage.indentation == undefined ? defaultIndentation : changelogMessage.indentation;
let message = changelogMessage.commitMessage.trim();
// Transform PR and/or Issue tags into a link.
message = await transformTags(message);
if (changelogMessage.specialFormatting)
return changelogMessage.specialFormatting.formatting(
message,
subMessage,
indentation,
changelogMessage.specialFormatting.storage,
);
if (changelogMessage.commitObject && !subMessage) {
if (data.combineList.has(changelogMessage.commitObject.hash)) {
const commits = data.combineList.get(changelogMessage.commitObject.hash);

View File

@ -6,17 +6,21 @@ import {
Ignored,
IgnoreInfo,
IgnoreLogic,
ModInfo,
ParsedModInfo,
Parser,
} from "../../types/changelogTypes";
import dedent from "dedent-js";
import matter, { GrayMatterFile } from "gray-matter";
import toml from "@ltd/j-toml";
import toml from "@iarna/toml";
import {
combineKey,
combineList,
combineRoot,
defaultIgnoreLogic,
detailsKey,
detailsList,
detailsRoot,
expandKey,
expandList,
fixUpKey,
@ -25,6 +29,8 @@ import {
ignoreKey,
ignoreLogics,
indentationLevel,
modInfoKey,
modInfoList,
} from "./definitions";
import { findCategories, findSubCategory } from "./parser";
import ChangelogData from "./changelogData";
@ -124,16 +130,17 @@ export async function parseIgnore(commitBody: string, commitObject: Commit): Pro
*/
export async function parseFixUp(commit: Commit): Promise<boolean> {
if (!commit.body || !commit.body.includes(fixUpKey)) return false;
await parseTOMLToList(
await parseTOMLWithRootToList<FixUpInfo>(
commit.body,
commit,
fixUpKey,
fixUpList,
(item: FixUpInfo) => item.sha && item.newTitle,
(item) => {
(item) => !item.sha || !item.newTitle,
async (item) => {
// Only override if no other overrides, from newer commits, set
if (!data.commitFixes.has(item.sha)) data.commitFixes.set(item.sha, item);
},
(item) => item as unknown as FixUpInfo,
(matter) => {
// Must override, even if newer commits specified changes, as need to remove fixup data
data.commitFixes.set(commit.hash, {
@ -147,16 +154,58 @@ export async function parseFixUp(commit: Commit): Promise<boolean> {
return true;
}
/**
* Parses a commit with 'mod info'.
*/
export async function parseModInfo(commitBody: string, commitObject: Commit): Promise<void> {
await parseTOMLWithRootToList<ModInfo>(
commitBody,
commitObject,
modInfoKey,
modInfoList,
(item): boolean => {
const invalidProjectID = !item.projectID || typeof item.projectID !== "number" || Number.isNaN(item.projectID);
const invalidInfo = !item.info;
const invalidRootDetails = !item.detail;
const invalidDetails = !item.details || !Array.isArray(item.details) || !(item.details.length > 0);
// Invalid if invalid ID, or invalid info and invalid details
return invalidProjectID || (invalidInfo && invalidRootDetails && invalidDetails);
},
async (item) => {
data.modInfoList.set(item.projectID, await getParsedModInfo(item));
},
);
}
/**
* Gets the parsed mod info of a mod info.
*/
async function getParsedModInfo(modInfo: ModInfo): Promise<ParsedModInfo> {
const subMessages: ChangelogMessage[] = [];
if (modInfo.detail) subMessages.push({ commitMessage: modInfo.detail, indentation: indentationLevel });
if (modInfo.details && modInfo.details.length > 0)
subMessages.push(
...modInfo.details.map((detail) => {
return { commitMessage: detail, indentation: indentationLevel };
}),
);
return {
info: modInfo.info,
details: subMessages,
};
}
/**
* Parses a commit with 'expand'.
*/
export async function parseExpand(commitBody: string, commitObject: Commit, parser: Parser): Promise<void> {
await parseTOMLToList(
await parseTOMLWithRootToList<ExpandedMessage>(
commitBody,
commitObject,
expandKey,
expandList,
(item: ExpandedMessage) => item.messageTitle,
(item) => !item.messageTitle,
async (item) => {
const title = dedent(item.messageTitle);
@ -214,12 +263,12 @@ async function expandDetailsLevel(
indentation = indentationLevel,
): Promise<ChangelogMessage[]> {
const result: ChangelogMessage[] = [];
await parseTOMLToList(
await parseTOMLWithRootToList<string>(
commitBody,
commitObject,
detailsKey,
detailsList,
(item: string) => item,
(item) => !item,
async (item) => {
item = dedent(item).trim();
if (item.includes(detailsKey)) {
@ -228,6 +277,7 @@ async function expandDetailsLevel(
result.push({ commitMessage: item, commitObject: commitObject, indentation: indentation });
}
},
(root) => root[detailsRoot] as string,
);
return result;
}
@ -236,16 +286,17 @@ async function expandDetailsLevel(
* Parses a commit with 'combine'.
*/
export async function parseCombine(commitBody: string, commitObject: Commit): Promise<void> {
await parseTOMLToList(
await parseTOMLWithRootToList<string>(
commitBody,
commitObject,
combineKey,
combineList,
(item: string) => item,
(item: string) => {
(item) => !item,
async (item) => {
if (!data.combineList.has(item)) data.combineList.set(item, []);
data.combineList.get(item).push(commitObject);
},
(root) => root[combineRoot] as string,
);
}
@ -280,7 +331,7 @@ async function parseTOML<T>(
delimiters: delimiter,
engines: {
toml: (input): Record<string, unknown> => {
return toml.parse(input, "\n");
return toml.parse(input);
},
},
language: "toml",
@ -314,50 +365,34 @@ async function parseTOML<T>(
}
/**
* Parse TOML in a commit body to produce a list.
* @param commitBody The body to parse
* @param commitObject The commit object to grab messages from, and to determine error messages.
* @param delimiter The delimiters, surrounding the TOML.
* @param listKey The key of the list to parse.
* @param emptyCheck The check to see if an item in the list is invalid.
* @param perItemCallback The callback to perform on each item in the list.
* @param matterCallback An optional callback to perform on the matter.
* Parses a List with safety.
* @param messages List to parse
* @param listKey List Key, used for error messages
* @param commitBody The commit body
* @param commitObject The commit object, used for error messages.
* @param endMessage The end message to use
* @param emptyCheck Check to see if an item is valid. True -> Empty
* @param perItemCallback Callback to perform per item. Async to allow for further toml parsing
* @param entryModifier Callback to change the entry in error messages. Optional. Defaults to `i + 1`.
*/
async function parseTOMLToList<T>(
async function parseList<T>(
messages: T[],
listKey: string,
commitBody: string,
commitObject: Commit,
delimiter: string,
listKey: string,
emptyCheck: (item: T) => string,
perItemCallback: (item: T) => void,
matterCallback?: (matter: GrayMatterFile<string>) => void,
): Promise<void> {
const messages = await parseTOML<T[]>(commitBody, commitObject, delimiter, listKey, matterCallback);
const endMessage = getEndMessage(delimiter);
if (!messages || !Array.isArray(messages) || messages.length === 0) {
error(dedent`
List (key: '${listKey}') in body:
\`\`\`
${commitBody}\`\`\`
of commit object ${commitObject.hash} (${commitObject.message}) is empty, not a list, or does not exist.`);
if (commitObject.body && commitBody !== commitObject.body) {
error(dedent`
Original Body:
\`\`\`
${commitObject.body}\`\`\``);
}
error(`${endMessage}\n`);
if (data.isTest) throw new Error("Failed Parsing Message List. See Above.");
return;
}
endMessage: string,
emptyCheck: (item: T) => boolean,
perItemCallback: (item: T) => Promise<void>,
entryModifier?: (index: number) => number,
) {
for (let i = 0; i < messages.length; i++) {
const item = messages[i];
if (!emptyCheck(item)) {
if (emptyCheck(item)) {
let index = i + 1;
if (entryModifier) index = entryModifier(i);
error(dedent`
Missing Requirements for entry ${i + 1} in body:
Missing Requirements for entry ${index} of list with key '${listKey}' in body:
\`\`\`
${commitBody}\`\`\`
of commit object ${commitObject.hash} (${commitObject.message}).`);
@ -373,10 +408,157 @@ async function parseTOMLToList<T>(
if (data.isTest) throw new Error("Bad Entry. See Above.");
continue;
}
perItemCallback(item);
await perItemCallback(item);
}
}
/**
* Parses TOML in a commit body to produce a list. List includes items in the base of the toml (added first).
* List just contains root item if none in list.
* @param commitBody The body to parse
* @param commitObject The commit object to grab messages from, and to determine error messages.
* @param delimiter The delimiters, surrounding the TOML.
* @param listKey The key of the list to parse.
* @param rootObjTransform Transform root obj into T. Return undefined if invalid.
* @param emptyCheck The check to see if an item in the list is invalid. True -> Invalid/Empty
* @param perItemCallback The callback to perform on each item in the list. Async to allow for further toml parsing
* @param matterCallback An optional callback to perform on the matter.
*/
async function parseTOMLWithRootToList<T>(
commitBody: string,
commitObject: Commit,
delimiter: string,
listKey: string,
emptyCheck: (item: T) => boolean,
perItemCallback: (item: T) => Promise<void>,
rootObjTransform?: (root: Record<string, unknown>) => T,
matterCallback?: (matter: GrayMatterFile<string>) => void,
): Promise<void> {
let root: Record<string, unknown>;
const messages: T[] = [];
const endMessage = getEndMessage(delimiter);
// Parse Root TOML
try {
root = await parseTOML<Record<string, unknown>>(commitBody, commitObject, delimiter, null, matterCallback);
const rootObj = rootObjTransform ? rootObjTransform(root) : (root as T);
// Only push root if it passes empty check
if (rootObj && !emptyCheck(rootObj)) messages.push(rootObj);
} catch (e) {
error(dedent`
Failed parsing Root TOML in body:
\`\`\`
${commitBody}\`\`\`
of commit object ${commitObject.hash} (${commitObject.message}).
This could be because of invalid syntax.`);
if (commitObject.body && commitBody !== commitObject.body) {
error(dedent`
Original Body:
\`\`\`
${commitObject.body}\`\`\``);
}
error(`\n${endMessage}\n`);
if (data.isTest) throw e;
return undefined;
}
// No List
if (!(listKey in root)) {
if (messages.length > 0) {
await perItemCallback(messages[0]);
return;
}
// No Valid Entry
error(dedent`
Missing Requirements for root entry, & no list with list key ${listKey} detected in body:
\`\`\`
${commitBody}\`\`\`
of commit object ${commitObject.hash} (${commitObject.message}).`);
if (commitObject.body && commitBody !== commitObject.body) {
error(dedent`
Original Body:
\`\`\`
${commitObject.body}\`\`\``);
}
error(`${endMessage}\n`);
if (data.isTest) throw new Error("No Valid Entry. See Above.");
}
// Parse List TOML
if (!root[listKey] || !Array.isArray(root[listKey])) {
error(dedent`
List (key: '${listKey}') in body:
\`\`\`
${commitBody}\`\`\`
of commit object ${commitObject.hash} (${commitObject.message}) not a list, or does not exist.`);
if (commitObject.body && commitBody !== commitObject.body) {
error(dedent`
Original Body:
\`\`\`
${commitObject.body}\`\`\``);
}
error(`${endMessage}\n`);
if (data.isTest) throw new Error("Failed Parsing List. See Above.");
return;
}
let list: T[];
try {
list = root[listKey] as unknown as T[];
} catch (e) {
error(dedent`
List (key: '${listKey}') in body:
\`\`\`
${commitBody}\`\`\`
of commit object ${commitObject.hash} (${commitObject.message}) could not be turned into correct list type.`);
if (commitObject.body && commitBody !== commitObject.body) {
error(dedent`
Original Body:
\`\`\`
${commitObject.body}\`\`\``);
}
error(`${endMessage}\n`);
if (data.isTest) throw new Error("Failed Parsing List. See Above.");
return;
}
if (list.length === 0) {
error(dedent`
List (key: '${listKey}') in body:
\`\`\`
${commitBody}\`\`\`
of commit object ${commitObject.hash} (${commitObject.message}) is empty.`);
if (commitObject.body && commitBody !== commitObject.body) {
error(dedent`
Original Body:
\`\`\`
${commitObject.body}\`\`\``);
}
error(`${endMessage}\n`);
if (data.isTest) throw new Error("Failed Parsing List. See Above.");
return;
}
if (messages.length > 0) {
messages.push(...list);
// Because we've already done empty check on root obj, no need to suppress error msg
// Keep as index (root: 0, obj1: 1, obj2: 2, ...)
await parseList<T>(messages, listKey, commitBody, commitObject, endMessage, emptyCheck, perItemCallback, (i) => i);
}
// Normal Parsing of List
else await parseList<T>(list, listKey, commitBody, commitObject, endMessage, emptyCheck, perItemCallback);
}
function getEndMessage(delimiter: string) {
if (data.isTest) {
return dedent`

View File

@ -122,8 +122,12 @@ export interface ChangelogMessage {
export interface SpecialChangelogFormatting<T> {
/**
* Formatting Function
* @param message The transformed message (trimmed, transformed issue/pr tags)
* @param subMessage Whether this message is a sub message (true if yes)
* @param indentation The indentation level to use
* @param storage May be null, is the defined storage in this interface
*/
formatting: (message: ChangelogMessage, storage?: T) => string;
formatting: (message: string, subMessage: boolean, indentation: string, storage?: T) => string;
/**
* Storage
@ -256,6 +260,18 @@ export interface FixUpInfo {
newBody?: string;
}
export interface ModInfo {
projectID: number;
info?: string;
detail?: string;
details?: string[];
}
export interface ParsedModInfo {
info?: string;
details?: ChangelogMessage[];
}
export type InputReleaseType = "Release" | "Beta Release" | "Alpha Release" | "Cutting Edge Build";
export interface DeployReleaseType {