diff --git a/api/languages/index.ts b/api/languages/index.ts
index 36ff84e..53de17a 100644
--- a/api/languages/index.ts
+++ b/api/languages/index.ts
@@ -1,9 +1,9 @@
import type { VercelRequest, VercelResponse } from "@vercel/node";
-import { parseQueryParams, type QueryParams } from "../../src/utils/params.js";
+import { parseQueryParams, type QueryParams } from "github-top-languages-lib/utils/params.js";
+import { generateChartData } from "github-top-languages-lib/render/chart.js";
+import { renderSvg } from "github-top-languages-lib/render/svg.js";
+import { renderError } from "github-top-languages-lib/render/error.js";
import { fetchLanguageData, processLanguageData } from "../../src/services/github.js";
-import { generateChartData } from "../../src/render/chart.js";
-import { renderSvg } from "../../src/render/svg.js";
-import { renderError } from "../../src/render/error.js";
export default async function handler(
req: VercelRequest,
diff --git a/package-lock.json b/package-lock.json
index 05df995..92ee69a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,9 @@
"name": "github-top-languages",
"version": "0.0.1",
"license": "MIT",
+ "dependencies": {
+ "github-top-languages-lib": "^1.0.0"
+ },
"devDependencies": {
"@types/node": "^25.3.1",
"@vercel/node": "^5.7.17",
@@ -17,9 +20,9 @@
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -27,9 +30,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
- "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -37,13 +40,13 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.29.3",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
- "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.29.0"
+ "@babel/types": "^7.29.7"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -53,14 +56,14 @@
}
},
"node_modules/@babel/types": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
- "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.28.5"
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -676,14 +679,14 @@
}
},
"node_modules/@napi-rs/wasm-runtime": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
- "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
+ "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
- "@tybys/wasm-util": "^0.10.1"
+ "@tybys/wasm-util": "^0.10.2"
},
"funding": {
"type": "github",
@@ -733,9 +736,9 @@
}
},
"node_modules/@oxc-project/types": {
- "version": "0.129.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz",
- "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==",
+ "version": "0.133.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
+ "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
"dev": true,
"license": "MIT",
"funding": {
@@ -754,9 +757,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz",
- "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
+ "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
"cpu": [
"arm64"
],
@@ -771,9 +774,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz",
- "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
+ "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
"cpu": [
"arm64"
],
@@ -788,9 +791,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz",
- "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
+ "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
"cpu": [
"x64"
],
@@ -805,9 +808,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz",
- "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
+ "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
"cpu": [
"x64"
],
@@ -822,9 +825,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz",
- "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
+ "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
"cpu": [
"arm"
],
@@ -839,9 +842,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz",
- "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
+ "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
"cpu": [
"arm64"
],
@@ -859,9 +862,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz",
- "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
+ "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
"cpu": [
"arm64"
],
@@ -879,9 +882,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz",
- "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
+ "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
"cpu": [
"ppc64"
],
@@ -899,9 +902,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz",
- "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
+ "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
"cpu": [
"s390x"
],
@@ -919,9 +922,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz",
- "integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
+ "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
"cpu": [
"x64"
],
@@ -939,9 +942,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz",
- "integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
+ "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
"cpu": [
"x64"
],
@@ -959,9 +962,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz",
- "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
+ "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
"cpu": [
"arm64"
],
@@ -976,9 +979,9 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz",
- "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
+ "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
"cpu": [
"wasm32"
],
@@ -995,9 +998,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz",
- "integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
+ "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
"cpu": [
"arm64"
],
@@ -1012,9 +1015,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz",
- "integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
+ "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
"cpu": [
"x64"
],
@@ -1029,16 +1032,16 @@
}
},
"node_modules/@rolldown/pluginutils": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz",
- "integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/pluginutils": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
- "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz",
+ "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1122,19 +1125,19 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "25.7.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.7.0.tgz",
- "integrity": "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==",
+ "version": "25.9.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz",
+ "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~7.21.0"
+ "undici-types": ">=7.24.0 <7.24.7"
}
},
"node_modules/@vercel/build-utils": {
- "version": "13.22.1",
- "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-13.22.1.tgz",
- "integrity": "sha512-oYzvl9Xh0woTHQrGA9lKVDj1K1+MdfPPnerNwlQtldbsCDez55sxadGxvjCBmkVmNhTf+rkkQzSUe6uz0dFhBA==",
+ "version": "13.29.0",
+ "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-13.29.0.tgz",
+ "integrity": "sha512-Az9gTLZpndmQouuKCzwrKmMrOUxqfWJf1KfEVF7y59RvAJlflCn3RZ5iajIZgo2FqoAmCBGIgd0sovWWSXBWtw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -1151,16 +1154,16 @@
"license": "MIT"
},
"node_modules/@vercel/error-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@vercel/error-utils/-/error-utils-2.1.0.tgz",
- "integrity": "sha512-DiJcXBOB9N6QM4d7hYPM9Ck/AUjzBl58XNQPxS74o7CuvIanjzrGgygP/70VsyEASeIJMazk1LrhwcNTR/eZGQ==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@vercel/error-utils/-/error-utils-2.2.0.tgz",
+ "integrity": "sha512-WFWiRxfPzoYWYifaj4thSKvAaZZwUOqD4k5GINRIgZgCiS2E3iAJbWbIsIZmkQdTecWFHcWGA6q48CjisgpOBA==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@vercel/nft": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.5.0.tgz",
- "integrity": "sha512-IWTDeIoWhQ7ZtRO/JRKH+jhmeQvZYhtGPmzw/QGDY+wDCQqfm25P9yIdoAFagu4fWsK4IwZXDFIjrmp5rRm/sA==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.10.0.tgz",
+ "integrity": "sha512-iLOW4fcsgkipfOh2Bw3wB38YDfxTlxr7+j4uFeui2OswkNT28jIitS/aMce7tS0mef1YPQ8zLIDYr3a0aahNrA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1185,9 +1188,9 @@
}
},
"node_modules/@vercel/node": {
- "version": "5.7.17",
- "resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.7.17.tgz",
- "integrity": "sha512-oFjbg+3nM2AvUu5OQTJIyp5KkNv7riu3YMAEjklPLd1HPtDXHWX0MVHtRiUeAQe/7bkJw8VNmqSRjfprP+OmPA==",
+ "version": "5.8.15",
+ "resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.8.15.tgz",
+ "integrity": "sha512-arTGDg9/6qXxcFqpGeM8zCFLEoji69T/d2GQS/GYbM2Zx9pnzXQCHwY/sMnB5QSmoWVPDVHRSUmno//G7npDOw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -1195,10 +1198,10 @@
"@edge-runtime/primitives": "4.1.0",
"@edge-runtime/vm": "3.2.0",
"@types/node": "20.11.0",
- "@vercel/build-utils": "13.22.1",
- "@vercel/error-utils": "2.1.0",
- "@vercel/nft": "1.5.0",
- "@vercel/static-config": "3.3.0",
+ "@vercel/build-utils": "13.29.0",
+ "@vercel/error-utils": "2.2.0",
+ "@vercel/nft": "1.10.0",
+ "@vercel/static-config": "3.4.0",
"async-listen": "3.0.0",
"cjs-module-lexer": "1.2.3",
"edge-runtime": "2.5.9",
@@ -1249,9 +1252,9 @@
}
},
"node_modules/@vercel/static-config": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/@vercel/static-config/-/static-config-3.3.0.tgz",
- "integrity": "sha512-GpS3tPwUeDJCkrKbMNtS2XLRFgfxTlN7YNUL+Bo23+fGolrDw6Oq79R3yvxTYgqRaJMGSEqC7iMw6mj6I5loxg==",
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/@vercel/static-config/-/static-config-3.4.0.tgz",
+ "integrity": "sha512-wCq90CMUB//ggnFh77NQO1xaLFsS4LigQIqKrH6ohnr9Br/KI1FhlErx62WfCOuueWaW+LVsbLOqNXIUjK8t6A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -1261,14 +1264,14 @@
}
},
"node_modules/@vitest/coverage-v8": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.6.tgz",
- "integrity": "sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz",
+ "integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
- "@vitest/utils": "4.1.6",
+ "@vitest/utils": "4.1.8",
"ast-v8-to-istanbul": "^1.0.0",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
@@ -1282,8 +1285,8 @@
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@vitest/browser": "4.1.6",
- "vitest": "4.1.6"
+ "@vitest/browser": "4.1.8",
+ "vitest": "4.1.8"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -1292,16 +1295,16 @@
}
},
"node_modules/@vitest/expect": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz",
- "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz",
+ "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.1.0",
"@types/chai": "^5.2.2",
- "@vitest/spy": "4.1.6",
- "@vitest/utils": "4.1.6",
+ "@vitest/spy": "4.1.8",
+ "@vitest/utils": "4.1.8",
"chai": "^6.2.2",
"tinyrainbow": "^3.1.0"
},
@@ -1310,13 +1313,13 @@
}
},
"node_modules/@vitest/mocker": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz",
- "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz",
+ "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "4.1.6",
+ "@vitest/spy": "4.1.8",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
@@ -1347,9 +1350,9 @@
}
},
"node_modules/@vitest/pretty-format": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz",
- "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz",
+ "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1360,13 +1363,13 @@
}
},
"node_modules/@vitest/runner": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz",
- "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz",
+ "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "4.1.6",
+ "@vitest/utils": "4.1.8",
"pathe": "^2.0.3"
},
"funding": {
@@ -1374,14 +1377,14 @@
}
},
"node_modules/@vitest/snapshot": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz",
- "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz",
+ "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.1.6",
- "@vitest/utils": "4.1.6",
+ "@vitest/pretty-format": "4.1.8",
+ "@vitest/utils": "4.1.8",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@@ -1390,9 +1393,9 @@
}
},
"node_modules/@vitest/spy": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz",
- "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz",
+ "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==",
"dev": true,
"license": "MIT",
"funding": {
@@ -1400,13 +1403,13 @@
}
},
"node_modules/@vitest/utils": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz",
- "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz",
+ "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.1.6",
+ "@vitest/pretty-format": "4.1.8",
"convert-source-map": "^2.0.0",
"tinyrainbow": "^3.1.0"
},
@@ -1492,9 +1495,9 @@
}
},
"node_modules/ast-v8-to-istanbul": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz",
- "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.4.tgz",
+ "integrity": "sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1907,6 +1910,12 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
+ "node_modules/github-top-languages-lib": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/github-top-languages-lib/-/github-top-languages-lib-1.0.0.tgz",
+ "integrity": "sha512-zeuw3sqpFG2RVdraWjZZu7fXOsEysShR9VodeUorB2zFb6Zp19Y9d95q2HLhO7wXce/XafJfh2we5ikoPK1wjw==",
+ "license": "MIT"
+ },
"node_modules/glob": {
"version": "13.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
@@ -2373,9 +2382,9 @@
}
},
"node_modules/lru-cache": {
- "version": "11.3.6",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz",
- "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==",
+ "version": "11.5.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
+ "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
@@ -2393,13 +2402,13 @@
}
},
"node_modules/magicast": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz",
- "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==",
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz",
+ "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.29.0",
+ "@babel/parser": "^7.29.3",
"@babel/types": "^7.29.0",
"source-map-js": "^1.2.1"
}
@@ -2618,15 +2627,18 @@
}
},
"node_modules/obug": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
- "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.2.tgz",
+ "integrity": "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==",
"dev": true,
"funding": [
"https://github.com/sponsors/sxzz",
"https://opencollective.com/debug"
],
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
},
"node_modules/parse-ms": {
"version": "2.1.0",
@@ -2705,9 +2717,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.14",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
- "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
+ "version": "8.5.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"dev": true,
"funding": [
{
@@ -2725,7 +2737,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.11",
+ "nanoid": "^3.3.12",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -2819,14 +2831,14 @@
}
},
"node_modules/rolldown": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz",
- "integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
+ "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@oxc-project/types": "=0.129.0",
- "@rolldown/pluginutils": "1.0.0"
+ "@oxc-project/types": "=0.133.0",
+ "@rolldown/pluginutils": "^1.0.0"
},
"bin": {
"rolldown": "bin/cli.mjs"
@@ -2835,21 +2847,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.0.0",
- "@rolldown/binding-darwin-arm64": "1.0.0",
- "@rolldown/binding-darwin-x64": "1.0.0",
- "@rolldown/binding-freebsd-x64": "1.0.0",
- "@rolldown/binding-linux-arm-gnueabihf": "1.0.0",
- "@rolldown/binding-linux-arm64-gnu": "1.0.0",
- "@rolldown/binding-linux-arm64-musl": "1.0.0",
- "@rolldown/binding-linux-ppc64-gnu": "1.0.0",
- "@rolldown/binding-linux-s390x-gnu": "1.0.0",
- "@rolldown/binding-linux-x64-gnu": "1.0.0",
- "@rolldown/binding-linux-x64-musl": "1.0.0",
- "@rolldown/binding-openharmony-arm64": "1.0.0",
- "@rolldown/binding-wasm32-wasi": "1.0.0",
- "@rolldown/binding-win32-arm64-msvc": "1.0.0",
- "@rolldown/binding-win32-x64-msvc": "1.0.0"
+ "@rolldown/binding-android-arm64": "1.0.3",
+ "@rolldown/binding-darwin-arm64": "1.0.3",
+ "@rolldown/binding-darwin-x64": "1.0.3",
+ "@rolldown/binding-freebsd-x64": "1.0.3",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.3",
+ "@rolldown/binding-linux-arm64-musl": "1.0.3",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.3",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.3",
+ "@rolldown/binding-linux-x64-gnu": "1.0.3",
+ "@rolldown/binding-linux-x64-musl": "1.0.3",
+ "@rolldown/binding-openharmony-arm64": "1.0.3",
+ "@rolldown/binding-wasm32-wasi": "1.0.3",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.3",
+ "@rolldown/binding-win32-x64-msvc": "1.0.3"
}
},
"node_modules/run-parallel": {
@@ -2877,9 +2889,9 @@
}
},
"node_modules/semver": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
- "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz",
+ "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -2960,9 +2972,9 @@
}
},
"node_modules/tar": {
- "version": "7.5.15",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz",
- "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==",
+ "version": "7.5.16",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz",
+ "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
@@ -3000,9 +3012,9 @@
"license": "MIT"
},
"node_modules/tinyexec": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz",
- "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
+ "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3010,9 +3022,9 @@
}
},
"node_modules/tinyglobby": {
- "version": "0.2.16",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
- "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3127,9 +3139,9 @@
}
},
"node_modules/undici-types": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.21.0.tgz",
- "integrity": "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==",
+ "version": "7.24.6",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
+ "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
"dev": true,
"license": "MIT"
},
@@ -3144,17 +3156,17 @@
}
},
"node_modules/vite": {
- "version": "8.0.12",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz",
- "integrity": "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==",
+ "version": "8.0.16",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
+ "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
- "postcss": "^8.5.14",
- "rolldown": "1.0.0",
- "tinyglobby": "^0.2.16"
+ "postcss": "^8.5.15",
+ "rolldown": "1.0.3",
+ "tinyglobby": "^0.2.17"
},
"bin": {
"vite": "bin/vite.js"
@@ -3222,19 +3234,19 @@
}
},
"node_modules/vitest": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz",
- "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==",
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz",
+ "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/expect": "4.1.6",
- "@vitest/mocker": "4.1.6",
- "@vitest/pretty-format": "4.1.6",
- "@vitest/runner": "4.1.6",
- "@vitest/snapshot": "4.1.6",
- "@vitest/spy": "4.1.6",
- "@vitest/utils": "4.1.6",
+ "@vitest/expect": "4.1.8",
+ "@vitest/mocker": "4.1.8",
+ "@vitest/pretty-format": "4.1.8",
+ "@vitest/runner": "4.1.8",
+ "@vitest/snapshot": "4.1.8",
+ "@vitest/spy": "4.1.8",
+ "@vitest/utils": "4.1.8",
"es-module-lexer": "^2.0.0",
"expect-type": "^1.3.0",
"magic-string": "^0.30.21",
@@ -3262,12 +3274,12 @@
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
- "@vitest/browser-playwright": "4.1.6",
- "@vitest/browser-preview": "4.1.6",
- "@vitest/browser-webdriverio": "4.1.6",
- "@vitest/coverage-istanbul": "4.1.6",
- "@vitest/coverage-v8": "4.1.6",
- "@vitest/ui": "4.1.6",
+ "@vitest/browser-playwright": "4.1.8",
+ "@vitest/browser-preview": "4.1.8",
+ "@vitest/browser-webdriverio": "4.1.8",
+ "@vitest/coverage-istanbul": "4.1.8",
+ "@vitest/coverage-v8": "4.1.8",
+ "@vitest/ui": "4.1.8",
"happy-dom": "*",
"jsdom": "*",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
diff --git a/package.json b/package.json
index e9e9c53..e0cfebe 100644
--- a/package.json
+++ b/package.json
@@ -13,12 +13,13 @@
"license": "MIT",
"type": "module",
"devDependencies": {
- "@types/node": "^25.3.1",
- "@vercel/node": "^5.7.17",
- "@vitest/coverage-v8": "^4.0.17",
"typescript": "^5.0.0",
- "vitest": "^4.0.17"
+ "@vercel/node": "^5.7.17",
+ "@types/node": "^25.3.1",
+ "vitest": "^4.0.17",
+ "@vitest/coverage-v8": "^4.0.17"
},
+ "dependencies": { "github-top-languages-lib": "^1.0.0" },
"overrides": {
"minimatch": "10.2.5",
"smol-toml": "1.6.1",
diff --git a/src/charts/donut.ts b/src/charts/donut.ts
deleted file mode 100644
index 532f1b6..0000000
--- a/src/charts/donut.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { Language, Theme, ChartResult } from "../types.js";
-import { resolveLayout, calculateChartCenter, calculateLegendStartX } from "./layout.js";
-import { DONUT_GEOMETRY } from "../constants/geometry.js";
-import { createDonutSegments } from "./geometry.js";
-import { createLegend } from "./legend.js";
-
-export function generateDonutChart(
- normalizedLanguages: Language[],
- selectedTheme: Theme,
- width: number,
- stroke: boolean
-): ChartResult {
- const { isShifted, useStroke } = resolveLayout(normalizedLanguages.length, stroke);
-
- const chartX = calculateChartCenter(width, isShifted);
- const legendStartX = calculateLegendStartX(chartX, DONUT_GEOMETRY.OUTER_RADIUS);
-
- const segments = createDonutSegments(normalizedLanguages, chartX, DONUT_GEOMETRY, [...selectedTheme.colours], useStroke);
- const legend = createLegend(normalizedLanguages, isShifted, selectedTheme, legendStartX, useStroke);
-
- return { segments, legend };
-}
diff --git a/src/charts/geometry.ts b/src/charts/geometry.ts
deleted file mode 100644
index 4043b39..0000000
--- a/src/charts/geometry.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import type { Point, Language, Geometry } from "../types.js"
-import { FULL_CIRCLE_ANGLE } from "../constants/geometry.js";
-
-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)),
- y: cy + (r * Math.sin(angleRad))
- };
-};
-
-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) {
- const midAngle = startAngle + 180;
- const firstHalf = describeSegment(cx, cy, innerR, outerR, startAngle, midAngle);
- const secondHalf = describeSegment(cx, cy, innerR, outerR, midAngle, endAngle);
- return firstHalf + ' ' + secondHalf;
- }
-
- const startOuter = polarToCartesian(cx, cy, outerR, endAngle);
- const endOuter = polarToCartesian(cx, cy, outerR, startAngle);
- const largeArcFlag = endAngle - startAngle <= 180 ? '0' : '1';
-
- if (innerR === 0) {
- return `
- M ${cx} ${cy}
- L ${startOuter.x} ${startOuter.y}
- A ${outerR} ${outerR} 0 ${largeArcFlag} 0 ${endOuter.x} ${endOuter.y}
- Z
- `.trim();
- }
-
- const startInner = polarToCartesian(cx, cy, innerR, startAngle);
- const endInner = polarToCartesian(cx, cy, innerR, endAngle);
-
- return `
- M ${startOuter.x} ${startOuter.y}
- A ${outerR} ${outerR} 0 ${largeArcFlag} 0 ${endOuter.x} ${endOuter.y}
- L ${startInner.x} ${startInner.y}
- A ${innerR} ${innerR} 0 ${largeArcFlag} 1 ${endInner.x} ${endInner.y}
- Z
- `.trim();
-};
-
-export const createDonutSegments = (
- languages: Language[],
- cx: number,
- geometry: Geometry,
- colours: string[],
- stroke: boolean
-): string => {
- let currentAngle = -0.1;
-
- return languages.map((lang, i) => {
- let angle = (lang.pct / 100) * 360;
-
- const segmentAngle = Math.min(currentAngle + angle + 0.1, FULL_CIRCLE_ANGLE);
- const path = describeSegment(
- cx,
- geometry.CENTER_Y,
- geometry.INNER_RADIUS,
- geometry.OUTER_RADIUS,
- currentAngle,
- segmentAngle
- );
-
- currentAngle += angle;
- const fillColour = colours[i % colours.length];
- const strokeAttr = stroke
- ? ` stroke="#000" stroke-width="0.5" stroke-linejoin="round"`
- : ` stroke="${fillColour}" stroke-width="0.2"`;
- return ``;
- }).join('');
-}
diff --git a/src/charts/layout.ts b/src/charts/layout.ts
deleted file mode 100644
index b16014a..0000000
--- a/src/charts/layout.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { LEGEND_SHIFT_THRESHOLD, LEGEND_STYLES, CHART_MARGIN_RIGHT } from "../constants/styles.js";
-
-export function resolveLayout(count: number, stroke: boolean) {
- return {
- isShifted: count > LEGEND_SHIFT_THRESHOLD,
- useStroke: count > 1 ? stroke : false
- };
-}
-
-export function calculateChartCenter(width: number, isShifted: boolean): number {
- const legendWidth = isShifted
- ? LEGEND_STYLES.COLUMN_WIDTH * 2
- : LEGEND_STYLES.WIDTH;
- return (width - legendWidth - CHART_MARGIN_RIGHT) / 2;
-}
-
-export function calculateLegendStartX(chartCenterX: number, radius: number): number {
- return chartCenterX + radius + CHART_MARGIN_RIGHT;
-}
diff --git a/src/charts/legend.ts b/src/charts/legend.ts
deleted file mode 100644
index 6fd7d09..0000000
--- a/src/charts/legend.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { LEGEND_STYLES } from "../constants/styles.js";
-import type { Language, Theme } from "../types.js";
-
-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: number, y: number;
-
- if (!isShifted) {
- x = legendStartX;
- y = LEGEND_STYLES.START_Y + i * LEGEND_STYLES.ROW_HEIGHT;
- } else {
- const half = Math.ceil(numLangs / 2);
- const col = Math.floor(i / half);
- const row = i % half;
-
- x = legendStartX + col * LEGEND_STYLES.COLUMN_WIDTH;
- y = LEGEND_STYLES.START_Y + row * LEGEND_STYLES.ROW_HEIGHT;
- }
-
- const fill = selectedTheme.colours[i];
- const strokeAttr = stroke
- ? ` stroke="#000" stroke-width="0.5" stroke-linejoin="round"`
- : ``;
-
- return `
-
-
- ${lang.lang} ${lang.pct.toFixed(1)}%
-
- `;
- }).join('');
-}
diff --git a/src/charts/pie.ts b/src/charts/pie.ts
deleted file mode 100644
index ce9fa95..0000000
--- a/src/charts/pie.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { Language, Theme, ChartResult } from "../types.js";
-import { resolveLayout, calculateChartCenter, calculateLegendStartX } from "./layout.js";
-import { PIE_GEOMETRY } from "../constants/geometry.js";
-import { createDonutSegments } from "./geometry.js";
-import { createLegend } from "./legend.js";
-
-export function generatePieChart(
- normalizedLanguages: Language[],
- selectedTheme: Theme,
- width: number,
- stroke: boolean
-): ChartResult {
- const { isShifted, useStroke } = resolveLayout(normalizedLanguages.length, stroke);
-
- const chartX = calculateChartCenter(width, isShifted);
- const legendStartX = calculateLegendStartX(chartX, PIE_GEOMETRY.OUTER_RADIUS);
-
- const segments = createDonutSegments(normalizedLanguages, chartX, PIE_GEOMETRY, [...selectedTheme.colours], useStroke);
- const legend = createLegend(normalizedLanguages, isShifted, selectedTheme, legendStartX, useStroke);
-
- return { segments, legend };
-}
diff --git a/src/constants/config.ts b/src/constants/config.ts
deleted file mode 100644
index 11c6024..0000000
--- a/src/constants/config.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export const DEFAULT_CONFIG = {
- TITLE: "Top Languages",
- WIDTH: 400,
- 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.ts b/src/constants/geometry.ts
deleted file mode 100644
index acc9f80..0000000
--- a/src/constants/geometry.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export const FULL_CIRCLE_ANGLE = 359.9999;
-
-const BASE_GEOMETRY = {
- CENTER_Y: 170,
- OUTER_RADIUS: 80,
-} as const;
-
-export const DONUT_GEOMETRY = { ...BASE_GEOMETRY, INNER_RADIUS: 50 } as const;
-export const PIE_GEOMETRY = { ...BASE_GEOMETRY, INNER_RADIUS: 0 } as const;
diff --git a/src/constants/styles.ts b/src/constants/styles.ts
deleted file mode 100644
index 704ff50..0000000
--- a/src/constants/styles.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-export const TITLE_STYLES = {
- TEXT_Y: 30,
- FONT_SIZE: 24
-} as const;
-
-export const LEGEND_STYLES = {
- START_Y: 80,
- ROW_HEIGHT: 25,
- SQUARE_SIZE: 12,
- SQUARE_RADIUS: 2,
- 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;
-export const CHART_MARGIN_RIGHT = 20;
diff --git a/src/constants/themes.ts b/src/constants/themes.ts
deleted file mode 100644
index ad938a8..0000000
--- a/src/constants/themes.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-export const THEMES = {
- default: {
- bg: "#0d1117",
- text: "#ffffff",
- colours: [
- "#A8D5Ba",
- "#FFD6A5",
- "#FFAAA6",
- "#D0CFCF",
- "#CBAACB",
- "#FFE156",
- "#96D5E9",
- "#F3B0C3",
- "#B4A7D6",
- "#FFB6B9",
- "#A3E4D7",
- "#F8B88B",
- "#C9E4CA",
- "#FAD7A0",
- "#AED6F1",
- "#D7BDE2"
- ]
- },
- light: {
- bg: "#ffffff",
- text: "#2f2f2f",
- colours: [
- "#2ecc71",
- "#3498db",
- "#e74c3c",
- "#f39c12",
- "#9b59b6",
- "#1abc9c",
- "#e67e22",
- "#34495e",
- "#16a085",
- "#c0392b",
- "#8e44ad",
- "#27ae60",
- "#d35400",
- "#2980b9",
- "#7f8c8d",
- "#f1c40f"
- ]
- },
- dark: {
- bg: "#1a1a1a",
- text: "#ccd6f6",
- colours: [
- "#ff6b6b",
- "#4ecdc3",
- "#45b7d1",
- "#ffa07a",
- "#98d8c8",
- "#f7dc6f",
- "#bb8fce",
- "#64ffda",
- "#85c1e2",
- "#ff8a80",
- "#a7ffeb",
- "#ffd54f",
- "#ea80fc",
- "#80d8ff",
- "#ffab91",
- "#b9f6ca"
- ]
- }
-} as const;
diff --git a/src/constants/types.ts b/src/constants/types.ts
deleted file mode 100644
index 835e8aa..0000000
--- a/src/constants/types.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export const VALID_TYPES = [
- "donut",
- "pie"
-] as const;
diff --git a/src/render/chart.ts b/src/render/chart.ts
deleted file mode 100644
index 9cf1f74..0000000
--- a/src/render/chart.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { Language, Theme, ChartType, ChartResult } from "../types.js";
-import { generateDonutChart } from "../charts/donut.js";
-import { generatePieChart } from "../charts/pie.js";
-
-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.ts b/src/render/error.ts
deleted file mode 100644
index 8a2b715..0000000
--- a/src/render/error.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import type { Theme } from "../types.js";
-import { THEMES } from "../constants/themes.js";
-import { ERROR_STYLES } from "../constants/styles.js"
-import { sanitize } from "../utils/sanitize.js";
-
-export function renderError(
- message: string,
- width: number,
- height: number,
- selectedTheme?: Theme
-): string {
- const background = selectedTheme?.bg || THEMES.default.bg;
- const maxLen = 40;
- const truncated = message.length > maxLen
- ? sanitize(message.slice(0, maxLen)) + "..."
- : sanitize(message);
- return `
-
- `.trim();
-}
diff --git a/src/render/svg.ts b/src/render/svg.ts
deleted file mode 100644
index 9a78ad8..0000000
--- a/src/render/svg.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { TITLE_STYLES } from "../constants/styles.js"
-
-export function renderSvg(
- width: number,
- height: number,
- background: string,
- segments: string,
- legend: string,
- title: string | null,
- textColour: string
-): string {
- const titleElement = title ? `
-
- ${title}
-
- ` : '';
-
- return `
-
- `.trim();
-}
diff --git a/src/services/github.ts b/src/services/github.ts
index 7c7ab74..aaaa9c9 100644
--- a/src/services/github.ts
+++ b/src/services/github.ts
@@ -1,5 +1,5 @@
-import { REFRESH_INTERVAL } from "../constants/config.js";
-import type { Language } from "../types.js";
+import { REFRESH_INTERVAL } from "github-top-languages-lib/constants/config.js";
+import type { Language } from "github-top-languages-lib/types.js";
type LanguageBytes = Record;
diff --git a/src/types.ts b/src/types.ts
deleted file mode 100644
index fafc211..0000000
--- a/src/types.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-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 ChartType = "donut" | "pie";
-
-export type ChartResult = {
- segments: string;
- legend: string;
-};
-
-export type Theme = {
- readonly colours: readonly string[];
- readonly text: string;
- readonly bg: string;
-};
diff --git a/src/utils/params.ts b/src/utils/params.ts
deleted file mode 100644
index aa431b4..0000000
--- a/src/utils/params.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import type { ChartType } from "../types.js";
-import { sanitize } from "./sanitize.js";
-import { VALID_TYPES } from "../constants/types.js";
-import { DEFAULT_CONFIG, MAX_COUNT } from "../constants/config.js";
-import { THEMES } from "../constants/themes.js";
-
-export type QueryParams = Record;
-
-const parseIntSafe = (
- val: string | undefined,
- fallback: number
-): number => {
- const parsed = Number.parseInt(val ?? '', 10);
- return Number.isNaN(parsed) ? fallback : parsed;
-}
-
-const normalizeHex = (val: string) => `#${val.replace(/^#/, '')}`;
-
-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: string[] = [...baseTheme.colours];
- for (let i = 1; i <= MAX_COUNT; i++) {
- const colourVal = query[`c${i}`];
- if(colourVal) customColours[i - 1] = normalizeHex(colourVal);
- }
-
- 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"] ? normalizeHex(query["bg"]) : baseTheme.bg),
- text: query["text"] ? normalizeHex(query["text"]) : baseTheme.text,
- colours: customColours
- },
- stroke: query["stroke"] === "true",
- useTestData: query["test"] === "true",
- errorTest: sanitize(query["error"] ?? '')
- }
-}
diff --git a/src/utils/sanitize.ts b/src/utils/sanitize.ts
deleted file mode 100644
index e527af5..0000000
--- a/src/utils/sanitize.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-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/tests/api/languages/index.test.ts b/tests/api/languages/index.test.ts
index db74b4e..68a0a97 100644
--- a/tests/api/languages/index.test.ts
+++ b/tests/api/languages/index.test.ts
@@ -1,18 +1,18 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import type { VercelRequest, VercelResponse } from "@vercel/node";
-import type { ChartResult } from "../../../src/types.js";
+import type { ChartResult } from "github-top-languages-lib/types.js";
+import { parseQueryParams } from "github-top-languages-lib/utils/params.js";
+import { generateChartData } from "github-top-languages-lib/render/chart.js";
+import { renderSvg } from "github-top-languages-lib/render/svg.js";
+import { renderError } from "github-top-languages-lib/render/error.js";
import handler from "../../../api/languages/index.js";
-import { parseQueryParams } from "../../../src/utils/params.js";
import { fetchLanguageData, processLanguageData } from "../../../src/services/github.js";
-import { generateChartData } from "../../../src/render/chart.js";
-import { renderSvg } from "../../../src/render/svg.js";
-import { renderError } from "../../../src/render/error.js";
-vi.mock("../../../src/utils/params.js");
+vi.mock("github-top-languages-lib/utils/params.js");
+vi.mock("github-top-languages-lib/render/chart.js");
+vi.mock("github-top-languages-lib/render/svg.js");
+vi.mock("github-top-languages-lib/render/error.js");
vi.mock("../../../src/services/github.js");
-vi.mock("../../../src/render/chart.js");
-vi.mock("../../../src/render/svg.js");
-vi.mock("../../../src/render/error.js");
describe("handler", () => {
let req: VercelRequest;
diff --git a/tests/charts/donut.test.ts b/tests/charts/donut.test.ts
deleted file mode 100644
index c94284c..0000000
--- a/tests/charts/donut.test.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { describe, it, expect, vi } from "vitest";
-import type { Theme } from "../../src/types.js";
-import { LEGEND_SHIFT_THRESHOLD } from "../../src/constants/styles.js";
-import { generateDonutChart } from "../../src/charts/donut.js";
-import { createDonutSegments } from "../../src/charts/geometry.js";
-import { createLegend } from "../../src/charts/legend.js";
-
-vi.mock("../../src/charts/geometry.js", () => ({
- createDonutSegments: vi.fn(() => ``)
-}));
-
-vi.mock("../../src/charts/legend.js", () => ({
- createLegend: vi.fn(() => "mockLegend")
-}));
-
-const mockCreateDonutSegments = vi.mocked(createDonutSegments);
-const mockCreateLegend = vi.mocked(createLegend);
-
-describe("generateDonutChart", () => {
- 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, false);
- expect(result).toHaveProperty("segments");
- expect(result).toHaveProperty("legend");
- 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, false);
- const legendCall = mockCreateLegend.mock.calls.at(-1) ?? [];
- expect(legendCall[1]).toBe(false);
- });
-
- it("passes isShifted=true when above threshold", () => {
- const langs = Array.from({ length: LEGEND_SHIFT_THRESHOLD + 1 }, (_, i) => ({
- lang: `L${i}`, pct: 100 / (LEGEND_SHIFT_THRESHOLD + 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, 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, 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.ts b/tests/charts/geometry.test.ts
deleted file mode 100644
index 31a0665..0000000
--- a/tests/charts/geometry.test.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { describe, it, expect } from "vitest";
-import type { Language } from "../../src/types.js";
-import { polarToCartesian, describeSegment, createDonutSegments } from "../../src/charts/geometry.js";
-
-const mockGeometry = { CENTER_Y: 100, INNER_RADIUS: 30, OUTER_RADIUS: 50 };
-
-describe("donut geometry", () => {
- it("polarToCartesian: quadrants correct", () => {
- expect(polarToCartesian(100, 100, 50, 0 )).toEqual({ x: 100, y: 50 }); // Top
- expect(polarToCartesian(100, 100, 50, 90 )).toEqual({ x: 150, y: 100 }); // Right
- expect(polarToCartesian(100, 100, 50, 180)).toEqual({ x: 100, y: 150 }); // Bottom
- expect(polarToCartesian(100, 100, 50, 270)).toEqual({ x: 50, y: 100 }); // Left
- });
-
- it("describeSegment: small arc path", () => {
- const path = describeSegment(100, 100, 30, 50, 0, 90);
- expect(path).toMatch(/^M \d+\.?\d* \d+\.?\d*/);
- expect(path).toMatch(/A 50 50 0 0 0/);
- expect(path).toMatch(/Z$/);
- });
-
- it("describeSegment: filled pie starts at center", () => {
- const path = describeSegment(100, 100, 0, 50, 0, 90);
- expect(path.startsWith("M 100 100")).toBe(true);
- });
-
- it("describeSegment: segments have stroke when enabled", () => {
- const langs: Language[] = [{ lang: "JS", pct: 50 }];
- const paths = createDonutSegments(
- langs,
- 100,
- mockGeometry,
- ["#f00"],
- true
- );
- expect(paths).toMatch(/stroke="#000"/);
- });
-
- it("createDonutSegments: single lang full circle", () => {
- const langs: Language[] = [{ lang: "JS", pct: 100 }];
- const paths = createDonutSegments(
- langs,
- 100,
- mockGeometry,
- ["#f00"],
- false
- );
- expect(paths).toMatch(/fill="#f00"/);
- });
-
- it("createDonutSegments: multi-lang sums to 360°", () => {
- const langs: Language[] = [
- { lang: "JS", pct: 33 },
- { lang: "TS", pct: 33 },
- { lang: "PY", pct: 34 }
- ];
- const paths = createDonutSegments(
- langs,
- 100,
- mockGeometry,
- ["#f00", "#0f0", "#00f"],
- false
- );
- expect(paths.split("/>").length - 1).toBe(3);
- });
-});
diff --git a/tests/charts/legend.test.ts b/tests/charts/legend.test.ts
deleted file mode 100644
index 969989e..0000000
--- a/tests/charts/legend.test.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { describe, it, expect } from "vitest";
-import type { Theme } from "../../src/types.js";
-import { LEGEND_STYLES } from "../../src/constants/styles.js";
-import { createLegend } from "../../src/charts/legend.js";
-
-const theme: Theme = { colours: ["#f00", "#0f0", "#00f"], text: "#333", bg: "#fff" };
-
-describe("createLegend", () => {
- it("single-column layout positions correctly", () => {
- const langs = [
- { lang: "JavaScript", pct: 60 },
- { lang: "Python", pct: 40 }
- ];
- 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}"`);
- });
-
- it("two-column layout when isShifted", () => {
- const langs = Array.from({ length: 8 }, (_, i) => ({
- lang: `Lang${i}`,
- pct: 12.5
- }));
- 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, false);
-
- expect(result).toContain("33.3%");
- expect(result).not.toContain("33.333");
- });
-
- it("adds stroke attributes when stroke is enabled", () => {
- const langs = [{ lang: "C#", pct: 100 }];
- const result = createLegend(langs, false, theme, 300, true);
-
- expect(result).toContain(`stroke="#000"`);
- expect(result).toContain(`stroke-width="0.5"`);
- });
-
- it("generates rect and text for each language", () => {
- const langs = [
- { lang: "C#", pct: 50 },
- { lang: "C++", pct: 50 }
- ];
- const result = createLegend(langs, false, theme, 300, false);
- expect(result.match(/ {
- const langs = [{ lang: "Java", pct: 100 }];
- 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.ts b/tests/charts/pie.test.ts
deleted file mode 100644
index 49ba4b6..0000000
--- a/tests/charts/pie.test.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { describe, it, expect, vi } from "vitest";
-import { generatePieChart } from "../../src/charts/pie.js";
-import { createDonutSegments } from "../../src/charts/geometry.js";
-import { LEGEND_SHIFT_THRESHOLD } from "../../src/constants/styles.js";
-import { createLegend } from "../../src/charts/legend.js";
-
-vi.mock("../../src/charts/geometry.js", () => ({
- createDonutSegments: vi.fn(() => ``)
-}));
-
-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", bg: "#fff" };
-
- it("returns segments and legend", () => {
- const langs = [{ lang: "JS", pct: 100 }];
- const result = generatePieChart(langs, theme, 800, false);
- expect(result).toHaveProperty("segments");
- expect(result).toHaveProperty("legend");
- expect(mockCreateDonutSegments).toHaveBeenCalled();
- expect(mockCreateLegend).toHaveBeenCalled();
- });
-
- it("passes INNER_RADIUS: 0 for filled pie", () => {
- const langs = [{ lang: "Python", pct: 100 }];
- generatePieChart(langs, theme, 800, false);
- const call = mockCreateDonutSegments.mock.calls.at(-1)!;
- const geometry = call[2];
- expect(geometry.INNER_RADIUS).toBe(0);
- });
-
- it("shifts legend when above threshold", () => {
- const langs = Array.from({ length: LEGEND_SHIFT_THRESHOLD + 1 }, (_, i) => ({
- lang: `L${i}`, pct: 100 / (LEGEND_SHIFT_THRESHOLD + 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, 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");
- });
-});
diff --git a/tests/render/chart.test.ts b/tests/render/chart.test.ts
deleted file mode 100644
index f2c636f..0000000
--- a/tests/render/chart.test.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { describe, it, expect, vi } from "vitest";
-import type { ChartType, ChartResult, Language, Theme } from "../../src/types.js";
-import { generateChartData } from "../../src/render/chart.js";
-import { generateDonutChart } from "../../src/charts/donut.js";
-import { THEMES } from "../../src/constants/themes.js";
-
-type MockChartResult = ChartResult & {
- mockData: boolean;
- data: Language[];
- theme: Theme;
- width: number;
-}
-
-vi.mock("../../src/charts/donut.js", () => ({
- 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 = 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) as MockChartResult;
- expect(generateDonutChart).toHaveBeenCalledWith(data, theme, width, stroke);
- expect(result.data).toBe(data);
- expect(result.theme).toBe(theme);
- expect(result.width).toBe(width);
- });
-
- it("defaults to donut generator when chartType is undefined", () => {
- 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" as ChartType, width, stroke);
- expect(generateDonutChart).toHaveBeenCalledWith(data, theme, width, stroke);
- });
-});
diff --git a/tests/render/error.test.ts b/tests/render/error.test.ts
deleted file mode 100644
index 2f4d506..0000000
--- a/tests/render/error.test.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { describe, it, expect } from "vitest";
-import type { Theme } from "../../src/types.js";
-import { renderError } from "../../src/render/error.js";
-import { THEMES } from "../../src/constants/themes.js";
-
-describe("renderError", () => {
- it("renders error SVG with message", () => {
- const result = renderError("Test error", 400, 300);
- expect(result).toContain(`