mirror of
https://github.com/ARMSX2/Armsx2-Repo.git
synced 2026-08-24 16:52:58 -07:00
136 lines
4.7 KiB
JavaScript
136 lines
4.7 KiB
JavaScript
import { resolve } from "node:path";
|
|
|
|
import { SourceGenerationError } from "./errors.js";
|
|
import { optionalJsonDocument } from "./source-utils.js";
|
|
|
|
const releaseRepository = (generatorOptions, metadataPayload) =>
|
|
generatorOptions.upstreamReleaseRepo
|
|
?? metadataPayload.releaseNotes.upstreamRepository
|
|
?? null;
|
|
|
|
const validReleaseRepository = (repositoryName) =>
|
|
typeof repositoryName === "string" && /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(repositoryName);
|
|
|
|
const githubHeaders = () => {
|
|
const headers = {
|
|
Accept: "application/vnd.github+json",
|
|
"User-Agent": "J1coding-ARMSX2-Source-Generator/2.1",
|
|
};
|
|
|
|
if (process.env.GITHUB_TOKEN) {
|
|
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
}
|
|
|
|
return headers;
|
|
};
|
|
|
|
export const fetchGithubReleases = async (generatorOptions, metadataPayload) => {
|
|
const repositoryName = releaseRepository(generatorOptions, metadataPayload);
|
|
|
|
if (generatorOptions.offline || !repositoryName) {
|
|
return [];
|
|
}
|
|
|
|
if (!validReleaseRepository(repositoryName)) {
|
|
throw new SourceGenerationError(`Invalid upstream release repository: ${repositoryName}`);
|
|
}
|
|
|
|
try {
|
|
const releaseResponse = await fetch(`https://api.github.com/repos/${repositoryName}/releases?per_page=100`, {
|
|
headers: githubHeaders(),
|
|
});
|
|
|
|
if (!releaseResponse.ok) {
|
|
console.warn(
|
|
`GitHub release lookup for ${repositoryName} failed: ${releaseResponse.status} ${releaseResponse.statusText}`,
|
|
);
|
|
return [];
|
|
}
|
|
|
|
const releasePayload = await releaseResponse.json();
|
|
return Array.isArray(releasePayload) ? releasePayload : [];
|
|
} catch (fetchError) {
|
|
const message = fetchError instanceof Error ? fetchError.message : String(fetchError);
|
|
console.warn(`GitHub release lookup for ${repositoryName} failed: ${message}`);
|
|
return [];
|
|
}
|
|
};
|
|
|
|
const escapedRegExp = (patternText) => patternText.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
|
|
const releaseMatchesManifest = (githubRelease, manifest) => {
|
|
const releaseFields = [
|
|
githubRelease.tag_name,
|
|
githubRelease.name,
|
|
].filter(Boolean).map(String);
|
|
|
|
const versionPattern = new RegExp(`(^|[^0-9])v?${escapedRegExp(manifest.version)}(?=$|[^0-9.])`, "iu");
|
|
return releaseFields.some((releaseField) => versionPattern.test(releaseField));
|
|
};
|
|
|
|
const matchingGithubRelease = (githubReleases, manifest) =>
|
|
githubReleases.find((githubRelease) => releaseMatchesManifest(githubRelease, manifest)) ?? null;
|
|
|
|
const markdownToStoreText = (markdownText) => {
|
|
const cleanedText = String(markdownText)
|
|
.replace(/\r\n?/gu, "\n")
|
|
.replace(/```[\s\S]*?```/gu, "")
|
|
.replace(/!\[[^\]]*\]\([^)]*\)/gu, "")
|
|
.replace(/\[([^\]]+)\]\([^)]*\)/gu, "$1")
|
|
.replace(/`([^`]+)`/gu, "$1")
|
|
.replace(/^#{1,6}\s*/gmu, "")
|
|
.replace(/^\s*[-*+]\s+/gmu, "- ")
|
|
.replace(/\n{3,}/gu, "\n\n")
|
|
.trim();
|
|
|
|
return cleanedText
|
|
.split("\n")
|
|
.map((line) => line.trimEnd())
|
|
.filter((line) => !/^full changelog:?/iu.test(line))
|
|
.slice(0, 10)
|
|
.join("\n")
|
|
.slice(0, 1200)
|
|
.trim();
|
|
};
|
|
|
|
const fallbackChangelog = (manifest, metadataPayload) => {
|
|
const fallbackTemplate = metadataPayload.releaseNotes.fallback
|
|
?? "Updated to ARMSX2 iOS {version}.\n\nIncludes the latest published iOS build, verified download metadata, and local-network permission disclosure for supported online features.";
|
|
|
|
return fallbackTemplate.replaceAll("{version}", manifest.version);
|
|
};
|
|
|
|
export const existingVersionDescriptions = async (repositoryRoot, sourcePath) => {
|
|
const existingSourcePath = resolve(repositoryRoot, sourcePath);
|
|
const existingSourcePayload = await optionalJsonDocument(existingSourcePath);
|
|
const versionDescriptions = new Map();
|
|
|
|
for (const sourceApp of existingSourcePayload.apps ?? []) {
|
|
for (const sourceVersion of sourceApp.versions ?? []) {
|
|
const description = sourceVersion.localizedDescription;
|
|
|
|
if (typeof description !== "string" || !description.startsWith("Updated to ARMSX2 iOS")) {
|
|
continue;
|
|
}
|
|
|
|
versionDescriptions.set(`${sourceVersion.version}|${sourceVersion.downloadURL}`, description);
|
|
versionDescriptions.set(sourceVersion.version, description);
|
|
}
|
|
}
|
|
|
|
return versionDescriptions;
|
|
};
|
|
|
|
export const storeChangelog = (manifest, metadataPayload, githubReleases, existingDescriptions) => {
|
|
const githubRelease = matchingGithubRelease(githubReleases, manifest);
|
|
const releaseBody = markdownToStoreText(githubRelease?.body ?? "");
|
|
|
|
if (releaseBody) {
|
|
return `Updated to ARMSX2 iOS ${manifest.version}.\n\n${releaseBody}`;
|
|
}
|
|
|
|
return existingDescriptions.get(`${manifest.version}|${manifest.downloadURL}`)
|
|
?? existingDescriptions.get(manifest.version)
|
|
?? fallbackChangelog(manifest, metadataPayload);
|
|
};
|