From b63bb396195b6d215ec933f694c752ad1e0bde27 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Wed, 11 Feb 2026 00:17:58 -0500 Subject: [PATCH 01/17] chore: add initial TypeScript tsconfig --- .gitignore | 3 ++- tsconfig.json | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 3c4ad79..a0cf8d7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ coverage/ node_modules/ +dist/ -.vercel \ No newline at end of file +.vercel diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1d4d687 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "ES6", + "outDir": "./dist", + "allowJs": true + }, + "include": ["./src/**/*.ts"] +} From 426985586a18bb0ef8758ec4a0002b0448154aef Mon Sep 17 00:00:00 2001 From: Masonlet Date: Wed, 11 Feb 2026 00:56:03 -0500 Subject: [PATCH 02/17] refactor: migrate src/constants to TypeScript --- src/constants/{config.js => config.ts} | 2 +- src/constants/{geometry.js => geometry.ts} | 2 +- src/constants/{styles.js => styles.ts} | 6 +++--- src/constants/{themes.js => themes.ts} | 2 +- src/constants/types.js | 1 - src/constants/types.ts | 4 ++++ 6 files changed, 10 insertions(+), 7 deletions(-) rename src/constants/{config.js => config.ts} (94%) rename src/constants/{geometry.js => geometry.ts} (92%) rename src/constants/{styles.js => styles.ts} (90%) rename src/constants/{themes.js => themes.ts} (98%) delete mode 100644 src/constants/types.js create mode 100644 src/constants/types.ts diff --git a/src/constants/config.js b/src/constants/config.ts similarity index 94% rename from src/constants/config.js rename to src/constants/config.ts index 8a59527..69c74a3 100644 --- a/src/constants/config.js +++ b/src/constants/config.ts @@ -4,7 +4,7 @@ export const DEFAULT_CONFIG = { HEIGHT: 300, COUNT: 8, MIN_WIDTH: 400 -} +} as const; export const REFRESH_INTERVAL = 1000 * 60 * 60; export const MAX_COUNT = 16; diff --git a/src/constants/geometry.js b/src/constants/geometry.ts similarity index 92% rename from src/constants/geometry.js rename to src/constants/geometry.ts index f29c854..02320d9 100644 --- a/src/constants/geometry.js +++ b/src/constants/geometry.ts @@ -3,6 +3,6 @@ export const DONUT_GEOMETRY = { OUTER_RADIUS: 80, INNER_RADIUS: 50, MARGIN_RIGHT: 20 -} +} as const; export const FULL_CIRCLE_ANGLE = 359.9999; diff --git a/src/constants/styles.js b/src/constants/styles.ts similarity index 90% rename from src/constants/styles.js rename to src/constants/styles.ts index 47f26e4..daa0b26 100644 --- a/src/constants/styles.js +++ b/src/constants/styles.ts @@ -1,7 +1,7 @@ export const TITLE_STYLES = { TEXT_Y: 30, FONT_SIZE: 24 -} +} as const; export const LEGEND_STYLES = { START_Y: 80, @@ -11,12 +11,12 @@ export const LEGEND_STYLES = { FONT_SIZE: 11, WIDTH: 130, COLUMN_WIDTH: 105 -} +} as const; export const ERROR_STYLES = { TEXT_Y: 100, FONT_SIZE: 18, COLOUR: "#ff6b6b" -} +} as const; export const LEGEND_SHIFT_THRESHOLD = 8; diff --git a/src/constants/themes.js b/src/constants/themes.ts similarity index 98% rename from src/constants/themes.js rename to src/constants/themes.ts index 4cd8c2d..1653ccd 100644 --- a/src/constants/themes.js +++ b/src/constants/themes.ts @@ -65,4 +65,4 @@ export const THEMES = { "#b9f6ca" ] } -}; +} as const; diff --git a/src/constants/types.js b/src/constants/types.js deleted file mode 100644 index bc405c6..0000000 --- a/src/constants/types.js +++ /dev/null @@ -1 +0,0 @@ -export const VALID_TYPES = ["donut", "pie"]; diff --git a/src/constants/types.ts b/src/constants/types.ts new file mode 100644 index 0000000..27e49af --- /dev/null +++ b/src/constants/types.ts @@ -0,0 +1,4 @@ +export const VALID_TYPES = [ + "donut", + "pie" +] as const; From 8c054d8cdb35d45d7d75aab5a9e0bff362e34484 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Wed, 11 Feb 2026 01:03:10 -0500 Subject: [PATCH 03/17] refactor: convert src/utils to TypeScript --- src/utils/params.js | 38 ----------------------------------- src/utils/params.ts | 46 +++++++++++++++++++++++++++++++++++++++++++ src/utils/sanitize.js | 10 ---------- src/utils/sanitize.ts | 14 +++++++++++++ tsconfig.json | 5 +++-- 5 files changed, 63 insertions(+), 50 deletions(-) delete mode 100644 src/utils/params.js create mode 100644 src/utils/params.ts delete mode 100644 src/utils/sanitize.js create mode 100644 src/utils/sanitize.ts diff --git a/src/utils/params.js b/src/utils/params.js deleted file mode 100644 index d157ce8..0000000 --- a/src/utils/params.js +++ /dev/null @@ -1,38 +0,0 @@ -import { DEFAULT_CONFIG, MAX_COUNT } from "../constants/config.js"; -import { THEMES } from "../constants/themes.js"; -import { VALID_TYPES } from "../constants/types.js"; -import { sanitize } from "./sanitize.js"; - -const parseIntSafe = (val, fallback) => { - const parsed = Number.parseInt(val, 10); - return Number.isNaN(parsed) ? fallback : parsed; -} - -export function parseQueryParams(query) { - const baseTheme = THEMES[query.theme] ?? THEMES.default; - - const count = parseIntSafe(query.count, DEFAULT_CONFIG.COUNT); - - const customColours = [...baseTheme.colours]; - for (let i = 1; i <= MAX_COUNT; i++) { - if(query[`c${i}`]) { - const colour = query[`c${i}`].replace(/^#/, ''); - customColours[i-1] = `#${colour}`; - } - } - - return { - chartType: VALID_TYPES.includes(query.type) ? query.type : "donut", - chartTitle: query.hide_title === "true" ? '' : sanitize(query.title ?? DEFAULT_CONFIG.TITLE), - width: Math.max(parseIntSafe(query.width, DEFAULT_CONFIG.WIDTH), DEFAULT_CONFIG.MIN_WIDTH), - height: parseIntSafe(query.height, DEFAULT_CONFIG.HEIGHT), - count: Math.min(Math.max(count, 1), MAX_COUNT), - selectedTheme: { - bg: THEMES[query.bg]?.bg ?? query.bg ?? baseTheme.bg, - text: query.text ?? baseTheme.text, - colours: customColours - }, - stroke: query.stroke === "true", - useTestData: query.test === "true" - } -} diff --git a/src/utils/params.ts b/src/utils/params.ts new file mode 100644 index 0000000..b413830 --- /dev/null +++ b/src/utils/params.ts @@ -0,0 +1,46 @@ +import { DEFAULT_CONFIG, MAX_COUNT } from "../constants/config"; +import { THEMES } from "../constants/themes"; +import { VALID_TYPES } from "../constants/types"; +import { sanitize } from "./sanitize"; + +type QueryParams = Record; +type ChartType = (typeof VALID_TYPES)[number]; + +const parseIntSafe = ( + val: string | undefined, + fallback: number +): number => { + const parsed = Number.parseInt(val ?? '', 10); + return Number.isNaN(parsed) ? fallback : parsed; +} + +export function parseQueryParams(query: QueryParams) { + const baseTheme = THEMES[query["theme"] as keyof typeof THEMES] ?? THEMES.default; + const count = parseIntSafe(query["count"], DEFAULT_CONFIG.COUNT); + + const customColours = [...baseTheme.colours] as string[]; + for (let i = 1; i <= MAX_COUNT; i++) { + const colourVal = query[`c${i}`]; + if(colourVal) customColours[i - 1] = `#${colourVal.replace(/^#/, '')}` as string; + } + + const typeParam = query["type"] as ChartType | undefined; + const chartType: ChartType = VALID_TYPES.some(t => t === typeParam) + ? typeParam! + : "donut"; + + return { + chartType, + chartTitle: query["hide_title"] === "true" ? '' : sanitize(query["title"] ?? DEFAULT_CONFIG.TITLE), + width: Math.max(parseIntSafe(query["width"], DEFAULT_CONFIG.WIDTH), DEFAULT_CONFIG.MIN_WIDTH), + height: parseIntSafe(query["height"], DEFAULT_CONFIG.HEIGHT), + count: Math.min(Math.max(count, 1), MAX_COUNT), + selectedTheme: { + bg: THEMES[query["bg"] as keyof typeof THEMES]?.bg ?? query["bg"] ?? baseTheme.bg, + text: query["text"] ?? baseTheme.text, + colours: customColours + }, + stroke: query["stroke"] === "true", + useTestData: query["test"] === "true" + } +} diff --git a/src/utils/sanitize.js b/src/utils/sanitize.js deleted file mode 100644 index 82d12f2..0000000 --- a/src/utils/sanitize.js +++ /dev/null @@ -1,10 +0,0 @@ -export const sanitize = (str) => { - if (typeof str !== "string") return ''; - return str.replace(/[<>&"']/g, (m) => ({ - '<': '<', - '>': '>', - '&': '&', - '"': '"', - "'": ''' - }[m])); -}; diff --git a/src/utils/sanitize.ts b/src/utils/sanitize.ts new file mode 100644 index 0000000..63a93fe --- /dev/null +++ b/src/utils/sanitize.ts @@ -0,0 +1,14 @@ +const ESCAPE_MAP = { + '<': '<', + '>': '>', + '&': '&', + '"': '"', + "'": ''', +} as const; + +export const sanitize = (str: unknown): string => { + if (typeof str !== "string") return ''; + return str.replace(/[<>&"']/g, (m: string): string => + ESCAPE_MAP[m as keyof typeof ESCAPE_MAP] + ); +}; diff --git a/tsconfig.json b/tsconfig.json index 1d4d687..30420e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,9 @@ { "compilerOptions": { "target": "ES6", - "outDir": "./dist", - "allowJs": true + "module": "ES6", + "moduleResolution": "bundler", + "outDir": "./dist" }, "include": ["./src/**/*.ts"] } From a5bba6cea3692854c0fd3b26efb5f21cc2fed466 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Wed, 11 Feb 2026 01:21:31 -0500 Subject: [PATCH 04/17] refactor: convert src/charts to TypeScript --- src/charts/{donut.js => donut.ts} | 33 ++++++++++++++++--------- src/charts/{geometry.js => geometry.ts} | 27 +++++++++++++++++--- src/charts/{legend.js => legend.ts} | 13 +++++++--- src/charts/{pie.js => pie.ts} | 29 ++++++++++++++-------- src/types/index.ts | 25 +++++++++++++++++++ 5 files changed, 99 insertions(+), 28 deletions(-) rename src/charts/{donut.js => donut.ts} (58%) rename src/charts/{geometry.js => geometry.ts} (79%) rename src/charts/{legend.js => legend.ts} (80%) rename src/charts/{pie.js => pie.ts} (60%) create mode 100644 src/types/index.ts diff --git a/src/charts/donut.js b/src/charts/donut.ts similarity index 58% rename from src/charts/donut.js rename to src/charts/donut.ts index d07b48f..a602783 100644 --- a/src/charts/donut.js +++ b/src/charts/donut.ts @@ -1,12 +1,15 @@ -import { createDonutSegments } from "./geometry.js"; -import { DONUT_GEOMETRY } from "../constants/geometry.js"; -import { createLegend } from "./legend.js"; -import { - LEGEND_SHIFT_THRESHOLD, - LEGEND_STYLES -} from "../constants/styles.js"; - -function calculateDonutCenter(width, isShifted) { +import { createDonutSegments } from "./geometry"; +import { DONUT_GEOMETRY } from "../constants/geometry"; +import { createLegend } from "./legend"; +import { LEGEND_SHIFT_THRESHOLD, LEGEND_STYLES } from "../constants/styles"; +import { Language, Theme, ChartResult } from "../types"; + + + +function calculateDonutCenter( + width: number, + isShifted: boolean +): number { const legendWidth = isShifted ? LEGEND_STYLES.COLUMN_WIDTH * 2 : LEGEND_STYLES.WIDTH; @@ -15,11 +18,19 @@ function calculateDonutCenter(width, isShifted) { return availableSpace / 2; } -function calculateLegendStartX(chartCenterX, donutRadius) { +function calculateLegendStartX( + chartCenterX: number, + donutRadius: number +): number { return chartCenterX + donutRadius + DONUT_GEOMETRY.MARGIN_RIGHT; } -export function generateDonutChart(normalizedLanguages, selectedTheme, width, stroke) { +export function generateDonutChart( + normalizedLanguages: Language[], + selectedTheme: Theme, + width: number, + stroke: boolean +): ChartResult { const isShifted = normalizedLanguages.length > LEGEND_SHIFT_THRESHOLD; const chartX = calculateDonutCenter(width, isShifted); const legendStartX = calculateLegendStartX(chartX, DONUT_GEOMETRY.OUTER_RADIUS); diff --git a/src/charts/geometry.js b/src/charts/geometry.ts similarity index 79% rename from src/charts/geometry.js rename to src/charts/geometry.ts index ddb0070..8458fd3 100644 --- a/src/charts/geometry.js +++ b/src/charts/geometry.ts @@ -1,6 +1,12 @@ -import { FULL_CIRCLE_ANGLE } from "../constants/geometry.js"; +import { FULL_CIRCLE_ANGLE } from "../constants/geometry"; +import { Point, Language, Geometry } from "../types" -export const polarToCartesian = (cx, cy, r, angleDeg) => { +export const polarToCartesian = ( + cx: number, + cy: number, + r: number, + angleDeg: number +): Point => { const angleRad = (angleDeg - 90) * Math.PI / 180; return { x: cx + (r * Math.cos(angleRad)), @@ -8,7 +14,14 @@ export const polarToCartesian = (cx, cy, r, angleDeg) => { }; }; -export const describeSegment = (cx, cy, innerR, outerR, startAngle, endAngle) => { +export const describeSegment = ( + cx: number, + cy: number, + innerR: number, + outerR: number, + startAngle: number, + endAngle: number +): string => { const angleDiff = endAngle - startAngle if (angleDiff >= 360 || angleDiff <= -360) { @@ -43,7 +56,13 @@ export const describeSegment = (cx, cy, innerR, outerR, startAngle, endAngle) => `.trim(); }; -export const createDonutSegments = (languages, cx, geometry, colours, stroke) => { +export const createDonutSegments = ( + languages: Language[], + cx: number, + geometry: Geometry, + colours: string[], + stroke: boolean +): string => { let currentAngle = -0.1; return languages.map((lang, i) => { diff --git a/src/charts/legend.js b/src/charts/legend.ts similarity index 80% rename from src/charts/legend.js rename to src/charts/legend.ts index a4f3e98..35090b0 100644 --- a/src/charts/legend.js +++ b/src/charts/legend.ts @@ -1,10 +1,17 @@ -import { LEGEND_STYLES } from "../constants/styles.js"; +import { LEGEND_STYLES } from "../constants/styles"; +import { Language, Theme } from "../types"; -export function createLegend(languages, isShifted, selectedTheme, legendStartX, stroke) { +export function createLegend( + languages: Language[], + isShifted: boolean, + selectedTheme: Theme, + legendStartX: number, + stroke: boolean +): string { const numLangs = languages.length; return languages.map((lang, i) => { - let x, y; + let x: number, y: number; if (!isShifted) { x = legendStartX; diff --git a/src/charts/pie.js b/src/charts/pie.ts similarity index 60% rename from src/charts/pie.js rename to src/charts/pie.ts index 0ea4bb0..f2d6ff9 100644 --- a/src/charts/pie.js +++ b/src/charts/pie.ts @@ -1,12 +1,13 @@ -import { createDonutSegments } from "./geometry.js"; -import { DONUT_GEOMETRY } from "../constants/geometry.js"; -import { createLegend } from "./legend.js"; -import { - LEGEND_SHIFT_THRESHOLD, - LEGEND_STYLES -} from "../constants/styles.js"; +import { createDonutSegments } from "./geometry"; +import { DONUT_GEOMETRY } from "../constants/geometry"; +import { createLegend } from "./legend"; +import { LEGEND_SHIFT_THRESHOLD, LEGEND_STYLES } from "../constants/styles"; +import { Language, Theme, ChartResult } from "../types"; -function calculatePieCenter(width, isShifted) { +function calculatePieCenter( + width: number, + isShifted: boolean +): number { const legendWidth = isShifted ? LEGEND_STYLES.COLUMN_WIDTH * 2 : LEGEND_STYLES.WIDTH; @@ -15,11 +16,19 @@ function calculatePieCenter(width, isShifted) { return availableSpace / 2; } -function calculateLegendStartX(chartCenterX, pieRadius) { +function calculateLegendStartX( + chartCenterX: number, + pieRadius: number +): number { return chartCenterX + pieRadius + DONUT_GEOMETRY.MARGIN_RIGHT; } -export function generatePieChart(normalizedLanguages, selectedTheme, width, stroke) { +export function generatePieChart( + normalizedLanguages: Language[], + selectedTheme: Theme, + width: number, + stroke: boolean +): ChartResult { const isShifted = normalizedLanguages.length > LEGEND_SHIFT_THRESHOLD; const chartX = calculatePieCenter(width, isShifted); const legendStartX = calculateLegendStartX(chartX, DONUT_GEOMETRY.OUTER_RADIUS); diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..6cdb160 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,25 @@ +export type Point = { + x: number; + y: number; +}; + +export type Language = { + lang: string; + pct: number; +}; + +export type Geometry = { + CENTER_Y: number; + INNER_RADIUS: number; + OUTER_RADIUS: number; +}; + +export type ChartResult = { + segments: string; + legend: string; +}; + +export type Theme = { + colours: string[]; + text: string; +}; From cd1c70822306b136089cb7a96bc6aafab5724003 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Wed, 11 Feb 2026 01:32:47 -0500 Subject: [PATCH 05/17] refactor: convert src/render to TypeScript --- src/render/chart.js | 12 ------------ src/render/chart.ts | 24 ++++++++++++++++++++++++ src/render/{error.js => error.ts} | 12 +++++++++--- src/render/{svg.js => svg.ts} | 12 ++++++++++-- src/types/index.ts | 3 +++ 5 files changed, 46 insertions(+), 17 deletions(-) delete mode 100644 src/render/chart.js create mode 100644 src/render/chart.ts rename src/render/{error.js => error.ts} (64%) rename src/render/{svg.js => svg.ts} (69%) diff --git a/src/render/chart.js b/src/render/chart.js deleted file mode 100644 index 1710045..0000000 --- a/src/render/chart.js +++ /dev/null @@ -1,12 +0,0 @@ -import { generateDonutChart } from "../charts/donut.js"; -import { generatePieChart } from "../charts/pie.js"; - -const CHART_GENERATORS = { - donut: generateDonutChart, - pie: generatePieChart, -} - -export function generateChartData(data, theme, chartType, width, stroke) { - const generator = CHART_GENERATORS[chartType] || CHART_GENERATORS.donut; - return generator(data, theme, width, stroke); -} diff --git a/src/render/chart.ts b/src/render/chart.ts new file mode 100644 index 0000000..cae12c7 --- /dev/null +++ b/src/render/chart.ts @@ -0,0 +1,24 @@ +import { generateDonutChart } from "../charts/donut"; +import { generatePieChart } from "../charts/pie"; +import { Language, Theme, ChartType, ChartResult } from "../types"; + +const CHART_GENERATORS: Record ChartResult> = { + donut: generateDonutChart, + pie: generatePieChart, +} + +export function generateChartData( + data: Language[], + theme: Theme, + chartType: ChartType, + width: number, + stroke: boolean +): ChartResult { + const generator = CHART_GENERATORS[chartType] || CHART_GENERATORS.donut; + return generator(data, theme, width, stroke); +} diff --git a/src/render/error.js b/src/render/error.ts similarity index 64% rename from src/render/error.js rename to src/render/error.ts index d678712..b88e3ba 100644 --- a/src/render/error.js +++ b/src/render/error.ts @@ -1,7 +1,13 @@ -import { THEMES } from "../constants/themes.js"; -import { ERROR_STYLES } from "../constants/styles.js" +import { THEMES } from "../constants/themes"; +import { ERROR_STYLES } from "../constants/styles" +import { Theme } from "../types"; -export function renderError(message, width, height, selectedTheme){ +export function renderError( + message: string, + width: number, + height: number, + selectedTheme: Theme +): string { const background = selectedTheme?.bg || THEMES.default.bg; return ` diff --git a/src/render/svg.js b/src/render/svg.ts similarity index 69% rename from src/render/svg.js rename to src/render/svg.ts index 61566a9..10355ff 100644 --- a/src/render/svg.js +++ b/src/render/svg.ts @@ -1,6 +1,14 @@ -import { TITLE_STYLES } from "../constants/styles.js" +import { TITLE_STYLES } from "../constants/styles" -export function renderSvg(width, height, background, segments, legend, title, textColour) { +export function renderSvg( + width: number, + height: number, + background: string, + segments: string, + legend: string, + title: string, + textColour: string +): string { const titleElement = title ? ` Date: Thu, 26 Feb 2026 13:06:03 -0500 Subject: [PATCH 06/17] refactor: convert src/api/github to TypeScript --- package-lock.json | 18 ++++++++++++++++++ package.json | 1 + src/api/{github.js => github.ts} | 19 ++++++++++++------- tsconfig.json | 4 ++-- 4 files changed, 33 insertions(+), 9 deletions(-) rename src/api/{github.js => github.ts} (80%) diff --git a/package-lock.json b/package-lock.json index 9b954ee..5cdc7dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "license": "MIT", "devDependencies": { + "@types/node": "^25.3.1", "@vitest/coverage-v8": "^4.0.17", "vitest": "^4.0.17" } @@ -925,6 +926,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.3.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.1.tgz", + "integrity": "sha512-hj9YIJimBCipHVfHKRMnvmHg+wfhKc0o4mTtXh9pKBjC8TLJzz0nzGmLi5UJsYAUgSvXFHgb0V2oY10DUFtImw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, "node_modules/@vitest/coverage-v8": { "version": "4.0.17", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.17.tgz", @@ -1534,6 +1545,13 @@ "node": ">=14.0.0" } }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", diff --git a/package.json b/package.json index 9693cf9..5f0ce93 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "license": "MIT", "type": "module", "devDependencies": { + "@types/node": "^25.3.1", "@vitest/coverage-v8": "^4.0.17", "vitest": "^4.0.17" } diff --git a/src/api/github.js b/src/api/github.ts similarity index 80% rename from src/api/github.js rename to src/api/github.ts index 00e8033..6a0b890 100644 --- a/src/api/github.js +++ b/src/api/github.ts @@ -1,9 +1,14 @@ -import { REFRESH_INTERVAL } from "../constants/config.js"; +import { REFRESH_INTERVAL } from "../constants/config"; +import { Language } from "../types"; -let cachedLanguageData = null; +type LanguageBytes = Record; + +let cachedLanguageData: LanguageBytes | null = null; let lastRefresh = 0; -export async function fetchLanguageData(useTestData = false) { +export async function fetchLanguageData( + useTestData = false +): Promise { if (useTestData) { const testData = await import ("../data/test-data.json", { with: { type: "json" } }); return testData.default; @@ -40,9 +45,9 @@ export async function fetchLanguageData(useTestData = false) { .then(r => r.ok ? r.json() : {}) ); - const langResults = await Promise.all(languageFetches); + const langResults: LanguageBytes[] = await Promise.all(languageFetches); - cachedLanguageData = langResults.reduce((acc, languages) => { + cachedLanguageData = langResults.reduce((acc, languages) => { for (const [lang, bytes] of Object.entries(languages)) { acc[lang] = (acc[lang] || 0) + bytes; } @@ -53,7 +58,7 @@ export async function fetchLanguageData(useTestData = false) { return cachedLanguageData; } -export function processLanguageData(languageBytes, count){ +export function processLanguageData(languageBytes: LanguageBytes, count: number): Language[] { if(Object.keys(languageBytes).length === 0) throw new Error("No language data available"); @@ -72,7 +77,7 @@ export function processLanguageData(languageBytes, count){ })); } -export function resetCache() { +export function resetCache(): void { cachedLanguageData = null; lastRefresh = 0; } diff --git a/tsconfig.json b/tsconfig.json index 30420e4..92f2ecd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "target": "ES6", - "module": "ES6", + "target": "ESNext", + "module": "ESNext", "moduleResolution": "bundler", "outDir": "./dist" }, From ebe856f332eb36edc9dc1a852da55076c5a94e60 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Thu, 26 Feb 2026 13:12:21 -0500 Subject: [PATCH 07/17] chore: enable strict compiler options --- src/api/github.ts | 8 ++++---- src/charts/donut.ts | 2 +- src/charts/geometry.ts | 2 +- src/charts/legend.ts | 2 +- src/charts/pie.ts | 2 +- src/render/chart.ts | 2 +- src/render/error.ts | 2 +- tsconfig.json | 41 +++++++++++++++++++++++++++++++++++++++-- 8 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/api/github.ts b/src/api/github.ts index 6a0b890..4e58bc5 100644 --- a/src/api/github.ts +++ b/src/api/github.ts @@ -1,5 +1,5 @@ import { REFRESH_INTERVAL } from "../constants/config"; -import { Language } from "../types"; +import type { Language } from "../types"; type LanguageBytes = Record; @@ -18,8 +18,8 @@ export async function fetchLanguageData( if (cachedLanguageData && now - lastRefresh < REFRESH_INTERVAL) return cachedLanguageData; - const usernames = process.env.GITHUB_USERNAMES?.split(',').map(u => u.trim()).filter(Boolean) || []; - const orgs = process.env.GITHUB_ORGS?.split(',').map(o => o.trim()).filter(Boolean) || []; + const usernames = process.env["GITHUB_USERNAMES"]?.split(',').map(u => u.trim()).filter(Boolean) || []; + const orgs = process.env["GITHUB_ORGS"]?.split(',').map(o => o.trim()).filter(Boolean) || []; if(usernames.length === 0 && orgs.length === 0) throw new Error("At least one of GITHUB_USERNAMES or GITHUB_ORGS must be set"); @@ -37,7 +37,7 @@ export async function fetchLanguageData( const repoArrays = await Promise.all(responses.map(r => r.json())); const repos = repoArrays.flat(); - const ignored = process.env.IGNORED_REPOS?.split(',').map(name => name.trim()) || []; + const ignored = process.env["IGNORED_REPOS"]?.split(',').map(name => name.trim()) || []; const filteredRepos = repos.filter(repo => !repo.fork && !ignored.includes(repo.name)); const languageFetches = filteredRepos.map(repo => diff --git a/src/charts/donut.ts b/src/charts/donut.ts index a602783..55837e5 100644 --- a/src/charts/donut.ts +++ b/src/charts/donut.ts @@ -2,7 +2,7 @@ import { createDonutSegments } from "./geometry"; import { DONUT_GEOMETRY } from "../constants/geometry"; import { createLegend } from "./legend"; import { LEGEND_SHIFT_THRESHOLD, LEGEND_STYLES } from "../constants/styles"; -import { Language, Theme, ChartResult } from "../types"; +import type { Language, Theme, ChartResult } from "../types"; diff --git a/src/charts/geometry.ts b/src/charts/geometry.ts index 8458fd3..8c8e09f 100644 --- a/src/charts/geometry.ts +++ b/src/charts/geometry.ts @@ -1,5 +1,5 @@ import { FULL_CIRCLE_ANGLE } from "../constants/geometry"; -import { Point, Language, Geometry } from "../types" +import type { Point, Language, Geometry } from "../types" export const polarToCartesian = ( cx: number, diff --git a/src/charts/legend.ts b/src/charts/legend.ts index 35090b0..8fea1ab 100644 --- a/src/charts/legend.ts +++ b/src/charts/legend.ts @@ -1,5 +1,5 @@ import { LEGEND_STYLES } from "../constants/styles"; -import { Language, Theme } from "../types"; +import type { Language, Theme } from "../types"; export function createLegend( languages: Language[], diff --git a/src/charts/pie.ts b/src/charts/pie.ts index f2d6ff9..3b58fb9 100644 --- a/src/charts/pie.ts +++ b/src/charts/pie.ts @@ -2,7 +2,7 @@ import { createDonutSegments } from "./geometry"; import { DONUT_GEOMETRY } from "../constants/geometry"; import { createLegend } from "./legend"; import { LEGEND_SHIFT_THRESHOLD, LEGEND_STYLES } from "../constants/styles"; -import { Language, Theme, ChartResult } from "../types"; +import type { Language, Theme, ChartResult } from "../types"; function calculatePieCenter( width: number, diff --git a/src/render/chart.ts b/src/render/chart.ts index cae12c7..e8b6a09 100644 --- a/src/render/chart.ts +++ b/src/render/chart.ts @@ -1,6 +1,6 @@ import { generateDonutChart } from "../charts/donut"; import { generatePieChart } from "../charts/pie"; -import { Language, Theme, ChartType, ChartResult } from "../types"; +import type { Language, Theme, ChartType, ChartResult } from "../types"; const CHART_GENERATORS: Record Date: Thu, 26 Feb 2026 13:19:42 -0500 Subject: [PATCH 08/17] refactor: convert api/languages/index to TypeScript --- api/languages/{index.js => index.ts} | 20 +- package-lock.json | 2210 ++++++++++++++++++++++++-- package.json | 1 + src/utils/params.ts | 4 +- 4 files changed, 2088 insertions(+), 147 deletions(-) rename api/languages/{index.js => index.ts} (58%) diff --git a/api/languages/index.js b/api/languages/index.ts similarity index 58% rename from api/languages/index.js rename to api/languages/index.ts index 112db53..573a5ca 100644 --- a/api/languages/index.js +++ b/api/languages/index.ts @@ -1,11 +1,15 @@ -import { parseQueryParams } from "../../src/utils/params.js"; -import { fetchLanguageData, processLanguageData } from "../../src/api/github.js"; -import { generateChartData } from "../../src/render/chart.js"; -import { renderSvg } from "../../src/render/svg.js"; -import { renderError } from "../../src/render/error.js"; +import { parseQueryParams, type QueryParams } from "../../src/utils/params"; +import { fetchLanguageData, processLanguageData } from "../../src/api/github"; +import { generateChartData } from "../../src/render/chart"; +import { renderSvg } from "../../src/render/svg"; +import { renderError } from "../../src/render/error"; +import type { VercelRequest, VercelResponse } from "@vercel/node"; -export default async function handler(req, res) { - const params = parseQueryParams(req.query); +export default async function handler( + req: VercelRequest, + res: VercelResponse +): Promise { + const params = parseQueryParams(req.query as QueryParams); const { chartType, chartTitle, width, height, count, selectedTheme, stroke, useTestData } = params; try { @@ -19,7 +23,7 @@ export default async function handler(req, res) { res.setHeader("Cache-Control", "public, max-age=3600, s-maxage=3600, stale-while-revalidate=60"); res.status(200).send(svg); } catch (error) { - const errorSvg = renderError(error.message, width, height, selectedTheme); + const errorSvg = renderError((error as Error).message, width, height, selectedTheme); res.setHeader("Content-Type", "image/svg+xml"); res.status(500).send(errorSvg); } diff --git a/package-lock.json b/package-lock.json index 5cdc7dc..ac3e6f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "devDependencies": { "@types/node": "^25.3.1", + "@vercel/node": "^5.6.7", "@vitest/coverage-v8": "^4.0.17", "vitest": "^4.0.17" } @@ -74,6 +75,66 @@ "node": ">=18" } }, + "node_modules/@bytecodealliance/preview2-shim": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.17.6.tgz", + "integrity": "sha512-n3cM88gTen5980UOBAD6xDcNNL3ocTK8keab21bpx1ONdA+ARj7uD1qoFxOWCyKlkpSi195FH+GeAut7Oc6zZw==", + "dev": true, + "license": "(Apache-2.0 WITH LLVM-exception)" + }, + "node_modules/@edge-runtime/format": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@edge-runtime/format/-/format-2.2.1.tgz", + "integrity": "sha512-JQTRVuiusQLNNLe2W9tnzBlV/GvSVcozLl4XZHk5swnRZ/v6jp8TqR8P7sqmJsQqblDZ3EztcWmLDbhRje/+8g==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=16" + } + }, + "node_modules/@edge-runtime/node-utils": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@edge-runtime/node-utils/-/node-utils-2.3.0.tgz", + "integrity": "sha512-uUtx8BFoO1hNxtHjp3eqVPC/mWImGb2exOfGjMLUoipuWgjej+f4o/VP4bUI8U40gu7Teogd5VTeZUkGvJSPOQ==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=16" + } + }, + "node_modules/@edge-runtime/ponyfill": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@edge-runtime/ponyfill/-/ponyfill-2.4.2.tgz", + "integrity": "sha512-oN17GjFr69chu6sDLvXxdhg0Qe8EZviGSuqzR9qOiKh4MhFYGdBBcqRNzdmYeAdeRzOW2mM9yil4RftUQ7sUOA==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=16" + } + }, + "node_modules/@edge-runtime/primitives": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@edge-runtime/primitives/-/primitives-4.1.0.tgz", + "integrity": "sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=16" + } + }, + "node_modules/@edge-runtime/vm": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@edge-runtime/vm/-/vm-3.2.0.tgz", + "integrity": "sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "@edge-runtime/primitives": "4.1.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", @@ -516,6 +577,52 @@ "node": ">=18" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -544,6 +651,107 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.3.tgz", + "integrity": "sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "consola": "^3.2.3", + "detect-libc": "^2.0.0", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^2.6.7", + "nopt": "^8.0.0", + "semver": "^7.5.3", + "tar": "^7.4.0" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@renovatebot/pep440": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@renovatebot/pep440/-/pep440-4.2.1.tgz", + "integrity": "sha512-2FK1hF93Fuf1laSdfiEmJvSJPVIDHEUTz68D3Fi9s0IZrrpaEcj6pTFBTbYvsgC5du4ogrtf5re7yMMvrKNgkw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.9.0 || ^22.11.0 || ^24", + "pnpm": "^10.0.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.55.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", @@ -901,6 +1109,32 @@ "dev": true, "license": "MIT" }, + "node_modules/@ts-morph/common": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.11.1.tgz", + "integrity": "sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.7", + "minimatch": "^3.0.4", + "mkdirp": "^1.0.4", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -926,6 +1160,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.3.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.1.tgz", @@ -936,141 +1177,760 @@ "undici-types": "~7.18.0" } }, - "node_modules/@vitest/coverage-v8": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.17.tgz", - "integrity": "sha512-/6zU2FLGg0jsd+ePZcwHRy3+WpNTBBhDY56P4JTRqUN/Dp6CvOEa9HrikcQ4KfV2b2kAHUFB4dl1SuocWXSFEw==", + "node_modules/@vercel/build-utils": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-13.5.0.tgz", + "integrity": "sha512-hDSJbpw7KFlX5d/L7e6x5pDfln7puE6EPiy4y+19zE0yBTNuO/d8tmpMPamt6uzzIKTT8tuAdef+QDVzGshaOw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.17", - "ast-v8-to-istanbul": "^0.3.10", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.1", - "obug": "^2.1.1", - "std-env": "^3.10.0", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "4.0.17", - "vitest": "4.0.17" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } + "@vercel/python-analysis": "0.7.0" } }, - "node_modules/@vitest/expect": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.17.tgz", - "integrity": "sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==", + "node_modules/@vercel/error-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vercel/error-utils/-/error-utils-2.0.3.tgz", + "integrity": "sha512-CqC01WZxbLUxoiVdh9B/poPbNpY9U+tO1N9oWHwTl5YAZxcqXmmWJ8KNMFItJCUUWdY3J3xv8LvAuQv2KZ5YdQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.17", - "@vitest/utils": "4.0.17", - "chai": "^6.2.1", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } + "license": "Apache-2.0" }, - "node_modules/@vitest/mocker": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.17.tgz", - "integrity": "sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ==", + "node_modules/@vercel/nft": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.1.1.tgz", + "integrity": "sha512-mKMGa7CEUcXU75474kOeqHbtvK1kAcu4wiahhmlUenB5JbTQB8wVlDI8CyHR3rpGo0qlzoRWqcDzI41FUoBJCA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.17", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" + "@mapbox/node-pre-gyp": "^2.0.0", + "@rollup/pluginutils": "^5.1.3", + "acorn": "^8.6.0", + "acorn-import-attributes": "^1.9.5", + "async-sema": "^3.1.1", + "bindings": "^1.4.0", + "estree-walker": "2.0.2", + "glob": "^13.0.0", + "graceful-fs": "^4.2.9", + "node-gyp-build": "^4.2.2", + "picomatch": "^4.0.2", + "resolve-from": "^5.0.0" }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" + "bin": { + "nft": "out/cli.js" }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "engines": { + "node": ">=20" } }, - "node_modules/@vitest/pretty-format": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.17.tgz", - "integrity": "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==", + "node_modules/@vercel/nft/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } + "license": "MIT" }, - "node_modules/@vitest/runner": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.17.tgz", - "integrity": "sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ==", + "node_modules/@vercel/node": { + "version": "5.6.7", + "resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.6.7.tgz", + "integrity": "sha512-a6T/59XUX2uQ5e5Z9aw8svTmaqxR54omUS88Ixfi50N40artnU1Olk9uQExf/9c5STNf8ewriQih1ep0PXx74g==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@vitest/utils": "4.0.17", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.17.tgz", - "integrity": "sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ==", + "@edge-runtime/node-utils": "2.3.0", + "@edge-runtime/primitives": "4.1.0", + "@edge-runtime/vm": "3.2.0", + "@types/node": "20.11.0", + "@vercel/build-utils": "13.5.0", + "@vercel/error-utils": "2.0.3", + "@vercel/nft": "1.1.1", + "@vercel/static-config": "3.1.2", + "async-listen": "3.0.0", + "cjs-module-lexer": "1.2.3", + "edge-runtime": "2.5.9", + "es-module-lexer": "1.4.1", + "esbuild": "0.27.0", + "etag": "1.8.1", + "mime-types": "2.1.35", + "node-fetch": "2.6.9", + "path-to-regexp": "6.1.0", + "path-to-regexp-updated": "npm:path-to-regexp@6.3.0", + "ts-morph": "12.0.0", + "tsx": "4.21.0", + "typescript": "npm:typescript@5.9.3", + "undici": "5.28.4" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", + "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.17", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@vitest/spy": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.17.tgz", - "integrity": "sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew==", + "node_modules/@vercel/node/node_modules/@esbuild/android-arm": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz", + "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@vitest/utils": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.17.tgz", - "integrity": "sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==", + "node_modules/@vercel/node/node_modules/@esbuild/android-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz", + "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/android-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz", + "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz", + "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/darwin-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz", + "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz", + "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz", + "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/linux-arm": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz", + "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/linux-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz", + "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/linux-ia32": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz", + "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/linux-loong64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz", + "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz", + "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz", + "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz", + "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/linux-s390x": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz", + "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/linux-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz", + "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz", + "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz", + "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz", + "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz", + "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz", + "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/sunos-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz", + "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/win32-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz", + "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/win32-ia32": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz", + "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@esbuild/win32-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz", + "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@types/node": { + "version": "20.11.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz", + "integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@vercel/node/node_modules/es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vercel/node/node_modules/esbuild": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", + "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.0", + "@esbuild/android-arm": "0.27.0", + "@esbuild/android-arm64": "0.27.0", + "@esbuild/android-x64": "0.27.0", + "@esbuild/darwin-arm64": "0.27.0", + "@esbuild/darwin-x64": "0.27.0", + "@esbuild/freebsd-arm64": "0.27.0", + "@esbuild/freebsd-x64": "0.27.0", + "@esbuild/linux-arm": "0.27.0", + "@esbuild/linux-arm64": "0.27.0", + "@esbuild/linux-ia32": "0.27.0", + "@esbuild/linux-loong64": "0.27.0", + "@esbuild/linux-mips64el": "0.27.0", + "@esbuild/linux-ppc64": "0.27.0", + "@esbuild/linux-riscv64": "0.27.0", + "@esbuild/linux-s390x": "0.27.0", + "@esbuild/linux-x64": "0.27.0", + "@esbuild/netbsd-arm64": "0.27.0", + "@esbuild/netbsd-x64": "0.27.0", + "@esbuild/openbsd-arm64": "0.27.0", + "@esbuild/openbsd-x64": "0.27.0", + "@esbuild/openharmony-arm64": "0.27.0", + "@esbuild/sunos-x64": "0.27.0", + "@esbuild/win32-arm64": "0.27.0", + "@esbuild/win32-ia32": "0.27.0", + "@esbuild/win32-x64": "0.27.0" + } + }, + "node_modules/@vercel/node/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vercel/python-analysis": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@vercel/python-analysis/-/python-analysis-0.7.0.tgz", + "integrity": "sha512-0l5ITyZd8V6etELQd4Xmblc2rou5Jlj6hzFHZ+Dgo7/i7HvcWvZ+thZ5IaPdiXQNYo2L8CvUAsGvybkstuFc8w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@bytecodealliance/preview2-shim": "0.17.6", + "@renovatebot/pep440": "4.2.1", + "fs-extra": "11.1.1", + "js-yaml": "4.1.1", + "minimatch": "10.1.1", + "pip-requirements-js": "1.0.2", + "smol-toml": "1.5.2", + "zod": "3.22.4" + } + }, + "node_modules/@vercel/static-config": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vercel/static-config/-/static-config-3.1.2.tgz", + "integrity": "sha512-2d+TXr6K30w86a+WbMbGm2W91O0UzO5VeemZYBBUJbCjk/5FLLGIi8aV6RS2+WmaRvtcqNTn2pUA7nCOK3bGcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ajv": "8.6.3", + "json-schema-to-ts": "1.6.4", + "ts-morph": "12.0.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.17.tgz", + "integrity": "sha512-/6zU2FLGg0jsd+ePZcwHRy3+WpNTBBhDY56P4JTRqUN/Dp6CvOEa9HrikcQ4KfV2b2kAHUFB4dl1SuocWXSFEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.17", + "ast-v8-to-istanbul": "^0.3.10", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.17", + "vitest": "4.0.17" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.17.tgz", + "integrity": "sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.17", + "@vitest/utils": "4.0.17", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.17.tgz", + "integrity": "sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.17", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.17.tgz", + "integrity": "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.17.tgz", + "integrity": "sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.17.tgz", + "integrity": "sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.17", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.17.tgz", + "integrity": "sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.17.tgz", + "integrity": "sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==", + "dev": true, + "license": "MIT", + "dependencies": { "@vitest/pretty-format": "4.0.17", "tinyrainbow": "^3.0.3" }, @@ -1078,38 +1938,283 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", + "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, + "node_modules/async-listen": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-3.0.0.tgz", + "integrity": "sha512-V+SsTpDqkrWTimiotsyl33ePSjA5/KrithwupuvJ6ztsqPvGv6ge4OredFhPffVXiLN/QUWvE0XcqJaYgt6fOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/async-sema": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz", + "integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/code-block-writer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-10.1.1.tgz", + "integrity": "sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/convert-hrtime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-3.0.0.tgz", + "integrity": "sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/ast-v8-to-istanbul": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", - "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^9.0.1" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/edge-runtime": { + "version": "2.5.9", + "resolved": "https://registry.npmjs.org/edge-runtime/-/edge-runtime-2.5.9.tgz", + "integrity": "sha512-pk+k0oK0PVXdlT4oRp4lwh+unuKB7Ng4iZ2HB+EZ7QCEQizX360Rp/F4aRpgpRgdP2ufB35N+1KppHmYjqIGSg==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "@edge-runtime/format": "2.2.1", + "@edge-runtime/ponyfill": "2.4.2", + "@edge-runtime/vm": "3.2.0", + "async-listen": "3.0.1", + "mri": "1.2.0", + "picocolors": "1.0.0", + "pretty-ms": "7.0.1", + "signal-exit": "4.0.2", + "time-span": "4.0.0" + }, + "bin": { + "edge-runtime": "dist/cli/index.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/edge-runtime/node_modules/async-listen": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-3.0.1.tgz", + "integrity": "sha512-cWMaNwUJnf37C/S5TfCkk/15MwbPRwVYALA2jtjkbHjCmAPiDXyNJy2q3p1KAZzDLHAWyarUWSujUoHR4pEgrA==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 14" } }, + "node_modules/edge-runtime/node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true, + "license": "ISC" + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -1169,6 +2274,16 @@ "@types/estree": "^1.0.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -1179,6 +2294,40 @@ "node": ">=12.0.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -1197,6 +2346,41 @@ } } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1212,6 +2396,96 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1229,6 +2503,53 @@ "dev": true, "license": "MIT" }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -1275,6 +2596,60 @@ "dev": true, "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-to-ts": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-1.6.4.tgz", + "integrity": "sha512-pR4yQ9DHz6itqswtHCm26mw45FSNfQ9rEQjosaZErhn5J3J2sIViQiz8rDaezjKAhFGpmsoczYVBgGHzFw/stA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.6", + "ts-toolbelt": "^6.15.5" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -1292,27 +2667,156 @@ "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "source-map-js": "^1.2.1" + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" } }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, "license": "MIT", - "dependencies": { - "semver": "^7.5.3" + "bin": { + "mkdirp": "bin/cmd.js" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1332,6 +2836,55 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "dev": true, + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/obug": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", @@ -1343,6 +2896,65 @@ ], "license": "MIT" }, + "node_modules/ohm-js": { + "version": "17.5.0", + "resolved": "https://registry.npmjs.org/ohm-js/-/ohm-js-17.5.0.tgz", + "integrity": "sha512-l4Sa7026+6jsvYbt0PXKmL+f+ML32fD++IznLgxDhx2t9Cx6NC7zwRqblCujPHGGmkQerHoeBzRutdxaw/S72g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.1" + } + }, + "node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.1.0.tgz", + "integrity": "sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp-updated": { + "name": "path-to-regexp", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -1370,6 +2982,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pip-requirements-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pip-requirements-js/-/pip-requirements-js-1.0.2.tgz", + "integrity": "sha512-awqoNOSOl4Blu4E4Hzp7jL0g8WKEhCwO+s7C2ibtIW3CAJMwspgoTXd4vnHd21UmhdrsI44Pn8FFSuA8QKrzvg==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "ohm-js": "^17.1.0" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -1399,6 +3021,94 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "4.55.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", @@ -1444,6 +3154,30 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -1464,6 +3198,32 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/smol-toml": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.5.2.tgz", + "integrity": "sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1501,6 +3261,39 @@ "node": ">=8" } }, + "node_modules/tar": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz", + "integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/time-span": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-4.0.0.tgz", + "integrity": "sha512-MyqZCTGLDZ77u4k+jqg4UlrzPTPZ49NDlaekU6uuFaJLzPIN1woaRXCbGeqOfxwc3Y37ZROGAJ614Rdv7Olt+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-hrtime": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -1545,6 +3338,91 @@ "node": ">=14.0.0" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ts-morph": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-12.0.0.tgz", + "integrity": "sha512-VHC8XgU2fFW7yO1f/b3mxKDje1vmyzFXHWzOYmKEkCEwcLjDtbdLgBQviqj4ZwP4MJkQtRo6Ha2I29lq/B+VxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.11.0", + "code-block-writer": "^10.1.1" + } + }, + "node_modules/ts-toolbelt": { + "version": "6.15.5", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz", + "integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/undici-types": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", @@ -1552,6 +3430,26 @@ "dev": true, "license": "MIT" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", @@ -1705,6 +3603,24 @@ } } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -1721,6 +3637,26 @@ "engines": { "node": ">=8" } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 5f0ce93..53113dc 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "type": "module", "devDependencies": { "@types/node": "^25.3.1", + "@vercel/node": "^5.6.7", "@vitest/coverage-v8": "^4.0.17", "vitest": "^4.0.17" } diff --git a/src/utils/params.ts b/src/utils/params.ts index b413830..da2bad1 100644 --- a/src/utils/params.ts +++ b/src/utils/params.ts @@ -2,9 +2,9 @@ import { DEFAULT_CONFIG, MAX_COUNT } from "../constants/config"; import { THEMES } from "../constants/themes"; import { VALID_TYPES } from "../constants/types"; import { sanitize } from "./sanitize"; +import type { ChartType } from "../types"; -type QueryParams = Record; -type ChartType = (typeof VALID_TYPES)[number]; +export type QueryParams = Record; const parseIntSafe = ( val: string | undefined, From ea54279a949ea2316ae26e1377fb1743c5756278 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Thu, 26 Feb 2026 13:28:05 -0500 Subject: [PATCH 09/17] fix: add .js extensions for runtime --- api/languages/index.ts | 10 +++++----- src/api/github.ts | 4 ++-- src/charts/donut.ts | 12 +++++------- src/charts/geometry.ts | 4 ++-- src/charts/legend.ts | 4 ++-- src/charts/pie.ts | 10 +++++----- src/render/chart.ts | 6 +++--- src/render/error.ts | 6 +++--- src/render/svg.ts | 2 +- src/{types/index.ts => types.ts} | 0 src/utils/params.ts | 10 +++++----- 11 files changed, 33 insertions(+), 35 deletions(-) rename src/{types/index.ts => types.ts} (100%) diff --git a/api/languages/index.ts b/api/languages/index.ts index 573a5ca..9a13327 100644 --- a/api/languages/index.ts +++ b/api/languages/index.ts @@ -1,8 +1,8 @@ -import { parseQueryParams, type QueryParams } from "../../src/utils/params"; -import { fetchLanguageData, processLanguageData } from "../../src/api/github"; -import { generateChartData } from "../../src/render/chart"; -import { renderSvg } from "../../src/render/svg"; -import { renderError } from "../../src/render/error"; +import { parseQueryParams, type QueryParams } from "../../src/utils/params.js"; +import { fetchLanguageData, processLanguageData } from "../../src/api/github.js"; +import { generateChartData } from "../../src/render/chart.js"; +import { renderSvg } from "../../src/render/svg.js"; +import { renderError } from "../../src/render/error.js"; import type { VercelRequest, VercelResponse } from "@vercel/node"; export default async function handler( diff --git a/src/api/github.ts b/src/api/github.ts index 4e58bc5..3cdd9d4 100644 --- a/src/api/github.ts +++ b/src/api/github.ts @@ -1,5 +1,5 @@ -import { REFRESH_INTERVAL } from "../constants/config"; -import type { Language } from "../types"; +import { REFRESH_INTERVAL } from "../constants/config.js"; +import type { Language } from "../types.js"; type LanguageBytes = Record; diff --git a/src/charts/donut.ts b/src/charts/donut.ts index 55837e5..a3b5db4 100644 --- a/src/charts/donut.ts +++ b/src/charts/donut.ts @@ -1,10 +1,8 @@ -import { createDonutSegments } from "./geometry"; -import { DONUT_GEOMETRY } from "../constants/geometry"; -import { createLegend } from "./legend"; -import { LEGEND_SHIFT_THRESHOLD, LEGEND_STYLES } from "../constants/styles"; -import type { Language, Theme, ChartResult } from "../types"; - - +import { createDonutSegments } from "./geometry.js"; +import { DONUT_GEOMETRY } from "../constants/geometry.js"; +import { createLegend } from "./legend.js"; +import { LEGEND_SHIFT_THRESHOLD, LEGEND_STYLES } from "../constants/styles.js"; +import type { Language, Theme, ChartResult } from "../types.js"; function calculateDonutCenter( width: number, diff --git a/src/charts/geometry.ts b/src/charts/geometry.ts index 8c8e09f..967576c 100644 --- a/src/charts/geometry.ts +++ b/src/charts/geometry.ts @@ -1,5 +1,5 @@ -import { FULL_CIRCLE_ANGLE } from "../constants/geometry"; -import type { Point, Language, Geometry } from "../types" +import { FULL_CIRCLE_ANGLE } from "../constants/geometry.js"; +import type { Point, Language, Geometry } from "../types.js" export const polarToCartesian = ( cx: number, diff --git a/src/charts/legend.ts b/src/charts/legend.ts index 8fea1ab..af5f757 100644 --- a/src/charts/legend.ts +++ b/src/charts/legend.ts @@ -1,5 +1,5 @@ -import { LEGEND_STYLES } from "../constants/styles"; -import type { Language, Theme } from "../types"; +import { LEGEND_STYLES } from "../constants/styles.js"; +import type { Language, Theme } from "../types.js"; export function createLegend( languages: Language[], diff --git a/src/charts/pie.ts b/src/charts/pie.ts index 3b58fb9..c30b334 100644 --- a/src/charts/pie.ts +++ b/src/charts/pie.ts @@ -1,8 +1,8 @@ -import { createDonutSegments } from "./geometry"; -import { DONUT_GEOMETRY } from "../constants/geometry"; -import { createLegend } from "./legend"; -import { LEGEND_SHIFT_THRESHOLD, LEGEND_STYLES } from "../constants/styles"; -import type { Language, Theme, ChartResult } from "../types"; +import { createDonutSegments } from "./geometry.js"; +import { DONUT_GEOMETRY } from "../constants/geometry.js"; +import { createLegend } from "./legend.js"; +import { LEGEND_SHIFT_THRESHOLD, LEGEND_STYLES } from "../constants/styles.js"; +import type { Language, Theme, ChartResult } from "../types.js"; function calculatePieCenter( width: number, diff --git a/src/render/chart.ts b/src/render/chart.ts index e8b6a09..ce01855 100644 --- a/src/render/chart.ts +++ b/src/render/chart.ts @@ -1,6 +1,6 @@ -import { generateDonutChart } from "../charts/donut"; -import { generatePieChart } from "../charts/pie"; -import type { Language, Theme, ChartType, ChartResult } from "../types"; +import { generateDonutChart } from "../charts/donut.js"; +import { generatePieChart } from "../charts/pie.js"; +import type { Language, Theme, ChartType, ChartResult } from "../types.js"; const CHART_GENERATORS: Record; From ba468cd50954a91f80ed827d962c434655190aa1 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Thu, 26 Feb 2026 13:31:35 -0500 Subject: [PATCH 10/17] refactor: convert tests/utils/ to TypeScript --- tests/utils/{params.test.js => params.test.ts} | 6 +++--- tests/utils/{sanitize.test.js => sanitize.test.ts} | 0 tsconfig.json | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) rename tests/utils/{params.test.js => params.test.ts} (95%) rename tests/utils/{sanitize.test.js => sanitize.test.ts} (100%) diff --git a/tests/utils/params.test.js b/tests/utils/params.test.ts similarity index 95% rename from tests/utils/params.test.js rename to tests/utils/params.test.ts index eb337fb..395e799 100644 --- a/tests/utils/params.test.js +++ b/tests/utils/params.test.ts @@ -58,13 +58,13 @@ describe("parseQueryParams", () => { expect(params.selectedTheme.text).toBe("#111111"); expect(params.selectedTheme.bg).toBe("#ffffff"); - expect(params.selectedTheme.colours).toEqual(THEMES[themeKey].colours); + expect(params.selectedTheme.colours).toEqual(THEMES[themeKey as keyof typeof THEMES]!.colours); }); it("if bg matches a theme name, use that theme's bg", () => { - const bgThemeKey = Object.keys(THEMES)[0]; + const bgThemeKey = Object.keys(THEMES)[0]!; const params = parseQueryParams({ bg: bgThemeKey }); - expect(params.selectedTheme.bg).toBe(THEMES[bgThemeKey].bg); + expect(params.selectedTheme.bg).toBe(THEMES[bgThemeKey as keyof typeof THEMES]!.bg); }); it("overrides colours via c1..cMAX_COUNT", () => { diff --git a/tests/utils/sanitize.test.js b/tests/utils/sanitize.test.ts similarity index 100% rename from tests/utils/sanitize.test.js rename to tests/utils/sanitize.test.ts diff --git a/tsconfig.json b/tsconfig.json index 877aa40..7ef1f04 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -35,6 +35,7 @@ }, "include": [ "./src/**/*.ts", + "./tests/**/*.ts", "./api/languages/**/*.ts" ], "exclude": [ From fbc442e8e6ce4510bc299f51b064e4c2c2571ef6 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Thu, 26 Feb 2026 13:50:59 -0500 Subject: [PATCH 11/17] refactor: convert tests/render to TypeScript --- src/charts/donut.ts | 2 +- src/charts/pie.ts | 2 +- src/render/error.ts | 2 +- src/render/svg.ts | 2 +- src/types.ts | 6 ++--- tests/charts/donut.test.js | 2 +- tests/render/{chart.test.js => chart.test.ts} | 23 ++++++++++++++----- tests/render/{error.test.js => error.test.ts} | 3 ++- tests/render/{svg.test.js => svg.test.ts} | 0 9 files changed, 27 insertions(+), 15 deletions(-) rename tests/render/{chart.test.js => chart.test.ts} (64%) rename tests/render/{error.test.js => error.test.ts} (89%) rename tests/render/{svg.test.js => svg.test.ts} (100%) diff --git a/src/charts/donut.ts b/src/charts/donut.ts index a3b5db4..284bc00 100644 --- a/src/charts/donut.ts +++ b/src/charts/donut.ts @@ -38,7 +38,7 @@ export function generateDonutChart( normalizedLanguages, chartX, DONUT_GEOMETRY, - selectedTheme.colours, + [...selectedTheme.colours], useStroke ); diff --git a/src/charts/pie.ts b/src/charts/pie.ts index c30b334..26f0e3e 100644 --- a/src/charts/pie.ts +++ b/src/charts/pie.ts @@ -39,7 +39,7 @@ export function generatePieChart( normalizedLanguages, chartX, pieGeometry, - selectedTheme.colours, + [...selectedTheme.colours], useStroke ); diff --git a/src/render/error.ts b/src/render/error.ts index a5a6a11..27a14bb 100644 --- a/src/render/error.ts +++ b/src/render/error.ts @@ -6,7 +6,7 @@ export function renderError( message: string, width: number, height: number, - selectedTheme: Theme + selectedTheme?: Theme ): string { const background = selectedTheme?.bg || THEMES.default.bg; return ` diff --git a/src/render/svg.ts b/src/render/svg.ts index 99575ba..359b89b 100644 --- a/src/render/svg.ts +++ b/src/render/svg.ts @@ -6,7 +6,7 @@ export function renderSvg( background: string, segments: string, legend: string, - title: string, + title: string | null, textColour: string ): string { const titleElement = title ? ` diff --git a/src/types.ts b/src/types.ts index 0bb3ded..b810374 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,7 +22,7 @@ export type ChartResult = { }; export type Theme = { - colours: string[]; - text: string; - bg: string; + readonly colours: readonly string[]; + readonly text: string; + readonly bg: string; }; diff --git a/tests/charts/donut.test.js b/tests/charts/donut.test.js index f3f58ad..19b80ca 100644 --- a/tests/charts/donut.test.js +++ b/tests/charts/donut.test.js @@ -58,7 +58,7 @@ describe("generateDonutChart", () => { generateDonutChart(langs, theme, 800); const segmentsCall = createDonutSegments.mock.calls[createDonutSegments.mock.calls.length - 1]; const legendCall = createLegend.mock.calls[createLegend.mock.calls.length - 1]; - expect(segmentsCall[3]).toBe(theme.colours); + expect(segmentsCall[3]).toEqual(theme.colours); expect(legendCall[2]).toBe(theme); }); }); diff --git a/tests/render/chart.test.js b/tests/render/chart.test.ts similarity index 64% rename from tests/render/chart.test.js rename to tests/render/chart.test.ts index 92fa120..2d22336 100644 --- a/tests/render/chart.test.js +++ b/tests/render/chart.test.ts @@ -1,25 +1,36 @@ import { describe, it, expect, vi } from "vitest"; import { generateChartData } from "../../src/render/chart.js"; import { generateDonutChart } from "../../src/charts/donut.js"; +import { THEMES } from "../../src/constants/themes.js"; +import type { ChartType, ChartResult, Language, Theme } from "../../src/types.js"; + +type MockChartResult = ChartResult & { + mockData: boolean; + data: Language[]; + theme: Theme; + width: number; +} vi.mock("../../src/charts/donut.js", () => ({ - generateDonutChart: vi.fn((data, theme, width) => ({ + generateDonutChart: vi.fn((data, theme, width, _stroke) => ({ + segments: "", + legend: "", mockData: true, data, theme, width - })) + } as MockChartResult)) })); describe("generateChartData", () => { const data = [{ lang: "JavaScript", pct: 60 }]; - const theme = "light"; + const theme = THEMES.default; const chartType = "donut"; const width = 400; const stroke = false; it("should call donut generator when chartType is donut", () => { - const result = generateChartData(data, theme, chartType, width, stroke); + const result = generateChartData(data, theme, chartType, width, stroke) as MockChartResult; expect(generateDonutChart).toHaveBeenCalledWith(data, theme, width, stroke); expect(result.data).toBe(data); expect(result.theme).toBe(theme); @@ -27,12 +38,12 @@ describe("generateChartData", () => { }); it("defaults to donut generator when chartType is undefined", () => { - generateChartData(data, theme, undefined, width, stroke); + generateChartData(data, theme, undefined as unknown as ChartType, width, stroke); expect(generateDonutChart).toHaveBeenCalledWith(data, theme, width, stroke); }); it("defaults to donut generator for unrecognized chartType", () => { - generateChartData(data, theme, "bigbadwolf", width, stroke); + generateChartData(data, theme, "bigbadwolf" as ChartType, width, stroke); expect(generateDonutChart).toHaveBeenCalledWith(data, theme, width, stroke); }); }); diff --git a/tests/render/error.test.js b/tests/render/error.test.ts similarity index 89% rename from tests/render/error.test.js rename to tests/render/error.test.ts index 08058ae..a30a7a3 100644 --- a/tests/render/error.test.js +++ b/tests/render/error.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect } from "vitest"; import { renderError } from "../../src/render/error.js"; import { THEMES } from "../../src/constants/themes.js"; +import type { Theme } from "../../src/types.js"; describe("renderError", () => { it("renders error SVG with message", () => { @@ -10,7 +11,7 @@ describe("renderError", () => { }); it("uses custom theme background when provided", () => { - const theme = { bg: "#123456" }; + const theme = { bg: "#123456" } as Theme; const result = renderError("Error", 400, 300, theme); expect(result).toContain(`fill="#123456"`); }); diff --git a/tests/render/svg.test.js b/tests/render/svg.test.ts similarity index 100% rename from tests/render/svg.test.js rename to tests/render/svg.test.ts From 608bc6d4f2ea0995e59d62432895f04e6c5cf7c9 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Thu, 26 Feb 2026 14:13:52 -0500 Subject: [PATCH 12/17] refactor: convert tests/charts to TypeScript --- tests/charts/{donut.test.js => donut.test.ts} | 37 ++++++++++--------- .../{geometry.test.js => geometry.test.ts} | 12 ++++-- .../charts/{legend.test.js => legend.test.ts} | 17 +++++---- tests/charts/{pie.test.js => pie.test.ts} | 25 +++++++------ 4 files changed, 52 insertions(+), 39 deletions(-) rename tests/charts/{donut.test.js => donut.test.ts} (58%) rename tests/charts/{geometry.test.js => geometry.test.ts} (85%) rename tests/charts/{legend.test.js => legend.test.ts} (75%) rename tests/charts/{pie.test.js => pie.test.ts} (65%) diff --git a/tests/charts/donut.test.js b/tests/charts/donut.test.ts similarity index 58% rename from tests/charts/donut.test.js rename to tests/charts/donut.test.ts index 19b80ca..947b501 100644 --- a/tests/charts/donut.test.js +++ b/tests/charts/donut.test.ts @@ -3,6 +3,7 @@ import { generateDonutChart } from "../../src/charts/donut.js"; import { createDonutSegments } from "../../src/charts/geometry.js"; import { createLegend } from "../../src/charts/legend.js"; import { LEGEND_SHIFT_THRESHOLD } from "../../src/constants/styles.js"; +import type { Theme } from "../../src/types.js"; vi.mock("../../src/charts/geometry.js", () => ({ createDonutSegments: vi.fn(() => ``) @@ -12,25 +13,27 @@ vi.mock("../../src/charts/legend.js", () => ({ createLegend: vi.fn(() => "mockLegend") })); +const mockCreateDonutSegments = vi.mocked(createDonutSegments); +const mockCreateLegend = vi.mocked(createLegend); + describe("generateDonutChart", () => { - const theme = { colours: ["#f00", "#0f0"], text: "#333" }; + const theme: Theme = { colours: ["#f00", "#0f0"], text: "#333", bg: "#fff" }; it("returns segments and legend", () => { const langs = [{ lang: "JS", pct: 100 }]; - const result = generateDonutChart(langs, theme, 800); + const result = generateDonutChart(langs, theme, 800, false); expect(result).toHaveProperty("segments"); expect(result).toHaveProperty("legend"); - expect(createDonutSegments).toHaveBeenCalled(); - expect(createLegend).toHaveBeenCalled(); + expect(mockCreateDonutSegments).toHaveBeenCalled(); + expect(mockCreateLegend).toHaveBeenCalled(); }); it("passes isShifted=false when below threshold", () => { const langs = Array.from({ length: LEGEND_SHIFT_THRESHOLD }, (_, i) => ({ lang: `L${i}`, pct: 100 / LEGEND_SHIFT_THRESHOLD })); - - generateDonutChart(langs, theme, 800); - const legendCall = createLegend.mock.calls[createLegend.mock.calls.length - 1]; + generateDonutChart(langs, theme, 800, false); + const legendCall = mockCreateLegend.mock.calls.at(-1) ?? []; expect(legendCall[1]).toBe(false); }); @@ -39,25 +42,25 @@ describe("generateDonutChart", () => { lang: `L${i}`, pct: 100 / (LEGEND_SHIFT_THRESHOLD + 1) })); - generateDonutChart(langs, theme, 800); - const legendCall = createLegend.mock.calls[createLegend.mock.calls.length - 1]; + generateDonutChart(langs, theme, 800, false); + const legendCall = mockCreateLegend.mock.calls.at(-1) ?? []; expect(legendCall[1]).toBe(true); }); it("calculates positions based on width", () => { const langs = [{ lang: "Python", pct: 100 }]; - generateDonutChart(langs, theme, 1000); - const segmentCall = createDonutSegments.mock.calls[createDonutSegments.mock.calls.length - 1]; - const legendCall = createLegend.mock.calls[createLegend.mock.calls.length - 1]; - expect(typeof segmentCall[1]).toBe("number"); - expect(typeof legendCall[3]).toBe("number"); + generateDonutChart(langs, theme, 1000, false); + const segmentCall = mockCreateDonutSegments.mock.calls.at(-1); + const legendCall = mockCreateLegend.mock.calls.at(-1) ?? []; + expect(typeof segmentCall![1]).toBe("number"); + expect(typeof legendCall![3]).toBe("number"); }); it("passes theme to both segments and legend", () => { const langs = [{ lang: "HTML", pct: 100 }]; - generateDonutChart(langs, theme, 800); - const segmentsCall = createDonutSegments.mock.calls[createDonutSegments.mock.calls.length - 1]; - const legendCall = createLegend.mock.calls[createLegend.mock.calls.length - 1]; + generateDonutChart(langs, theme, 800, false); + const segmentsCall = mockCreateDonutSegments.mock.calls.at(-1)!; + const legendCall = mockCreateLegend.mock.calls.at(-1) ?? []; expect(segmentsCall[3]).toEqual(theme.colours); expect(legendCall[2]).toBe(theme); }); diff --git a/tests/charts/geometry.test.js b/tests/charts/geometry.test.ts similarity index 85% rename from tests/charts/geometry.test.js rename to tests/charts/geometry.test.ts index 0b96724..ac64351 100644 --- a/tests/charts/geometry.test.js +++ b/tests/charts/geometry.test.ts @@ -4,6 +4,7 @@ import { describeSegment, createDonutSegments } from "../../src/charts/geometry.js"; +import type { Language } from "../../src/types.js"; const mockGeometry = { CENTER_Y: 100, INNER_RADIUS: 30, OUTER_RADIUS: 50 }; @@ -28,8 +29,9 @@ describe("donut geometry", () => { }); it("describeSegment: segments have stroke when enabled", () => { + const langs: Language[] = [{ lang: "JS", pct: 50 }]; const paths = createDonutSegments( - [{ pct: 50 }], + langs, 100, mockGeometry, ["#f00"], @@ -39,7 +41,7 @@ describe("donut geometry", () => { }); it("createDonutSegments: single lang full circle", () => { - const langs = [{ pct: 100 }]; + const langs: Language[] = [{ lang: "JS", pct: 100 }]; const paths = createDonutSegments( langs, 100, @@ -51,7 +53,11 @@ describe("donut geometry", () => { }); it("createDonutSegments: multi-lang sums to 360°", () => { - const langs = [{ pct: 33 }, { pct: 33 }, { pct: 34 }]; + const langs: Language[] = [ + { lang: "JS", pct: 33 }, + { lang: "TS", pct: 33 }, + { lang: "PY", pct: 34 } + ]; const paths = createDonutSegments( langs, 100, diff --git a/tests/charts/legend.test.js b/tests/charts/legend.test.ts similarity index 75% rename from tests/charts/legend.test.js rename to tests/charts/legend.test.ts index c9ce502..3b2f45b 100644 --- a/tests/charts/legend.test.js +++ b/tests/charts/legend.test.ts @@ -1,8 +1,9 @@ import { describe, it, expect } from "vitest"; import { createLegend } from "../../src/charts/legend.js"; import { LEGEND_STYLES } from "../../src/constants/styles.js"; +import type { Theme } from "../../src/types.js"; -const theme = { colours: ["#f00", "#0f0", "#00f"], text: "#333" }; +const theme: Theme = { colours: ["#f00", "#0f0", "#00f"], text: "#333", bg: "#fff" }; describe("createLegend", () => { it("single-column layout positions correctly", () => { @@ -10,7 +11,7 @@ describe("createLegend", () => { { lang: "JavaScript", pct: 60 }, { lang: "Python", pct: 40 } ]; - const result = createLegend(langs, false, theme, 300); + const result = createLegend(langs, false, theme, 300, false); expect(result).toContain(`x="300"`); expect(result).toContain(`y="${LEGEND_STYLES.START_Y}"`); expect(result).toContain(`y="${LEGEND_STYLES.START_Y + LEGEND_STYLES.ROW_HEIGHT}"`); @@ -21,14 +22,14 @@ describe("createLegend", () => { lang: `Lang${i}`, pct: 12.5 })); - const result = createLegend(langs, true, theme, 300); + const result = createLegend(langs, true, theme, 300, false); expect(result).toContain(`x="300`); expect(result).toContain(`x="${300 + LEGEND_STYLES.COLUMN_WIDTH}"`); }); it("formats percentages to one decimal", () => { const langs = [{ lang: "Rust", pct: 33.333 }]; - const result = createLegend(langs, false, theme, 300); + const result = createLegend(langs, false, theme, 300, false); expect(result).toContain("33.3%"); expect(result).not.toContain("33.333"); @@ -47,16 +48,16 @@ describe("createLegend", () => { { lang: "C#", pct: 50 }, { lang: "C++", pct: 50 } ]; - const result = createLegend(langs, false, theme, 300); - expect(result.match(/ { const langs = [{ lang: "Java", pct: 100 }]; - const result = createLegend(langs, false, theme, 300); + const result = createLegend(langs, false, theme, 300, false); expect(result).toContain(`fill="#f00"`); expect(result).toContain(`fill="${theme.text}"`); }); diff --git a/tests/charts/pie.test.js b/tests/charts/pie.test.ts similarity index 65% rename from tests/charts/pie.test.js rename to tests/charts/pie.test.ts index d21eddc..8ef4288 100644 --- a/tests/charts/pie.test.js +++ b/tests/charts/pie.test.ts @@ -12,22 +12,25 @@ vi.mock("../../src/charts/legend.js", () => ({ createLegend: vi.fn(() => `mockLegend`) })); +const mockCreateDonutSegments = vi.mocked(createDonutSegments); +const mockCreateLegend = vi.mocked(createLegend); + describe("generatePieChart", () => { - const theme = { colours: ["#f00", "#0f0"], text: "#333" }; + const theme = { colours: ["#f00", "#0f0"], text: "#333", bg: "#fff" }; it("returns segments and legend", () => { const langs = [{ lang: "JS", pct: 100 }]; - const result = generatePieChart(langs, theme, 800); + const result = generatePieChart(langs, theme, 800, false); expect(result).toHaveProperty("segments"); expect(result).toHaveProperty("legend"); - expect(createDonutSegments).toHaveBeenCalled(); - expect(createLegend).toHaveBeenCalled(); + expect(mockCreateDonutSegments).toHaveBeenCalled(); + expect(mockCreateLegend).toHaveBeenCalled(); }); it("passes INNER_RADIUS: 0 for filled pie", () => { const langs = [{ lang: "Python", pct: 100 }]; - generatePieChart(langs, theme, 800); - const call = createDonutSegments.mock.calls[createDonutSegments.mock.calls.length - 1]; + generatePieChart(langs, theme, 800, false); + const call = mockCreateDonutSegments.mock.calls.at(-1)!; const geometry = call[2]; expect(geometry.INNER_RADIUS).toBe(0); }); @@ -36,16 +39,16 @@ describe("generatePieChart", () => { const langs = Array.from({ length: LEGEND_SHIFT_THRESHOLD + 1 }, (_, i) => ({ lang: `L${i}`, pct: 100 / (LEGEND_SHIFT_THRESHOLD + 1) })); - generatePieChart(langs, theme, 800); - const legendCall = createLegend.mock.calls[createLegend.mock.calls.length - 1]; + generatePieChart(langs, theme, 800, false); + const legendCall = mockCreateLegend.mock.calls.at(-1)!; expect(legendCall[1]).toBe(true); }); it("calculates positions based on width", () => { const langs = [{ lang: "Rust", pct: 100 }]; - generatePieChart(langs, theme, 1000); - const segmentCall = createDonutSegments.mock.calls[createDonutSegments.mock.calls.length - 1]; - const legendCall = createLegend.mock.calls[createLegend.mock.calls.length - 1]; + generatePieChart(langs, theme, 1000, false); + const segmentCall = mockCreateDonutSegments.mock.calls.at(-1)!; + const legendCall = mockCreateLegend.mock.calls.at(-1)!; expect(typeof segmentCall[1]).toBe("number"); expect(typeof legendCall[3]).toBe("number"); }); From 26464c59544ac5016c5faf60900eac3843e4f9ab Mon Sep 17 00:00:00 2001 From: Masonlet Date: Thu, 26 Feb 2026 14:25:48 -0500 Subject: [PATCH 13/17] convert tests/api/github to TypeScript --- tests/api/{github.test.js => github.test.ts} | 65 ++++++++++---------- 1 file changed, 31 insertions(+), 34 deletions(-) rename tests/api/{github.test.js => github.test.ts} (75%) diff --git a/tests/api/github.test.js b/tests/api/github.test.ts similarity index 75% rename from tests/api/github.test.js rename to tests/api/github.test.ts index 1a0a64c..77565d6 100644 --- a/tests/api/github.test.js +++ b/tests/api/github.test.ts @@ -13,6 +13,12 @@ const languages = { HTML: 2000 }; +const mockFetch = () => vi.mocked(global.fetch); +const mockResponse = (data: unknown) => + ({ ok: true, json: async () => data }) as unknown as Response; +const mockErrorResponse = (status: number, statusText = "") => + ({ ok: false, status, statusText }) as unknown as Response; + describe("fetchLanguageData", () => { beforeEach(() => { vi.stubEnv("GITHUB_USERNAMES", "testuser"); @@ -42,24 +48,20 @@ describe("fetchLanguageData", () => { vi.unstubAllEnvs(); vi.stubEnv("GITHUB_USERNAMES", "testuser"); - global.fetch - .mockResolvedValueOnce({ ok: true, json: async () => repos }) - .mockResolvedValueOnce({ ok: true, json: async () => languages }) - .mockResolvedValueOnce({ ok: true, json: async () => ({}) }); + mockFetch() + .mockResolvedValueOnce(mockResponse(repos)) + .mockResolvedValueOnce(mockResponse(languages)) + .mockResolvedValueOnce(mockResponse({})); const result = await fetchLanguageData(); expect(global.fetch).toHaveBeenCalledTimes(3); - expect(result).toEqual({ - JavaScript: 5000, - Python: 3000, - HTML: 2000 - }); + expect(result).toEqual({ JavaScript: 5000, Python: 3000, HTML: 2000 }); }); it("fetches repos and filters forks and ignored", async () => { - global.fetch - .mockResolvedValueOnce({ ok: true, json: async () => repos }) - .mockResolvedValueOnce({ ok: true, json: async () => languages }); + mockFetch() + .mockResolvedValueOnce(mockResponse(repos)) + .mockResolvedValueOnce(mockResponse(languages)); await fetchLanguageData(); @@ -84,29 +86,26 @@ describe("fetchLanguageData", () => { { name: "repo2", fork: false, full_name: "user/repo2" } ]; - global.fetch - .mockResolvedValueOnce({ ok: true, json: async () => repos }) - .mockResolvedValueOnce({ ok: true, json: async () => ({ JavaScript: 1000 }) }) - .mockResolvedValueOnce({ ok: true, json: async () => ({ JavaScript: 500, Python: 300 }) }); + mockFetch() + .mockResolvedValueOnce(mockResponse(repos)) + .mockResolvedValueOnce(mockResponse({ JavaScript: 1000 })) + .mockResolvedValueOnce(mockResponse({ JavaScript: 500, Python: 300 })); const result = await fetchLanguageData(); expect(result).toEqual({ JavaScript: 1500, Python: 300 }); }); it("throws on repos API error", async () => { - global.fetch.mockResolvedValueOnce({ - ok: false, - status: 404, - statusText: "Not Found" - }); + mockFetch() + .mockResolvedValueOnce(mockErrorResponse(404, "Not Found")); await expect(fetchLanguageData()).rejects.toThrow("GitHub API error: 404 Not Found"); }); it("caches results within refresh interval", async () => { - global.fetch - .mockResolvedValueOnce({ ok: true, json: async () => [repos[0]] }) - .mockResolvedValueOnce({ ok: true, json: async () => languages }); + mockFetch() + .mockResolvedValueOnce(mockResponse([repos[0]])) + .mockResolvedValueOnce(mockResponse(languages)); const result1 = await fetchLanguageData(); const result2 = await fetchLanguageData(); @@ -120,10 +119,10 @@ describe("fetchLanguageData", () => { { name: "repo2", fork: false, full_name: "user/repo2" } ]; - global.fetch - .mockResolvedValueOnce({ ok: true, json: async () => repos }) - .mockResolvedValueOnce({ ok: false, status: 403 }) // Failed language fetch - .mockResolvedValueOnce({ ok: true, json: async () => ({ Python: 500 }) }); + mockFetch() + .mockResolvedValueOnce(mockResponse(repos)) + .mockResolvedValueOnce(mockErrorResponse(403)) // Failed language fetch + .mockResolvedValueOnce(mockResponse({ Python: 500 })); const result = await fetchLanguageData(); expect(result).toEqual({ Python: 500 }); @@ -137,9 +136,9 @@ describe("fetchLanguageData", () => { { name: "org-repo", fork: false, full_name: "test-org/org-repo" } ]; - global.fetch - .mockResolvedValueOnce({ ok: true, json: async () => orgRepos }) - .mockResolvedValueOnce({ ok: true, json: async () => ({ TypeScript: 4000 }) }) + mockFetch() + .mockResolvedValueOnce(mockResponse(orgRepos)) + .mockResolvedValueOnce(mockResponse({ TypeScript: 4000 })); const result = await fetchLanguageData(); @@ -164,9 +163,7 @@ describe("processLanguageData", () => { const data = { HTML: 1000, JavaScript: 5000, Python: 3000 }; const result = processLanguageData(data, 3); - expect(result[0].lang).toBe("JavaScript"); - expect(result[1].lang).toBe("Python"); - expect(result[2].lang).toBe("HTML"); + expect(result.map(l => l.lang)).toEqual(["JavaScript", "Python", "HTML"]); }); it("limits to count", () => { From 820ed7a454d2ff0ab24f0e7a450b085c54cbce52 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Thu, 26 Feb 2026 14:46:19 -0500 Subject: [PATCH 14/17] refactor: convert tests/api/languages/index to TypeScript --- .../{index.test.js => index.test.ts} | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) rename tests/api/languages/{index.test.js => index.test.ts} (65%) diff --git a/tests/api/languages/index.test.js b/tests/api/languages/index.test.ts similarity index 65% rename from tests/api/languages/index.test.js rename to tests/api/languages/index.test.ts index d6ebc3b..46900a7 100644 --- a/tests/api/languages/index.test.js +++ b/tests/api/languages/index.test.ts @@ -5,6 +5,8 @@ import { fetchLanguageData, processLanguageData } from "../../../src/api/github. import { generateChartData } from "../../../src/render/chart.js"; import { renderSvg } from "../../../src/render/svg.js"; import { renderError } from "../../../src/render/error.js"; +import type { VercelRequest, VercelResponse } from "@vercel/node"; +import type { ChartResult } from "../../../src/types.js"; vi.mock("../../../src/utils/params.js"); vi.mock("../../../src/api/github.js"); @@ -13,23 +15,31 @@ vi.mock("../../../src/render/svg.js"); vi.mock("../../../src/render/error.js"); describe("handler", () => { - let req, res; + let req: VercelRequest; + let res: VercelResponse; + + const mockTheme = { + bg: "#ffffff", + text: "#000000", + colours: [] as string[] + } as const; beforeEach(() => { - req = { query: {} }; + vi.clearAllMocks(); + req = { query: {} } as VercelRequest; res = { setHeader: vi.fn(), status: vi.fn().mockReturnThis(), send: vi.fn() - }; + } as unknown as VercelResponse; - parseQueryParams.mockReturnValue({ + vi.mocked(parseQueryParams).mockReturnValue({ chartType: "donut", chartTitle: "Languages", width: 600, height: 400, count: 5, - selectedTheme: { bg: "#fff", text: "#000" }, + selectedTheme: mockTheme, stroke: false, useTestData: false }); @@ -41,23 +51,23 @@ describe("handler", () => { { lang: "JavaScript", pct: 62.5 }, { lang: "Python", pct: 37.5 } ]; - const chartData = { - segments: [{ lang: "JavaScript", pct: 62.5 }], - legend: [{ lang: "JavaScript", colour: "#f00" }] + const chartData: ChartResult = { + segments: '', + legend: 'JS' }; const svgOutput = "chart"; - fetchLanguageData.mockResolvedValue(rawData); - processLanguageData.mockReturnValue(normalizedData); - generateChartData.mockReturnValue(chartData); - renderSvg.mockReturnValue(svgOutput); + vi.mocked(fetchLanguageData).mockResolvedValue(rawData); + vi.mocked(processLanguageData).mockReturnValue(normalizedData); + vi.mocked(generateChartData).mockReturnValue(chartData); + vi.mocked(renderSvg).mockReturnValue(svgOutput); await handler(req, res); expect(fetchLanguageData).toHaveBeenCalledWith(false); expect(processLanguageData).toHaveBeenCalledWith(rawData, 5); - expect(generateChartData).toHaveBeenCalledWith(normalizedData, { bg: "#fff", text: "#000" }, "donut", 600, false); - expect(renderSvg).toHaveBeenCalledWith(600, 400, "#fff", chartData.segments, chartData.legend, "Languages", "#000"); + expect(generateChartData).toHaveBeenCalledWith(normalizedData, mockTheme, "donut", 600, false); + expect(renderSvg).toHaveBeenCalledWith(600, 400, "#ffffff", chartData.segments, chartData.legend, "Languages", "#000000"); expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml"); expect(res.setHeader).toHaveBeenCalledWith("Cache-Control", "public, max-age=3600, s-maxage=3600, stale-while-revalidate=60"); expect(res.status).toHaveBeenCalledWith(200); @@ -68,12 +78,12 @@ describe("handler", () => { const error = new Error("GitHub API error"); const errorSvg = "error"; - fetchLanguageData.mockRejectedValue(error); - renderError.mockReturnValue(errorSvg); + vi.mocked(fetchLanguageData).mockRejectedValue(error); + vi.mocked(renderError).mockReturnValue(errorSvg); await handler(req, res); - expect(renderError).toHaveBeenCalledWith("GitHub API error", 600, 400, { bg: "#fff", text: "#000" }); + expect(renderError).toHaveBeenCalledWith("GitHub API error", 600, 400, mockTheme); expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml"); expect(res.status).toHaveBeenCalledWith(500); expect(res.send).toHaveBeenCalledWith(errorSvg); @@ -83,13 +93,13 @@ describe("handler", () => { const error = new Error("No language data available"); const errorSvg = "error"; - fetchLanguageData.mockResolvedValue({}); - processLanguageData.mockImplementation(() => { throw error; }); - renderError.mockReturnValue(errorSvg); + vi.mocked(fetchLanguageData).mockResolvedValue({}); + vi.mocked(processLanguageData).mockImplementation(() => { throw error; }); + vi.mocked(renderError).mockReturnValue(errorSvg); await handler(req, res); - expect(renderError).toHaveBeenCalledWith("No language data available", 600, 400, { bg: "#fff", text: "#000" }); + expect(renderError).toHaveBeenCalledWith("No language data available", 600, 400, mockTheme); expect(res.status).toHaveBeenCalledWith(500); }); }); From 642f9f2e139d78613227f2219f5901edf9b6afed Mon Sep 17 00:00:00 2001 From: Masonlet Date: Thu, 26 Feb 2026 14:55:43 -0500 Subject: [PATCH 15/17] docs: add TypeScript prerequisite --- README.md | 1 + package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 51b65ee..ab1e933 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ To get 10 languages, a dark theme, and a custom title: ### Prerequisites - Node.js 18+ +- TypeScript 5.0+ - (Optional) Vercel or other Node.js hosting ### Installation diff --git a/package.json b/package.json index 53113dc..1c4184b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github-top-languages", "version": "0.0.1", - "description": "Github Top Langauges card generator", + "description": "Github Top Languages card generator", "main": "index.js", "scripts": { "test": "vitest", @@ -12,6 +12,7 @@ "license": "MIT", "type": "module", "devDependencies": { + "typescript": "^5.0.0", "@types/node": "^25.3.1", "@vercel/node": "^5.6.7", "@vitest/coverage-v8": "^4.0.17", From d33ddf148b61bfee688e9dc8525851bce26c81aa Mon Sep 17 00:00:00 2001 From: Masonlet Date: Thu, 26 Feb 2026 15:00:41 -0500 Subject: [PATCH 16/17] ci: add typescript check to workflow --- .github/workflows/tests.yml | 5 ++++- package.json | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a8c39cc..a61d11a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,5 +22,8 @@ jobs: - name: Install dependencies run: npm install + - name: Type check + run: npm run typecheck + - name: Run tests - run: npm test \ No newline at end of file + run: npm test diff --git a/package.json b/package.json index 1c4184b..e6de891 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "test": "vitest", "test:watch": "vitest --watch", - "test:coverage": "vitest --coverage" + "test:coverage": "vitest --coverage", + "typecheck": "tsc --noEmit" }, "author": "Mason L'Etoile", "license": "MIT", From cef172e3231d562fccccf7b31e3d0e92b0dec318 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Thu, 26 Feb 2026 15:23:40 -0500 Subject: [PATCH 17/17] chore: final cleanup --- src/charts/donut.ts | 2 +- tsconfig.json | 30 ++++++++++++++++++++++-------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/charts/donut.ts b/src/charts/donut.ts index 284bc00..f140cf6 100644 --- a/src/charts/donut.ts +++ b/src/charts/donut.ts @@ -48,7 +48,7 @@ export function generateDonutChart( selectedTheme, legendStartX, useStroke - ) + ); return { segments, legend }; } diff --git a/tsconfig.json b/tsconfig.json index 7ef1f04..f71d434 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,13 @@ { "compilerOptions": { + // Module/Target "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler", + "moduleDetection": "force", "outDir": "./dist", + // Core Strictness "strict": true, "noImplicitAny": true, "strictNullChecks": true, @@ -14,24 +17,35 @@ "noImplicitThis": true, "useUnknownInCatchVariables": true, - "allowUnusedLabels": false, - "allowUnreachableCode": false, - "exactOptionalPropertyTypes": true, - "noFallthroughCasesInSwitch": true, - "noImplicitOverride": true, + // Unused Code Elimination + "noUnusedLocals": true, + "noUnusedParameters": true, "noImplicitReturns": true, + "importsNotUsedAsValues": "remove", + "preserveValueImports": false, + + // Safety/Validation + "noErrorTruncation": true, + "exactOptionalPropertyTypes": true, "noPropertyAccessFromIndexSignature": true, "noUncheckedIndexedAccess": true, "noUncheckedSideEffectImports": true, + + // Code Quality + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, "useDefineForClassFields": true, - "noUnusedLocals": true, - "noUnusedParameters": true, + // Build/Output "isolatedModules": true, "verbatimModuleSyntax": true, "forceConsistentCasingInFileNames": true, "esModuleInterop": true, - "skipLibCheck": true + "skipLibCheck": true, + "checkJs": false, + "noEmit": true }, "include": [ "./src/**/*.ts",