Allow for Transforming Issue Tags into Links (#511)

[COMBINE]
commits = ["ce5272fcf17b617495f05de102cf39dc23aece95"]
[COMBINE]
This commit is contained in:
Integer Limit 2023-11-01 08:26:24 +11:00 committed by GitHub
parent 123adfeccb
commit 2d6e7a647e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 146 additions and 57 deletions

View File

@ -16,3 +16,9 @@ export const configFolder = upath.join(overridesFolder, "config");
export const configOverridesFolder = upath.join(overridesFolder, "config-overrides");
export const rootDirectory = "..";
export const templatesFolder = "templates";
// The Repository Owner (For Issues & PR Tags Transforms in Changelog)
export const repoOwner = "Nomi-CEu";
// The Repository Name (For Issues & PR Tags Transforms in Changelog)
export const repoName = "Nomi-CEu";

View File

@ -10,6 +10,8 @@ import parse from "./parser";
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.
@ -24,6 +26,8 @@ async function createChangelog(): Promise<ChangelogData> {
const tags = data.getIterations();
pushTitle(data);
for (const tag of tags) {
const iteration = tags.indexOf(tag);
log(`Iteration ${iteration + 1} of Changelog.`);
data.setupIteration(tag);
categoriesSetup();
specialParserSetup(data);
@ -34,8 +38,8 @@ async function createChangelog(): Promise<ChangelogData> {
await generateModChanges(data);
pushChangelog(data);
if (tags.indexOf(tag) < tags.length - 1) {
await pushChangelog(data);
if (iteration < tags.length - 1) {
// More to go
pushSeperator(data);
data.resetForIteration();
@ -43,6 +47,7 @@ async function createChangelog(): Promise<ChangelogData> {
}
return data;
}
log("No Iterations Detected.");
categoriesSetup();
specialParserSetup(data);
@ -53,7 +58,7 @@ async function createChangelog(): Promise<ChangelogData> {
await generateModChanges(data);
pushAll(data);
await pushAll(data);
return data;
}

View File

@ -8,6 +8,7 @@ import { defaultIndentation, modChangesAllocations, repoLink } from "./definitio
import ChangelogData from "./changelogData";
import { SpecialChangelogFormatting } from "../../types/changelogTypes";
import { sortCommitListReverse } from "./pusher";
import { error } from "fancy-log";
/**
* Mod Changes special formatting
@ -161,7 +162,7 @@ function getCommitChange(SHA: string): CommitChange {
oldManifest = JSON.parse(getFileAtRevision("manifest.json", `${SHA}^`)) as ModpackManifest;
newManifest = JSON.parse(getFileAtRevision("manifest.json", SHA)) as ModpackManifest;
} catch (e) {
console.error(dedent`
error(dedent`
Failed to parse the manifest.json file at commit ${SHA} or the commit before!
Skipping...`);
return;

View File

@ -2,12 +2,15 @@ import ChangelogData from "./changelogData";
import { categories, defaultIndentation } from "./definitions";
import { Category, ChangelogMessage, Commit } from "../../types/changelogTypes";
import { repoLink } from "./definitions";
import { Octokit } from "@octokit/rest";
import { getIssueURL, getNewestIssueURLs } from "../../util/util";
let data: ChangelogData;
let octokit: Octokit;
export default function pushAll(inputData: ChangelogData): void {
export default async function pushAll(inputData: ChangelogData): Promise<void> {
pushTitle(inputData);
pushChangelog(inputData);
await pushChangelog(inputData);
}
export function pushTitle(inputData: ChangelogData): void {
@ -35,15 +38,22 @@ export function pushTitle(inputData: ChangelogData): void {
}
}
export function pushChangelog(inputData: ChangelogData): void {
export async function pushChangelog(inputData: ChangelogData): Promise<void> {
data = inputData;
octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
});
// Save Issue/PR Info to Cache
await getNewestIssueURLs(octokit);
data.builder.push(`# Changes Since ${data.since}`, "");
// Push Sections of Changelog
categories.forEach((category) => {
pushCategory(category);
});
for (const category of categories) {
await pushCategory(category);
}
// Push the commit log
if (data.commitList.length > 0) {
@ -75,12 +85,12 @@ export function pushSeperator(inputData: ChangelogData): void {
/**
* Pushes a given category to the builders.
*/
function pushCategory(category: Category) {
async function pushCategory(category: Category) {
const categoryLog: string[] = [];
let hasValues = false;
// Push All Sub Categories
category.subCategories.forEach((subCategory) => {
for (const subCategory of category.subCategories) {
// Loop through key list instead of map to produce correct order
const list = category.changelogSection.get(subCategory);
if (list && list.length != 0) {
@ -97,19 +107,18 @@ function pushCategory(category: Category) {
);
// Push Log
list.forEach((changelogMessage) => {
categoryLog.push(formatChangelogMessage(changelogMessage));
for (const changelogMessage of list) {
categoryLog.push(await formatChangelogMessage(changelogMessage));
// Push Sub Messages
if (changelogMessage.subChangelogMessages) {
changelogMessage.subChangelogMessages.forEach((subMessage) => {
categoryLog.push(formatChangelogMessage(subMessage, true));
});
for (const subMessage of changelogMessage.subChangelogMessages)
categoryLog.push(await formatChangelogMessage(subMessage, true));
}
});
}
categoryLog.push("");
hasValues = true;
}
});
}
if (hasValues) {
// Push Title
data.builder.push(`## ${category.categoryName}:`);
@ -164,22 +173,15 @@ export function sortCommitListReverse(list: Commit[]): void {
* @param subMessage Whether this message is a subMessage (used in details). Set to true to make it a subMessage (different parsing). Defaults to false.
* @return string Formatted Changelog Message
*/
function formatChangelogMessage(changelogMessage: ChangelogMessage, subMessage = false): string {
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 tags into a link.
if (message.match(/\(#\d+\)/g)) {
const matched = message.match(/\(#\d+\)/g);
matched.forEach((match) => {
// Extract digits
const digits = match.match(/\d+/g);
message = message.replace(match, `([#${digits}](${repoLink}pull/${digits}))`);
});
}
// Transform PR and/or Issue tags into a link.
message = await transformTags(message);
if (changelogMessage.commitObject && !subMessage) {
if (data.combineList.has(changelogMessage.commitObject.hash)) {
@ -228,3 +230,23 @@ function formatCommit(commit: Commit): string {
return `* [\`${shortSHA}\`](${repoLink}commit/${commit.hash}): ${formattedCommit}`;
}
/**
* Transforms PR/Issue Tags into Links.
*/
async function transformTags(message: string): Promise<string> {
if (message.search(/#\d+/) !== -1) {
const matched = message.match(/#\d+/g);
for (const match of matched) {
// Extract digits
const digits = Number.parseInt(match.match(/\d+/)[0]);
// Get PR/Issue Info (PRs are listed in the Issue API Endpoint)
const url = await getIssueURL(digits, octokit);
if (url) {
message = message.replace(match, `[#${digits}](${url})`);
}
}
}
return message;
}

View File

@ -28,6 +28,7 @@ import {
} from "./definitions";
import { findCategories, findSubCategory } from "./parser";
import ChangelogData from "./changelogData";
import { error } from "fancy-log";
let data: ChangelogData;
@ -46,7 +47,7 @@ export async function parseIgnore(commitBody: string, commitObject: Commit): Pro
if (!info) return undefined;
if (!info.checks) {
console.error(dedent`
error(dedent`
Ignore Info in body:
\`\`\`
${commitBody}\`\`\`
@ -58,7 +59,7 @@ export async function parseIgnore(commitBody: string, commitObject: Commit): Pro
try {
infoKeys = Object.keys(info.checks);
} catch (err) {
console.error(dedent`
error(dedent`
Could not get the keys in Ignore Info of body:
\`\`\`
${commitBody}\`\`\`
@ -73,7 +74,7 @@ export async function parseIgnore(commitBody: string, commitObject: Commit): Pro
infoKeys.forEach((key) => {
if (ignoreKeys.has(key)) checkResults.push(ignoreChecks[key].call(this, info.checks[key], data));
else {
console.error(dedent`
error(dedent`
Ignore Check with key '${key}' in body:
\`\`\`
${commitBody}\`\`\`
@ -85,7 +86,7 @@ export async function parseIgnore(commitBody: string, commitObject: Commit): Pro
}
});
if (checkResults.length === 0) {
console.error(dedent`
error(dedent`
No Ignore Checks found in body:
\`\`\`
${commitBody}\`\`\`
@ -102,7 +103,7 @@ export async function parseIgnore(commitBody: string, commitObject: Commit): Pro
if (info.logic === undefined) logic = defaultIgnoreLogic;
else if (Object.keys(ignoreLogics).includes(info.logic)) logic = ignoreLogics[info.logic];
else {
console.error(dedent`
error(dedent`
Ignore Logic '${info.logic}' in body:
\`\`\`
${commitBody}\`\`\`
@ -290,7 +291,7 @@ async function parseTOML<T>(
if (!itemKey) item = parseResult.data as T;
else item = parseResult.data[itemKey];
} catch (e) {
console.error(dedent`
error(dedent`
Failed parsing TOML in body:
\`\`\`
${commitBody}\`\`\`
@ -298,13 +299,13 @@ async function parseTOML<T>(
This could be because of invalid syntax.`);
if (commitObject.body && commitBody !== commitObject.body) {
console.error(dedent`
error(dedent`
Original Body:
\`\`\`
${commitObject.body}\`\`\``);
}
console.error(`\n${endMessage}\n`);
error(`\n${endMessage}\n`);
if (data.isTest) throw e;
return undefined;
}
@ -335,19 +336,19 @@ async function parseTOMLToList<T>(
const endMessage = getEndMessage(delimiter);
if (!messages || !Array.isArray(messages) || messages.length === 0) {
console.error(dedent`
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) {
console.error(dedent`
error(dedent`
Original Body:
\`\`\`
${commitObject.body}\`\`\``);
}
console.error(`${endMessage}\n`);
error(`${endMessage}\n`);
if (data.isTest) throw new Error("Failed Parsing Message List. See Above.");
return;
@ -355,19 +356,19 @@ async function parseTOMLToList<T>(
for (let i = 0; i < messages.length; i++) {
const item = messages[i];
if (!emptyCheck(item)) {
console.error(dedent`
error(dedent`
Missing Requirements for entry ${i + 1} in body:
\`\`\`
${commitBody}\`\`\`
of commit object ${commitObject.hash} (${commitObject.message}).`);
if (commitObject.body && commitBody !== commitObject.body) {
console.error(dedent`
error(dedent`
Original Body:
\`\`\`
${commitObject.body}\`\`\``);
}
console.error(`${endMessage}\n`);
error(`${endMessage}\n`);
if (data.isTest) throw new Error("Bad Entry. See Above.");
continue;

View File

@ -6,6 +6,7 @@ import gulp from "gulp";
import dedent from "dedent-js";
import { checkEnvironmentalVariables } from "../../util/util";
import sortedStringify from "json-stable-stringify-without-jsonify";
import log, { error } from "fancy-log";
// This updates all the files, for a release.
@ -30,11 +31,11 @@ export async function check(): Promise<void> {
const versionsFilePath: string = upath.join(templatesFolder, "versions.txt");
if (notRelease) {
console.log("Detected that this is not a release commit.");
console.log("Version info will not change, but the files will be updated from the template.");
log("Detected that this is not a release commit.");
log("Version info will not change, but the files will be updated from the template.");
await checkNotRelease(versionsFilePath);
} else {
console.log("Detected that this is a release commit.");
log("Detected that this is a release commit.");
await checkRelease(versionsFilePath);
}
}
@ -50,9 +51,7 @@ export async function setNotRelease(): Promise<void> {
async function checkNotRelease(versionsFilePath: string) {
// Check if versions.txt exists
if (!fs.existsSync(versionsFilePath)) {
console.error(
`Version.txt does not exist. Creating empty file, and adding ${version} to it. This may be an error.`,
);
error(`Version.txt does not exist. Creating empty file, and adding ${version} to it. This may be an error.`);
// Create Versions.txt, with version
await fs.promises.writeFile(versionsFilePath, ` - ${version}`);
@ -62,7 +61,7 @@ async function checkNotRelease(versionsFilePath: string) {
// No Duplicate Key
if (!versionList.includes(version)) {
console.error(`Version is not in version.txt. Adding ${version} to version.txt. This may be an error.`);
error(`Version is not in version.txt. Adding ${version} to version.txt. This may be an error.`);
versionList = ` - ${version}\n${versionList}`;
await fs.promises.writeFile(versionsFilePath, versionList);
@ -74,7 +73,7 @@ async function checkNotRelease(versionsFilePath: string) {
async function checkRelease(versionsFilePath: string) {
// Check if versions.txt exists
if (!fs.existsSync(versionsFilePath)) {
console.error("Version.txt does not exist. Creating empty file. This may be an error.");
error("Version.txt does not exist. Creating empty file. This may be an error.");
// Create Versions.txt
fs.closeSync(fs.openSync(versionsFilePath, "w"));

View File

@ -85,12 +85,12 @@ async function fetchExternalDependencies() {
*/
async function fetchOrMakeChangelog() {
if (isEnvVariableSet("CHANGELOG_URL") && isEnvVariableSet("CHANGELOG_CF_URL")) {
console.log("Using Changelog Files from URL.");
log("Using Changelog Files from URL.");
await downloadChangelogs(process.env.CHANGELOG_URL, process.env.CHANGELOG_CF_URL);
return;
}
if (isEnvVariableSet("CHANGELOG_BRANCH")) {
console.log("Using Changelog Files from Branch.");
log("Using Changelog Files from Branch.");
const url = "https://raw.githubusercontent.com/Nomi-CEu/Nomi-CEu/{{ branch }}/{{ filename }}";
await downloadChangelogs(
mustache.render(url, { branch: process.env.CHANGELOG_BRANCH, filename: "CHANGELOG.md" }),
@ -98,7 +98,7 @@ async function fetchOrMakeChangelog() {
);
return;
}
console.log("Creating Changelog Files.");
log("Creating Changelog Files.");
await createBuildChangelog();
}

View File

@ -13,10 +13,11 @@ 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 log, { error } from "fancy-log";
import { pathspec, SimpleGit, simpleGit } from "simple-git";
import { Commit, ModChangeInfo } from "../types/changelogTypes";
import { rootDirectory } from "../globals";
import { repoName, repoOwner, rootDirectory } from "../globals";
import { Octokit } from "@octokit/rest";
const LIBRARY_REG = /^(.+?):(.+?):(.+?)$/;
@ -238,7 +239,7 @@ export async function getChangelog(since = "HEAD", to = "HEAD", dirs: string[] =
const commitList: Commit[] = [];
await git.log(options, (err, output) => {
if (err) {
console.error(err);
error(err);
throw new Error();
}
@ -443,3 +444,57 @@ export function cleanupVersion(version: string): string {
const list = version.match(/[\d+.?]+/g);
return list[list.length - 1];
}
const issueURLCache: Map<number, string> = new Map<number, string>();
/**
* Gets newest updated 100 closed issue/PR URLs of the repo and saves it to the cache.
*/
export async function getNewestIssueURLs(octokit: Octokit): Promise<void> {
if (issueURLCache.size > 0) return;
try {
const issues = await octokit.issues.listForRepo({
owner: repoOwner,
repo: repoName,
per_page: 100,
state: "closed",
sort: "updated",
});
if (issues.status !== 200) {
error(`Failed to get all Issue URLs of Repo. Returned Status Code ${issues.status}, expected Status 200.`);
return;
}
issues.data.forEach((issue) => {
if (!issueURLCache.has(issue.number)) issueURLCache.set(issue.number, issue.html_url);
});
} catch (e) {
error("Failed to get all Issue URLs of Repo. This may be because there are no issues, or because of rate limits.");
}
}
/**
* Gets the specified Issue URL from the cache, or retrieves it.
*/
export async function getIssueURL(issueNumber: number, octokit: Octokit): Promise<string> {
if (issueURLCache.has(issueNumber)) return issueURLCache.get(issueNumber);
try {
const issueInfo = await octokit.issues.get({
owner: repoOwner,
repo: repoName,
issue_number: issueNumber,
});
if (issueInfo.status !== 200) {
error(
`Failed to get the Issue/PR Info for Issue/PR #${issueNumber}. Returned Status Code ${issueInfo.status}, expected Status 200.`,
);
return "";
}
log(`No Issue URL Cache for Issue Number ${issueNumber}. Retrieved Specifically.`);
return issueInfo.data.html_url;
} catch (e) {
error(
`Failed to get the Issue/PR Info for Issue/PR #${issueNumber}. This may be because this is not a PR or Issue, or could be because of rate limits.`,
);
return "";
}
}