diff --git a/tools/tasks/changelog/changelogData.ts b/tools/tasks/changelog/changelogData.ts index d55438e..41a2815 100644 --- a/tools/tasks/changelog/changelogData.ts +++ b/tools/tasks/changelog/changelogData.ts @@ -1,5 +1,5 @@ import { Commit, FixUpInfo, InputReleaseType } from "../../types/changelogTypes"; -import { getLastGitTag, isEnvVariableSet } from "../../util/util"; +import { getLastGitTag, getTags, isEnvVariableSet } from "../../util/util"; export default class ChangelogData { since: string; @@ -16,7 +16,10 @@ export default class ChangelogData { // Map of a commit SHA to the commits which need to be added to its commit list. combineList: Map; - constructor() { + // Set of tags + tags: Set; + + async init(): Promise { this.since = getLastGitTag(); this.to = "HEAD"; @@ -48,5 +51,7 @@ export default class ChangelogData { this.commitFixes = new Map(); this.shaList = new Set(); this.combineList = new Map(); + + this.tags = new Set(await getTags(this.to)); } } diff --git a/tools/tasks/changelog/createChangelog.ts b/tools/tasks/changelog/createChangelog.ts index 982b6eb..eda3fc2 100644 --- a/tools/tasks/changelog/createChangelog.ts +++ b/tools/tasks/changelog/createChangelog.ts @@ -16,7 +16,10 @@ import pushAll from "./pusher"; */ async function createChangelog(): Promise { const data: ChangelogData = new ChangelogData(); - changelogSetup(data); + + await data.init(); + categoriesSetup(); + specialParserSetup(data); for (const parser of parsers) { await parse(data, parser); @@ -29,11 +32,6 @@ async function createChangelog(): Promise { return data; } -function changelogSetup(data: ChangelogData) { - categoriesSetup(); - specialParserSetup(data); -} - /** * Creates a changelog based on environment variables, and saves it to the root directory. */ diff --git a/tools/tasks/changelog/definitions.ts b/tools/tasks/changelog/definitions.ts index 4c1cb25..527e382 100644 --- a/tools/tasks/changelog/definitions.ts +++ b/tools/tasks/changelog/definitions.ts @@ -1,4 +1,4 @@ -import { Category, Commit, Parser, SubCategory } from "../../types/changelogTypes"; +import { Category, Commit, Ignored, Parser, SubCategory } from "../../types/changelogTypes"; import { modpackManifest } from "../../globals"; import { parseCommitBody } from "./parser"; import { parseFixUp } from "./specialParser"; @@ -26,6 +26,7 @@ export const combineKey = "[COMBINE]"; export const combineList = "commits"; export const fixUpKey = "[FIXUP]"; export const fixUpList = "fixes"; +export const ignoreKey = "[IGNORE]"; /* Sub Category Keys */ // Mode Category Keys @@ -121,7 +122,7 @@ const defaultParsingCallback = async ( commit: Commit, commitMessage: string, commitBody: string, -): Promise => { +): Promise => { if (!commitBody) return false; return parseCommitBody(commitMessage, commitBody, commit, parser); }; diff --git a/tools/tasks/changelog/parser.ts b/tools/tasks/changelog/parser.ts index 5fcc7d6..ee9b5b4 100644 --- a/tools/tasks/changelog/parser.ts +++ b/tools/tasks/changelog/parser.ts @@ -1,6 +1,14 @@ -import { Category, Commit, Parser, SubCategory } from "../../types/changelogTypes"; -import { categories, combineKey, defaultIndentation, detailsKey, expandKey, noCategoryKey } from "./definitions"; -import { parseCombine, parseDetails, parseExpand } from "./specialParser"; +import { Category, Commit, Ignored, Parser, SubCategory } from "../../types/changelogTypes"; +import { + categories, + combineKey, + defaultIndentation, + detailsKey, + expandKey, + ignoreKey, + noCategoryKey, +} from "./definitions"; +import { parseCombine, parseDetails, parseExpand, parseIgnore } from "./specialParser"; import { getChangelog } from "../../util/util"; import ChangelogData from "./changelogData"; @@ -22,6 +30,12 @@ export default async function parseParser(data: ChangelogData, parser: Parser): } const parsed = await parser.itemCallback(parser, commit, commit.message, commit.body); + if (parsed instanceof Ignored) { + if (parsed.getCommitList() && parser.addCommitListCallback) { + if (parser.addCommitListCallback(commit, true)) data.commitList.push(commit); + } + continue; + } if (!parsed && parser.leftOverCallback) parser.leftOverCallback(commit, commit.message, commit.body, []); if (!parser.addSHACallback || parser.addSHACallback(commit, parsed)) data.shaList.add(commit.hash); @@ -43,11 +57,17 @@ export async function parseCommitBody( commitBody: string, commitObject: Commit, parser: Parser, -): Promise { +): Promise { if (commitBody.includes(expandKey)) { await parseExpand(commitBody, commitObject, parser); return true; } + if (commitBody.includes(ignoreKey)) { + const ignore = await parseIgnore(commitBody, commitObject); + + // Only return if ignore is not undefined + if (ignore) return ignore; + } if (commitBody.includes(detailsKey)) { await parseDetails(commitMessage, commitBody, commitObject, parser); return true; diff --git a/tools/tasks/changelog/pusher.ts b/tools/tasks/changelog/pusher.ts index 9f86da6..f89755b 100644 --- a/tools/tasks/changelog/pusher.ts +++ b/tools/tasks/changelog/pusher.ts @@ -35,19 +35,17 @@ export default function pushAll(inputData: ChangelogData): void { }); // Push the commit log - if (data.commitList) { + if (data.commitList.length > 0) { sortCommitList(data.commitList, (commit) => commit); data.builder.push("## Commits"); data.commitList.forEach((commit) => { data.builder.push(formatCommit(commit)); }); - } - - // Check if the builder only contains the title. - if (data.builder.length <= 3) { + } else { + // No Commit List = No Changes data.builder.push(""); - data.builder.push("There haven't been any changes."); + data.builder.push("**There haven't been any changes.**"); } // Push link @@ -133,7 +131,7 @@ function sortCommitList(list: T[], transform: (obj: T) => Commit | undefined, * Sorts a commits list so that newest commits are on the bottom. * @param list The commit list. */ -export function sortCommitListReverse(list: Commit[]) { +export function sortCommitListReverse(list: Commit[]): void { list.sort((a, b) => { const dateA = new Date(a.date); const dateB = new Date(b.date); diff --git a/tools/tasks/changelog/specialParser.ts b/tools/tasks/changelog/specialParser.ts index ea27a91..be474e6 100644 --- a/tools/tasks/changelog/specialParser.ts +++ b/tools/tasks/changelog/specialParser.ts @@ -1,4 +1,12 @@ -import { ChangelogMessage, Commit, ExpandedMessage, FixUpInfo, Parser } from "../../types/changelogTypes"; +import { + ChangelogMessage, + Commit, + ExpandedMessage, + FixUpInfo, + Ignored, + IgnoreInfo, + Parser, +} from "../../types/changelogTypes"; import dedent from "dedent-js"; import matter, { GrayMatterFile } from "gray-matter"; import toml from "@ltd/j-toml"; @@ -11,6 +19,7 @@ import { expandList, fixUpKey, fixUpList, + ignoreKey, indentationLevel, } from "./definitions"; import { findCategories, findSubCategory } from "./parser"; @@ -22,12 +31,46 @@ export function specialParserSetup(inputData: ChangelogData): void { data = inputData; } +/** + * Checks a commit's ignore. + * @commit The Commit Body. Does check whether the ignore key is there. + * @return Returns undefined to continue, and an Ignored object if to skip. + */ +export async function parseIgnore(commitBody: string, commitObject: Commit): Promise { + if (!commitBody.includes(ignoreKey)) return undefined; + const info = await parseTOML(commitBody, commitObject, ignoreKey); + if (!info) return undefined; + if (!info.before && !info.after) { + console.error(dedent` + Ignore Info in body: + \`\`\` + ${commitBody}\`\`\` + of commit object ${commitObject.hash} (${commitObject.message}) is missing both keys 'before' and 'after'! + At least one of these values must be set!`); + if (data.isTest) throw new Error("Failed Parsing Ignore Info. See Above."); + return undefined; + } + + let isBefore = undefined, + isAfter = undefined; + + if (info.before) isBefore = !data.tags.has(info.before); + if (info.after) isAfter = data.tags.has(info.after); + + // Return Ignores + if (isBefore === undefined || isAfter === undefined) { + if (isBefore || isAfter) return new Ignored(info.addCommitList); + } else if (isBefore && isAfter) return new Ignored(info.addCommitList); + + return undefined; +} + /** * Parses a commit with 'Fixup'. */ export async function parseFixUp(commit: Commit): Promise { if (!commit.body || !commit.body.includes(fixUpKey)) return false; - await parse( + await parseTOMLToList( commit.body, commit, fixUpKey, @@ -54,7 +97,7 @@ export async function parseFixUp(commit: Commit): Promise { * Parses a commit with 'expand'. */ export async function parseExpand(commitBody: string, commitObject: Commit, parser: Parser): Promise { - await parse( + await parseTOMLToList( commitBody, commitObject, expandKey, @@ -117,7 +160,7 @@ async function expandDetailsLevel( indentation = indentationLevel, ): Promise { const result: ChangelogMessage[] = []; - await parse( + await parseTOMLToList( commitBody, commitObject, detailsKey, @@ -139,7 +182,7 @@ async function expandDetailsLevel( * Parses a commit with 'combine'. */ export async function parseCombine(commitBody: string, commitObject: Commit): Promise { - await parse( + await parseTOMLToList( commitBody, commitObject, combineKey, @@ -153,32 +196,23 @@ export async function parseCombine(commitBody: string, commitObject: Commit): Pr } /** - * Parse TOML in a commit body to produce a list. + * Parse TOML in a commit body. * @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 itemKey The key of the item to parse. If not set, will just parse the main object. * @param matterCallback An optional callback to perform on the matter. + * @returns item The Item/Object. Undefined if error. */ -async function parse( +async function parseTOML( commitBody: string, commitObject: Commit, delimiter: string, - listKey: string, - emptyCheck: (item: T) => string, - perItemCallback: (item: T) => void, + itemKey?: string, matterCallback?: (matter: GrayMatterFile) => void, -): Promise { - let messages: T[]; - let endMessage = "Skipping..."; - - if (data.isTest) { - endMessage = dedent` - Try checking the TOML syntax in https://www.toml-lint.com/, checking the object tree in https://www.convertsimple.com/convert-toml-to-json/, checking syntax in https://toml.io/en/v1.0.0, and looking through https://github.com/Nomi-CEu/Nomi-CEu/blob/main/CONTRIBUTING.md! - Also check that you have surrounded the TOML in '${delimiter}'!`; - } +): Promise { + let item: T; + const endMessage = getEndMessage(delimiter); try { // Remove everything before first delimiter in body @@ -200,14 +234,15 @@ async function parse( if (matterCallback) matterCallback(parseResult); - messages = parseResult.data[listKey]; + if (!itemKey) item = parseResult.data as T; + else item = parseResult.data[itemKey]; } catch (e) { console.error(dedent` Failed parsing TOML in body: \`\`\` ${commitBody}\`\`\` of commit object ${commitObject.hash} (${commitObject.message}). - This could be because of invalid syntax, or because the Message List (key: '${listKey}') is not an array.`); + This could be because of invalid syntax.`); if (commitObject.body && commitBody !== commitObject.body) { console.error(dedent` @@ -218,9 +253,34 @@ async function parse( console.error(`\n${endMessage}\n`); if (data.isTest) throw e; - return; + return undefined; } + return item; +} + +/** + * 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. + */ +async function parseTOMLToList( + commitBody: string, + commitObject: Commit, + delimiter: string, + listKey: string, + emptyCheck: (item: T) => string, + perItemCallback: (item: T) => void, + matterCallback?: (matter: GrayMatterFile) => void, +): Promise { + const messages = await parseTOML(commitBody, commitObject, delimiter, listKey, matterCallback); + const endMessage = getEndMessage(delimiter); + if (!messages || !Array.isArray(messages) || messages.length === 0) { console.error(dedent` List (key: '${listKey}') in body: @@ -262,3 +322,12 @@ async function parse( perItemCallback(item); } } + +function getEndMessage(delimiter: string) { + if (data.isTest) { + return dedent` + Try checking the TOML syntax in https://www.toml-lint.com/, checking the object tree in https://www.convertsimple.com/convert-toml-to-json/, checking syntax in https://toml.io/en/v1.0.0, and looking through https://github.com/Nomi-CEu/Nomi-CEu/blob/main/CONTRIBUTING.md! + Also check that you have surrounded the TOML in '${delimiter}'!`; + } + return "Skipping..."; +} diff --git a/tools/types/changelogTypes.ts b/tools/types/changelogTypes.ts index 1c96792..3c241d9 100644 --- a/tools/types/changelogTypes.ts +++ b/tools/types/changelogTypes.ts @@ -159,9 +159,14 @@ export interface Parser { * commit: The commit object. * commitMessage: The message of the commit.

* commitBody: The body of the commit. Might be undefined.

- * return: True if parsing was successful, false if not. + * return: True if parsing was successful, false if not. Can return Ignored if commit was ignored (not skipped). */ - itemCallback: (parser: Parser, commit: Commit, commitMessage: string, commitBody?: string) => Promise; + itemCallback: ( + parser: Parser, + commit: Commit, + commitMessage: string, + commitBody?: string, + ) => Promise; /** * The callback to perform on any commits, which did not pass parsing. If not set, no callback will be performed, and those commits will be discarded. @@ -199,6 +204,25 @@ export interface Parser { addCommitListCallback: (commit: Commit, parsed: boolean) => boolean; } +export interface IgnoreInfo { + before?: string; + after?: string; + addCommitList?: boolean; +} + +export class Ignored { + private readonly addCommitList: boolean | undefined; + + constructor(addCommitList?: boolean) { + this.addCommitList = addCommitList; + } + + getCommitList(): boolean { + if (this.addCommitList === undefined) return false; + return this.addCommitList; + } +} + export interface ModChangeInfo { modName: string; projectID?: number; diff --git a/tools/util/util.ts b/tools/util/util.ts index 0a80b46..5a5a04b 100644 --- a/tools/util/util.ts +++ b/tools/util/util.ts @@ -255,6 +255,17 @@ export async function getChangelog(since = "HEAD", to = "HEAD", dirs: string[] = return commitList; } +/** + * Gets the list of tags that are at or before a certain ref point. + * @param ref The ref point. Can be a tag or a commit sha. If not set, defaults to HEAD. + * @returns tags An array of all the tags + */ +export async function getTags(ref = "HEAD"): Promise { + const options: string[] = ["--merged", ref]; + const test = await git.tags(options); + return test.all; +} + /** * Gets the file at a certain point in time. * @param path The path to the file