From 799fdfb9d50166cf7f56d7bfd06399b2a7ede370 Mon Sep 17 00:00:00 2001 From: Rangga Fajar Oktariansyah <86386385+FajarKim@users.noreply.github.com> Date: Thu, 2 Apr 2026 18:59:55 +0700 Subject: [PATCH 1/2] fix: remove `any` types --- api/index.ts | 21 +++++++++++---------- scripts/generate-translation-doc.ts | 11 +++-------- src/card.ts | 2 +- src/fetcher/repositoryStats.ts | 16 +++++++++++++--- src/getData.ts | 7 ++----- tests/renderStatsCard.test.ts | 17 ++++++++++++++--- 6 files changed, 44 insertions(+), 30 deletions(-) diff --git a/api/index.ts b/api/index.ts index f01ae7f0..6760e1b9 100644 --- a/api/index.ts +++ b/api/index.ts @@ -1,3 +1,4 @@ +import { Request, Response } from "express"; import escapeHTML from "escape-html"; import { Resvg } from "@resvg/resvg-js"; import parseBoolean from "@barudakrosul/parse-boolean"; @@ -16,7 +17,7 @@ import { isValidHexColor, isValidGradient } from "../src/common/utils"; * @property {string} borderColor - Color for borders. * @property {string} strokeColor - Color for strokes. * @property {string} usernameColor - Color for the username. - * @property {any} bgColor - Background color or gradient. + * @property {string} bgColor - Background color or gradient. * @property {string} Title - Add custom title (optional). * @property {string} Locale - Locale setting. * @property {number|string} borderWidth - Width of borders. @@ -38,7 +39,7 @@ type UiConfig = { borderColor: string; strokeColor: string; usernameColor: string; - bgColor: any; + bgColor: string; Title: string | undefined; Locale: string; borderWidth: number | string; @@ -57,10 +58,10 @@ type UiConfig = { /** * Generates an XML string representation of the provided data. * - * @param {any} data - The data to be converted into XML format. + * @param {Record} data - The data to be converted into XML format. * @returns {string} - A string containing the XML representation of the data. */ -function generateXML(data: any): string { +function generateXML(data: Record): string { let xml = `\n`; for (const key in data) { xml += ` <${key}>${escapeHTML(data[key])}\n`; @@ -72,11 +73,11 @@ function generateXML(data: any): string { /** * Handles the generation card of a GitHub stats based on user data and specified options. * - * @param {any} req - The request object from the client. - * @param {any} res - The response object to send data back to the client. - * @returns {Promise} - A promise that resolves when the photo profile is generated and sent. + * @param {Request} req - The request object from the client. + * @param {Response} res - The response object to send data back to the client. + * @returns {Promise} - A promise that resolves when the photo profile is generated and sent. */ -async function readmeStats(req: any, res: any): Promise { +async function readmeStats(req: Request, res: Response): Promise { try { const username = escapeHTML(req.query.username); const photoQuality = Math.max(0, Math.min(parseInt(escapeHTML(req.query.photo_quality || "15")), 100)); @@ -130,7 +131,7 @@ async function readmeStats(req: any, res: any): Promise { } } - const fetchStats = await getData(username); + const fetchStats: GetData = await getData(username); res.setHeader("Cache-Control", "s-maxage=7200, stale-while-revalidate"); if (uiConfig.Format === "json") { @@ -153,7 +154,7 @@ async function readmeStats(req: any, res: any): Promise { const svg = await card(fetchStats, uiConfig); res.send(svg); } - } catch (error: any) { + } catch (error: unknown) { const message = error.message; res.status(500).send(escapeHTML(message)); } diff --git a/scripts/generate-translation-doc.ts b/scripts/generate-translation-doc.ts index f3b812a6..c3af6cb9 100644 --- a/scripts/generate-translation-doc.ts +++ b/scripts/generate-translation-doc.ts @@ -16,7 +16,7 @@ function generateTranslationsMarkdown(locale: string): string { return `${locale}`; } -export function generateReadmeLocales() { +export function generateReadmeLocales(): string { const availableLocales = Object.keys(locales).sort(); let localesListTable = ""; @@ -24,14 +24,10 @@ export function generateReadmeLocales() { const localesSlice = availableLocales.slice(i, i + 1); const row = localesSlice.map(locale => generateTranslationsMarkdown(locale)).join(""); - const progress = (Object.keys(locales[row]).length / 16) * 100; + const progress = (Object.keys(locales[row as keyof typeof locales]).length / 16) * 100; const progressColor = getProgressColor(progress); - localesListTable += ` -

${row}

-

${languageNames[row]}

-

${progress.toFixed(0)}%

- \n`; + localesListTable += ` \n

${row}

\n

${languageNames[row as keyof typeof languageNames]}

\n

${progress.toFixed(0)}%

\n \n`; } const readmeContent = ` @@ -59,5 +55,4 @@ Want to add new translations? Consider reading the [contribution guidelines](htt } const generatedReadme = generateReadmeLocales(); - fs.writeFileSync(TARGET_FILE, generatedReadme); diff --git a/src/card.ts b/src/card.ts index 5cab4066..18907dad 100644 --- a/src/card.ts +++ b/src/card.ts @@ -241,7 +241,7 @@ export default async function card(data: GetData, uiConfig: UiConfig): Promise item.visible); const cardItemsSVG = visibleItems.map((item, idx) => { - const label = (selectedLocale as any)[item.labelKey]; + const label = (selectedLocale as Record)[item.labelKey]; const value = data[item.valueKey as keyof GetData]; return ` diff --git a/src/fetcher/repositoryStats.ts b/src/fetcher/repositoryStats.ts index 119afe84..05610317 100644 --- a/src/fetcher/repositoryStats.ts +++ b/src/fetcher/repositoryStats.ts @@ -1,6 +1,15 @@ import axios from "axios"; import getToken from "../getToken"; +/** + * Represents the structure of a GitHub repository object returned from API. + */ +interface Repository { + stargazers_count: number; + forks_count: number; + open_issues: number; +} + /** * Type representing the data associated with a user's repository stats. * @@ -35,8 +44,8 @@ async function repositoryStats( { length: totalpage }, async (_, i) => await getPerPageRepositoryData(username, i + 1) ) - ).then((data: object[]) => { - data.forEach((repo: any) => { + ).then((data: RepositoryData[]) => { + data.forEach((repo: RepositoryData) => { stars += repo.stars; forks += repo.forks; openedIssues += repo.openedIssues; @@ -76,7 +85,8 @@ async function getPerPageRepositoryData( let forks = 0; let openedIssues = 0; - data.data.forEach((repo: any) => { + // Iterate over each repository in the response and accumulate stats + data.data.forEach((repo: Repository) => { stars += repo.stargazers_count; forks += repo.forks_count; openedIssues += repo.open_issues; diff --git a/src/getData.ts b/src/getData.ts index 4f5f64ac..9eebd071 100644 --- a/src/getData.ts +++ b/src/getData.ts @@ -75,17 +75,14 @@ async function getData(username: string): Promise { total_closed_issues: millify(user.closedIssues.totalCount), total_prs: millify(user.pullRequests.totalCount), total_prs_merged: millify(user.mergedPullRequests.totalCount), - total_commits: millify( - user.restrictedContributionsCount + user.totalCommitContributions - ), + total_commits: millify(user.totalCommitContributions + user.restrictedContributionsCount), total_review: millify(user.totalPullRequestReviewContributions), total_discussion_answered: millify(user.discussionAnswered.totalCount), total_discussion_started: millify(user.discussionStarted.totalCount), total_contributed_to: millify(user.repositoriesContributedTo.totalCount), - }; + } as GetData; return output; } - export { GetData, getData }; export default getData; diff --git a/tests/renderStatsCard.test.ts b/tests/renderStatsCard.test.ts index f0feb0fb..2a9b6542 100644 --- a/tests/renderStatsCard.test.ts +++ b/tests/renderStatsCard.test.ts @@ -5,6 +5,17 @@ import card from "../src/card"; import themes from "../themes/index"; import locales from "../i18n/index"; +interface MockRequest extends Partial { + query: Record; +} + +interface MockResponse extends Partial { + json: jest.Mock; + send: jest.Mock; + setHeader: jest.Mock; + status: jest.Mock; +} + jest.mock("../src/getData"); jest.mock("../src/card"); @@ -37,8 +48,8 @@ const exampleUserData: User = { }; describe("Test GitHub Readme Profile API", () => { - let mockRequest: any; - let mockResponse: any; + let mockRequest: MockRequest; + let mockResponse: MockResponse; beforeEach(() => { mockRequest = { @@ -49,7 +60,7 @@ describe("Test GitHub Readme Profile API", () => { json: jest.fn(), send: jest.fn(), setHeader: jest.fn(), - status: jest.fn(), + status: jest.fn(() => mockResponse), }; jest.clearAllMocks(); From 6fd894c95f9eee34288d3597a83797502e16620b Mon Sep 17 00:00:00 2001 From: Rangga Fajar Oktariansyah <86386385+FajarKim@users.noreply.github.com> Date: Fri, 3 Apr 2026 17:10:38 +0700 Subject: [PATCH 2/2] Fixes error --- api/index.ts | 19 +++++++++---------- src/common/utils.ts | 17 ++++++++++++----- tests/renderStatsCard.test.ts | 17 +++-------------- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/api/index.ts b/api/index.ts index 6760e1b9..a810e400 100644 --- a/api/index.ts +++ b/api/index.ts @@ -1,8 +1,7 @@ -import { Request, Response } from "express"; import escapeHTML from "escape-html"; import { Resvg } from "@resvg/resvg-js"; import parseBoolean from "@barudakrosul/parse-boolean"; -import getData from "../src/getData"; +import { getData,GetData } from "../src/getData"; import card from "../src/card"; import { themes, Themes } from "../themes/index"; import { isValidHexColor, isValidGradient } from "../src/common/utils"; @@ -17,7 +16,7 @@ import { isValidHexColor, isValidGradient } from "../src/common/utils"; * @property {string} borderColor - Color for borders. * @property {string} strokeColor - Color for strokes. * @property {string} usernameColor - Color for the username. - * @property {string} bgColor - Background color or gradient. + * @property {string|string[]} bgColor - Background color or gradient. * @property {string} Title - Add custom title (optional). * @property {string} Locale - Locale setting. * @property {number|string} borderWidth - Width of borders. @@ -39,7 +38,7 @@ type UiConfig = { borderColor: string; strokeColor: string; usernameColor: string; - bgColor: string; + bgColor: string|string[]; Title: string | undefined; Locale: string; borderWidth: number | string; @@ -58,10 +57,10 @@ type UiConfig = { /** * Generates an XML string representation of the provided data. * - * @param {Record} data - The data to be converted into XML format. + * @param {any} data - The data to be converted into XML format. * @returns {string} - A string containing the XML representation of the data. */ -function generateXML(data: Record): string { +function generateXML(data: any): string { let xml = `\n`; for (const key in data) { xml += ` <${key}>${escapeHTML(data[key])}\n`; @@ -73,11 +72,11 @@ function generateXML(data: Record): string { /** * Handles the generation card of a GitHub stats based on user data and specified options. * - * @param {Request} req - The request object from the client. - * @param {Response} res - The response object to send data back to the client. + * @param {any} req - The request object from the client. + * @param {any} res - The response object to send data back to the client. * @returns {Promise} - A promise that resolves when the photo profile is generated and sent. */ -async function readmeStats(req: Request, res: Response): Promise { +async function readmeStats(req: any, res: any): Promise { try { const username = escapeHTML(req.query.username); const photoQuality = Math.max(0, Math.min(parseInt(escapeHTML(req.query.photo_quality || "15")), 100)); @@ -154,7 +153,7 @@ async function readmeStats(req: Request, res: Response): Promise { const svg = await card(fetchStats, uiConfig); res.send(svg); } - } catch (error: unknown) { + } catch (error: any) { const message = error.message; res.status(500).send(escapeHTML(message)); } diff --git a/src/common/utils.ts b/src/common/utils.ts index 5ccd4889..f2d43bbc 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,10 +1,11 @@ /** * Checks whether the provided value is a valid hexadecimal color. * - * @param {any} hexColor - The input value to check for valid hexadecimal color. + * @param {string|string[]} hexColor - The input value to check for valid hexadecimal color. * @returns {boolean} - True if the input is a valid hexadecimal color, false otherwise. */ -function isValidHexColor(hexColor: string): boolean { +function isValidHexColor(hexColor: string | string[]): boolean { + if (Array.isArray(hexColor)) return false; const re = new RegExp("^([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$", "g"); return re.test(hexColor); } @@ -12,11 +13,17 @@ function isValidHexColor(hexColor: string): boolean { /** * Checks whether the provided array of hexadecimal colors is a valid gradient. * - * @param {string[]} hexColors - The array of hexadecimal colors to check for a valid gradient. + * @param {string|string[]} hexColors - The array of hexadecimal colors to check for a valid gradient. * @returns {boolean} - True if the array represents a valid gradient, false otherwise. */ -function isValidGradient(hexColors: string): boolean { - const colors = hexColors.split(","); +function isValidGradient(hexColors: string | string[]): boolean { + let colors; + if (Array.isArray(hexColors)) { + colors = hexColors; + } else { + colors = hexColors.split(","); + } + for (let i = 1; i < colors.length; i++) { if (!isValidHexColor(colors[i])) { return false; diff --git a/tests/renderStatsCard.test.ts b/tests/renderStatsCard.test.ts index 2a9b6542..f0feb0fb 100644 --- a/tests/renderStatsCard.test.ts +++ b/tests/renderStatsCard.test.ts @@ -5,17 +5,6 @@ import card from "../src/card"; import themes from "../themes/index"; import locales from "../i18n/index"; -interface MockRequest extends Partial { - query: Record; -} - -interface MockResponse extends Partial { - json: jest.Mock; - send: jest.Mock; - setHeader: jest.Mock; - status: jest.Mock; -} - jest.mock("../src/getData"); jest.mock("../src/card"); @@ -48,8 +37,8 @@ const exampleUserData: User = { }; describe("Test GitHub Readme Profile API", () => { - let mockRequest: MockRequest; - let mockResponse: MockResponse; + let mockRequest: any; + let mockResponse: any; beforeEach(() => { mockRequest = { @@ -60,7 +49,7 @@ describe("Test GitHub Readme Profile API", () => { json: jest.fn(), send: jest.fn(), setHeader: jest.fn(), - status: jest.fn(() => mockResponse), + status: jest.fn(), }; jest.clearAllMocks();