diff --git a/dist/gh-profile-card.min.js b/dist/gh-profile-card.min.js
index 755b314..5aa812f 100644
--- a/dist/gh-profile-card.min.js
+++ b/dist/gh-profile-card.min.js
@@ -1,6 +1,6 @@
/**
* github-profile-card - 3.2.0 | MIT
-* (c) 2014 - 2025 Piotr Lewandowski | https://piotrl.github.io/github-profile-card/
+* (c) 2014 - 2026 Piotr Lewandowski | https://piotrl.github.io/github-profile-card/
*/
(() => {
@@ -9,111 +9,181 @@
constructor(storage) {
this.storage = storage;
this.cacheName = "github-request-cache";
- this.requestCache = this.getCache() || {};
+ this.requestCache = this.initializeCache();
}
get(key) {
return this.requestCache[key];
}
add(url, entry) {
this.requestCache[url] = entry;
- this.storage.setItem(this.cacheName, JSON.stringify(this.requestCache));
+ this.saveCache();
}
- getCache() {
- return JSON.parse(this.storage.getItem(this.cacheName));
+ initializeCache() {
+ try {
+ const cacheData = this.storage.getItem(this.cacheName);
+ if (!cacheData) {
+ return {};
+ }
+ const cache = JSON.parse(cacheData);
+ return cache && typeof cache === "object" ? cache : {};
+ } catch (error) {
+ console.error("Failed to parse cache:", error);
+ try {
+ this.storage.removeItem(this.cacheName);
+ } catch (cleanupError) {
+ console.error("Failed to clear corrupted cache:", cleanupError);
+ }
+ return {};
+ }
+ }
+ saveCache() {
+ try {
+ this.storage.setItem(this.cacheName, JSON.stringify(this.requestCache));
+ } catch (error) {
+ console.error("Failed to save cache:", error);
+ this.clearExpiredEntries(/* @__PURE__ */ new Date());
+ try {
+ this.storage.setItem(this.cacheName, JSON.stringify(this.requestCache));
+ } catch (retryError) {
+ console.error("Failed to save cache after cleanup:", retryError);
+ }
+ }
+ }
+ clearExpiredEntries(currentDate) {
+ let hasChanges = false;
+ for (const [url, entry] of Object.entries(this.requestCache)) {
+ if (entry.lastModified) {
+ const entryDate = new Date(entry.lastModified);
+ if (isNaN(entryDate.getTime()) || entryDate < currentDate) {
+ delete this.requestCache[url];
+ hasChanges = true;
+ }
+ }
+ }
+ if (hasChanges) {
+ this.saveCache();
+ }
}
};
// src/gh-data-loader.ts
+ var API_HOST = "https://api.github.com";
var GitHubApiLoader = class {
constructor() {
- this.apiBase = "https://api.github.com";
this.cache = new CacheStorage(window.localStorage);
}
- loadUserData(username, callback) {
- const request = this.apiGet(`${this.apiBase}/users/${username}`);
- request.success((profile) => {
- this.apiGet(profile.repos_url).success((repositories) => {
- callback({ profile, repositories }, null);
- });
- });
- request.error((result, request2) => {
- const error = this.identifyError(result, request2);
- callback(null, error);
- });
+ async loadUserData(username) {
+ if (typeof username !== "string") {
+ throw new Error("Invalid username provided");
+ }
+ const sanitizedUsername = username.trim();
+ if (!sanitizedUsername) {
+ throw new Error("Username cannot be empty");
+ }
+ const profile = await this.fetch(
+ `${API_HOST}/users/${sanitizedUsername}`
+ );
+ const repositories = await this.fetch(profile.repos_url);
+ return { profile, repositories };
}
loadRepositoriesLanguages(repositories, callback) {
+ if (!repositories || repositories.length === 0) {
+ callback([]);
+ return;
+ }
const languagesUrls = this.extractLangURLs(repositories);
const langStats = [];
- let requestsAmount = languagesUrls.length;
+ const requestsAmount = languagesUrls.length;
+ let completedRequests = 0;
+ if (requestsAmount === 0) {
+ callback([]);
+ return;
+ }
+ const handleCompletion = () => {
+ completedRequests++;
+ if (completedRequests === requestsAmount) {
+ callback(langStats);
+ }
+ };
languagesUrls.forEach((repoLangUrl) => {
- const request = this.apiGet(repoLangUrl);
- request.error(() => requestsAmount--);
- request.success((repoLangs) => {
- langStats.push(repoLangs);
- if (langStats.length === requestsAmount) {
- callback(langStats);
- }
+ this.fetch(repoLangUrl).then((repoLangs) => {
+ langStats.push(repoLangs || {});
+ handleCompletion();
+ }).catch((error) => {
+ console.warn("Failed to load languages for repository:", error);
+ langStats.push({});
+ handleCompletion();
});
});
}
- identifyError(result, request) {
+ async identifyError(response) {
+ let result;
+ try {
+ result = await response.json();
+ } catch {
+ result = { message: "Failed to parse error response" };
+ }
const error = {
- message: result.message
+ message: result.message || `HTTP ${response.status}: ${response.statusText}`
};
- if (request.status === 404) {
+ if (response.status === 404) {
error.isWrongUser = true;
}
- const limitRequests = request.getResponseHeader("X-RateLimit-Remaining");
+ const limitRequests = response.headers.get("X-RateLimit-Remaining");
if (Number(limitRequests) === 0) {
- const resetTime = request.getResponseHeader("X-RateLimit-Reset");
- error.resetDate = new Date(Number(resetTime) * 1e3);
- error.message = error.message.split("(")[0];
+ const resetTime = response.headers.get("X-RateLimit-Reset");
+ if (resetTime) {
+ error.resetDate = new Date(Number(resetTime) * 1e3);
+ error.message = error.message.split("(")[0];
+ }
}
return error;
}
extractLangURLs(profileRepositories) {
- return profileRepositories.map((repository) => repository.languages_url);
+ return profileRepositories.filter((repo) => repo && repo.languages_url).map((repository) => repository.languages_url);
}
- apiGet(url) {
- const request = this.buildRequest(url);
- return {
- success: (callback) => {
- request.addEventListener("load", () => {
- if (request.status === 304) {
- callback(this.cache.get(url).data, request);
- }
- if (request.status === 200) {
- const response = JSON.parse(request.responseText);
- this.cache.add(url, {
- lastModified: request.getResponseHeader("Last-Modified"),
- data: response
- });
- callback(response, request);
- }
- });
- },
- error: (callback) => {
- request.addEventListener("load", () => {
- if (request.status !== 200 && request.status !== 304) {
- callback(JSON.parse(request.responseText), request);
- }
- });
- }
- };
+ async fetch(url) {
+ if (typeof url !== "string") {
+ throw new Error("Invalid URL provided for fetch");
+ }
+ const cache = this.cache.get(url);
+ let response;
+ try {
+ response = await fetch(url, {
+ headers: this.buildHeaders(cache)
+ });
+ } catch (networkError) {
+ throw new Error(`Network error: ${networkError.message}`);
+ }
+ if (response.status === 304 && cache) {
+ return cache.data;
+ }
+ if (response.status !== 200) {
+ throw await this.identifyError(response);
+ }
+ let jsonResponse;
+ try {
+ jsonResponse = await response.json();
+ } catch {
+ throw new Error("Failed to parse API response as JSON");
+ }
+ const lastModified = response.headers.get("Last-Modified");
+ if (lastModified) {
+ this.cache.add(url, {
+ lastModified,
+ data: jsonResponse
+ });
+ }
+ return jsonResponse;
}
- buildRequest(url) {
- const request = new XMLHttpRequest();
- request.open("GET", url);
- this.buildApiHeaders(request, url);
- request.send();
- return request;
- }
- buildApiHeaders(request, url) {
- request.setRequestHeader("Accept", "application/vnd.github.v3+json");
- const urlCache = this.cache.get(url);
- if (urlCache) {
- request.setRequestHeader("If-Modified-Since", urlCache.lastModified);
+ buildHeaders(cache) {
+ const headers = {
+ Accept: "application/vnd.github.v3+json"
+ };
+ if (cache?.lastModified) {
+ headers["If-Modified-Since"] = cache.lastModified;
}
+ return headers;
}
};
@@ -131,26 +201,27 @@
const $name = document.createElement("a");
$name.href = profileUrl;
$name.className = "name";
- $name.appendChild(document.createTextNode(name));
+ $name.appendChild(document.createTextNode(name || ""));
return $name;
}
function createAvatar(avatarUrl) {
const $avatar = document.createElement("img");
$avatar.src = avatarUrl;
$avatar.className = "avatar";
+ $avatar.alt = "GitHub avatar";
return $avatar;
}
function createFollowButton(username, followUrl) {
const $followButton = document.createElement("a");
$followButton.href = followUrl;
$followButton.className = "follow-button";
- $followButton.innerHTML = "Follow @" + username;
+ $followButton.textContent = `Follow @${username}`;
return $followButton;
}
function createFollowers(followersAmount) {
const $followers = document.createElement("span");
$followers.className = "followers";
- $followers.innerHTML = "" + followersAmount;
+ $followers.textContent = String(followersAmount);
return $followers;
}
function createFollowContainer(children) {
@@ -163,21 +234,27 @@
// src/gh-dom-operator.ts
var DOMOperator = class {
static clearChildren($parent) {
- while ($parent.hasChildNodes()) {
- $parent.removeChild($parent.firstChild);
- }
+ $parent.textContent = "";
}
static createError(error, username) {
const $error = document.createElement("div");
$error.className = "error";
- $error.innerHTML = `${error.message}`;
+ const $message = document.createElement("span");
+ $message.textContent = error.message;
+ $error.appendChild($message);
if (error.isWrongUser) {
- $error.innerHTML = `Not found user: ${username}`;
+ $message.textContent = `Not found user: ${username}`;
}
if (error.resetDate) {
- let remainingTime = error.resetDate.getMinutes() - (/* @__PURE__ */ new Date()).getMinutes();
- remainingTime = remainingTime < 0 ? 60 + remainingTime : remainingTime;
- $error.innerHTML += `Come back after ${remainingTime} minutes`;
+ const currentTime = (/* @__PURE__ */ new Date()).getTime();
+ const resetTime = error.resetDate.getTime();
+ const remainingMinutes = Math.ceil(
+ (resetTime - currentTime) / (1e3 * 60)
+ );
+ const $remainingTime = document.createElement("span");
+ $remainingTime.className = "remain";
+ $remainingTime.textContent = `Come back after ${remainingMinutes} minutes`;
+ $error.appendChild($remainingTime);
}
return $error;
}
@@ -195,10 +272,19 @@
return $langsList;
}
static createTopLanguagesList(langs) {
- return Object.keys(langs).map((language) => ({
+ const sortedLanguages = Object.keys(langs).map((language) => ({
name: language,
stat: langs[language]
- })).sort((a, b) => b.stat - a.stat).slice(0, 3).map((lang) => `
${lang.name}`).reduce((list, nextElement) => list + nextElement);
+ })).sort((a, b) => b.stat - a.stat).slice(0, 3);
+ return sortedLanguages.map((lang) => {
+ const escapedName = this.escapeHtml(lang.name);
+ return `${escapedName}`;
+ }).join("");
+ }
+ static escapeHtml(text) {
+ const div = document.createElement("div");
+ div.textContent = text;
+ return div.innerHTML;
}
static createRepositoriesHeader(headerText) {
const $repositoriesHeader = document.createElement("span");
@@ -216,12 +302,19 @@
const updated = new Date(repository.updated_at);
const $repoLink = document.createElement("a");
$repoLink.href = repository.html_url;
- $repoLink.title = repository.description;
- $repoLink.innerHTML = `
- ${repository.name}
- Updated: ${updated.toLocaleDateString()}
- ${repository.stargazers_count}
- `;
+ $repoLink.title = repository.description || "";
+ const $repoName = document.createElement("span");
+ $repoName.className = "repo-name";
+ $repoName.textContent = repository.name;
+ const $updated = document.createElement("span");
+ $updated.className = "updated";
+ $updated.textContent = `Updated: ${updated.toLocaleDateString()}`;
+ const $star = document.createElement("span");
+ $star.className = "star";
+ $star.textContent = String(repository.stargazers_count);
+ $repoLink.appendChild($repoName);
+ $repoLink.appendChild($updated);
+ $repoLink.appendChild($star);
return $repoLink;
}
};
@@ -230,15 +323,18 @@
var GitHubCardWidget = class {
constructor(options = {}) {
this.apiLoader = new GitHubApiLoader();
+ this.userData = null;
this.$template = this.findTemplate(options.template);
this.extractHtmlConfig(options, this.$template);
this.options = this.completeConfiguration(options);
}
- init() {
- this.apiLoader.loadUserData(this.options.username, (data, err) => {
- this.userData = data;
+ async init() {
+ try {
+ this.userData = await this.apiLoader.loadUserData(this.options.username);
+ this.render(this.options);
+ } catch (err) {
this.render(this.options, err);
- });
+ }
}
refresh(options) {
this.options = this.completeConfiguration(options);
@@ -246,7 +342,7 @@
}
completeConfiguration(options) {
const defaultConfig = {
- username: null,
+ username: "",
template: "#github-card",
sortBy: "stars",
// possible: 'stars', 'updateTime'
@@ -254,53 +350,62 @@
maxRepos: 5,
hideTopLanguages: false
};
- for (const key in defaultConfig) {
- defaultConfig[key] = options[key] || defaultConfig[key];
- }
- return defaultConfig;
+ return {
+ ...defaultConfig,
+ ...options
+ };
}
findTemplate(templateCssSelector = "#github-card") {
const $template = document.querySelector(
templateCssSelector
);
if (!$template) {
- throw `No template found for selector: ${templateCssSelector}`;
+ throw new Error(`No template found for selector: ${templateCssSelector}`);
}
$template.className = "gh-profile-card";
return $template;
}
extractHtmlConfig(widgetConfig, $template) {
- widgetConfig.username = widgetConfig.username || $template.dataset["username"];
- widgetConfig.sortBy = widgetConfig.sortBy || $template.dataset["sortBy"];
- widgetConfig.headerText = widgetConfig.headerText || $template.dataset["headerText"];
- widgetConfig.maxRepos = widgetConfig.maxRepos || parseInt($template.dataset["maxRepos"], 10);
- widgetConfig.hideTopLanguages = widgetConfig.hideTopLanguages || $template.dataset["hideTopLanguages"] === "true";
+ const dataset = $template.dataset;
+ widgetConfig.username = widgetConfig.username || dataset.username;
+ widgetConfig.sortBy = widgetConfig.sortBy || dataset.sortBy;
+ widgetConfig.headerText = widgetConfig.headerText || dataset.headerText;
+ if (dataset.maxRepos) {
+ const parsedMaxRepos = parseInt(dataset.maxRepos, 10);
+ if (!isNaN(parsedMaxRepos)) {
+ widgetConfig.maxRepos = widgetConfig.maxRepos || parsedMaxRepos;
+ }
+ }
+ widgetConfig.hideTopLanguages = widgetConfig.hideTopLanguages || dataset.hideTopLanguages === "true";
if (!widgetConfig.username) {
- throw "Not provided username";
+ throw new Error("Username is required but not provided");
}
}
render(options, error) {
const $root = this.$template;
DOMOperator.clearChildren($root);
if (error) {
- const $errorSection = DOMOperator.createError(error, options.username);
+ const $errorSection = DOMOperator.createError(
+ error,
+ options.username || ""
+ );
$root.appendChild($errorSection);
return;
}
const repositories = this.userData.repositories;
- this.sortRepositories(repositories, options.sortBy);
+ this.sortRepositories(repositories, options.sortBy || "stars");
const $profile = DOMOperator.createProfile(this.userData.profile);
if (!options.hideTopLanguages) {
$profile.appendChild(this.createTopLanguagesSection(repositories));
}
$root.appendChild($profile);
- if (options.maxRepos > 0) {
+ if ((options.maxRepos || 0) > 0) {
const $reposHeader = DOMOperator.createRepositoriesHeader(
- options.headerText
+ options.headerText || "Repositories"
);
const $reposList = DOMOperator.createRepositoriesList(
repositories,
- options.maxRepos
+ options.maxRepos || 5
);
$reposList.insertBefore($reposHeader, $reposList.firstChild);
$root.appendChild($reposList);
@@ -308,11 +413,16 @@
}
createTopLanguagesSection(repositories) {
const $topLanguages = DOMOperator.createTopLanguagesSection();
+ if (!repositories || repositories.length === 0) {
+ return $topLanguages;
+ }
this.apiLoader.loadRepositoriesLanguages(
repositories.slice(0, 10),
(langStats) => {
- const languagesRank = this.groupLanguagesUsage(langStats);
- $topLanguages.innerHTML = DOMOperator.createTopLanguagesList(languagesRank);
+ if (langStats.length > 0) {
+ const languagesRank = this.groupLanguagesUsage(langStats);
+ $topLanguages.innerHTML = DOMOperator.createTopLanguagesList(languagesRank);
+ }
}
);
return $topLanguages;
@@ -320,16 +430,22 @@
groupLanguagesUsage(langStats) {
const languagesRank = {};
langStats.forEach((repoLangs) => {
- for (const language in repoLangs) {
- languagesRank[language] = languagesRank[language] || 0;
- languagesRank[language] += repoLangs[language];
+ if (repoLangs && typeof repoLangs === "object") {
+ Object.entries(repoLangs).forEach(([language, bytes]) => {
+ if (typeof bytes === "number" && bytes > 0) {
+ languagesRank[language] = (languagesRank[language] || 0) + bytes;
+ }
+ });
}
});
return languagesRank;
}
- sortRepositories(repos, sortyBy) {
+ sortRepositories(repos, sortBy) {
+ if (!repos || repos.length === 0) {
+ return;
+ }
repos.sort((firstRepo, secondRepo) => {
- if (sortyBy === "stars") {
+ if (sortBy === "stars") {
const starDifference = secondRepo.stargazers_count - firstRepo.stargazers_count;
if (starDifference !== 0) {
return starDifference;
@@ -339,7 +455,12 @@
});
}
dateDifference(first, second) {
- return new Date(first).getTime() - new Date(second).getTime();
+ const firstDate = new Date(first);
+ const secondDate = new Date(second);
+ if (isNaN(firstDate.getTime()) || isNaN(secondDate.getTime())) {
+ return 0;
+ }
+ return firstDate.getTime() - secondDate.getTime();
}
};
@@ -549,8 +670,14 @@
document.addEventListener("DOMContentLoaded", () => {
const $defaultTemplate = document.querySelector("#github-card");
if ($defaultTemplate) {
- const widget = new GitHubCardWidget();
- widget.init();
+ try {
+ const widget = new GitHubCardWidget();
+ widget.init().catch((error) => {
+ console.error("Failed to initialize GitHub Card widget:", error);
+ });
+ } catch (error) {
+ console.error("Failed to construct GitHub Card widget:", error);
+ }
}
});
})();
diff --git a/knip.json b/knip.json
new file mode 100644
index 0000000..365c613
--- /dev/null
+++ b/knip.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "https://unpkg.com/knip@5.82.1/schema.json",
+ "entry": ["src/gh-widget-init.ts"],
+ "project": ["src/**/*.ts"],
+ "ignore": ["demo/**", "dist/**", "src/**/*.spec.ts"],
+ "ignoreDependencies": ["sass"]
+}
diff --git a/package-lock.json b/package-lock.json
index 8d75179..4fef88e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,6 +19,7 @@
"eslint-plugin-prettier": "5.5.1",
"jest": "30.0.4",
"jest-environment-jsdom": "30.0.4",
+ "knip": "5.82.1",
"prettier": "3.6.2",
"sass": "1.25.0",
"ts-jest": "29.4.0",
@@ -709,21 +710,21 @@
}
},
"node_modules/@emnapi/core": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.4.tgz",
- "integrity": "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g==",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
+ "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/wasi-threads": "1.0.3",
+ "@emnapi/wasi-threads": "1.1.0",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.4.tgz",
- "integrity": "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
+ "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -732,9 +733,9 @@
}
},
"node_modules/@emnapi/wasi-threads": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.3.tgz",
- "integrity": "sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
+ "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -1966,6 +1967,306 @@
"node": ">= 8"
}
},
+ "node_modules/@oxc-resolver/binding-android-arm-eabi": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.16.4.tgz",
+ "integrity": "sha512-6XUHilmj8D6Ggus+sTBp64x/DUQ7LgC/dvTDdUOt4iMQnDdSep6N1mnvVLIiG+qM5tRnNHravNzBJnUlYwRQoA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-android-arm64": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.16.4.tgz",
+ "integrity": "sha512-5ODwd1F5mdkm6JIg1CNny9yxIrCzrkKpxmqas7Alw23vE0Ot8D4ykqNBW5Z/nIZkXVEo5VDmnm0sMBBIANcpeQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-darwin-arm64": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.16.4.tgz",
+ "integrity": "sha512-egwvDK9DMU4Q8F4BG74/n4E22pQ0lT5ukOVB6VXkTj0iG2fnyoStHoFaBnmDseLNRA4r61Mxxz8k940CIaJMDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-darwin-x64": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.16.4.tgz",
+ "integrity": "sha512-HMkODYrAG4HaFNCpaYzSQFkxeiz2wzl+smXwxeORIQVEo1WAgUrWbvYT/0RNJg/A8z2aGMGK5KWTUr2nX5GiMw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-freebsd-x64": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.16.4.tgz",
+ "integrity": "sha512-mkcKhIdSlUqnndD928WAVVFMEr1D5EwHOBGHadypW0PkM0h4pn89ZacQvU7Qs/Z2qquzvbyw8m4Mq3jOYI+4Dw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.16.4.tgz",
+ "integrity": "sha512-ZJvzbmXI/cILQVcJL9S2Fp7GLAIY4Yr6mpGb+k6LKLUSEq85yhG+rJ9eWCqgULVIf2BFps/NlmPTa7B7oj8jhQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.16.4.tgz",
+ "integrity": "sha512-iZUB0W52uB10gBUDAi79eTnzqp1ralikCAjfq7CdokItwZUVJXclNYANnzXmtc0Xr0ox+YsDsG2jGcj875SatA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-arm64-gnu": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.16.4.tgz",
+ "integrity": "sha512-qNQk0H6q1CnwS9cnvyjk9a+JN8BTbxK7K15Bb5hYfJcKTG1hfloQf6egndKauYOO0wu9ldCMPBrEP1FNIQEhaA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-arm64-musl": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.16.4.tgz",
+ "integrity": "sha512-wEXSaEaYxGGoVSbw0i2etjDDWcqErKr8xSkTdwATP798efsZmodUAcLYJhN0Nd4W35Oq6qAvFGHpKwFrrhpTrA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.16.4.tgz",
+ "integrity": "sha512-CUFOlpb07DVOFLoYiaTfbSBRPIhNgwc/MtlYeg3p6GJJw+kEm/vzc9lohPSjzF2MLPB5hzsJdk+L/GjrTT3UPw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.16.4.tgz",
+ "integrity": "sha512-d8It4AH8cN9ReK1hW6ZO4x3rMT0hB2LYH0RNidGogV9xtnjLRU+Y3MrCeClLyOSGCibmweJJAjnwB7AQ31GEhg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-riscv64-musl": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.16.4.tgz",
+ "integrity": "sha512-d09dOww9iKyEHSxuOQ/Iu2aYswl0j7ExBcyy14D6lJ5ijQSP9FXcJYJsJ3yvzboO/PDEFjvRuF41f8O1skiPVg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-s390x-gnu": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.16.4.tgz",
+ "integrity": "sha512-lhjyGmUzTWHduZF3MkdUSEPMRIdExnhsqv8u1upX3A15epVn6YVwv4msFQPJl1x1wszkACPeDHGOtzHsITXGdw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-x64-gnu": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.16.4.tgz",
+ "integrity": "sha512-ZtqqiI5rzlrYBm/IMMDIg3zvvVj4WO/90Dg/zX+iA8lWaLN7K5nroXb17MQ4WhI5RqlEAgrnYDXW+hok1D9Kaw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-linux-x64-musl": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.16.4.tgz",
+ "integrity": "sha512-LM424h7aaKcMlqHnQWgTzO+GRNLyjcNnMpqm8SygEtFRVW693XS+XGXYvjORlmJtsyjo84ej1FMb3U2HE5eyjg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-openharmony-arm64": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.16.4.tgz",
+ "integrity": "sha512-8w8U6A5DDWTBv3OUxSD9fNk37liZuEC5jnAc9wQRv9DeYKAXvuUtBfT09aIZ58swaci0q1WS48/CoMVEO6jdCA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-wasm32-wasi": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.16.4.tgz",
+ "integrity": "sha512-hnjb0mDVQOon6NdfNJ1EmNquonJUjoYkp7UyasjxVa4iiMcApziHP4czzzme6WZbp+vzakhVv2Yi5ACTon3Zlw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
+ "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1",
+ "@tybys/wasm-util": "^0.10.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ }
+ },
+ "node_modules/@oxc-resolver/binding-win32-arm64-msvc": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.16.4.tgz",
+ "integrity": "sha512-+i0XtNfSP7cfnh1T8FMrMm4HxTeh0jxKP/VQCLWbjdUxaAQ4damho4gN9lF5dl0tZahtdszXLUboBFNloSJNOQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-win32-ia32-msvc": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.16.4.tgz",
+ "integrity": "sha512-ePW1islJrv3lPnef/iWwrjrSpRH8kLlftdKf2auQNWvYLx6F0xvcnv9d+r/upnVuttoQY9amLnWJf+JnCRksTw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@oxc-resolver/binding-win32-x64-msvc": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.16.4.tgz",
+ "integrity": "sha512-qnjQhjHI4TDL3hkidZyEmQRK43w2NHl6TP5Rnt/0XxYuLdEgx/1yzShhYidyqWzdnhGhSPTM/WVP2mK66XLegA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/@parcel/watcher": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
@@ -2364,9 +2665,9 @@
"peer": true
},
"node_modules/@tybys/wasm-util": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz",
- "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==",
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -4440,6 +4741,16 @@
"bser": "2.1.1"
}
},
+ "node_modules/fd-package-json": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz",
+ "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "walk-up-path": "^4.0.0"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -4554,6 +4865,22 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/formatly": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz",
+ "integrity": "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fd-package-json": "^2.0.0"
+ },
+ "bin": {
+ "formatly": "bin/index.mjs"
+ },
+ "engines": {
+ "node": ">=18.3.0"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -5759,6 +6086,16 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -5767,9 +6104,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "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": {
@@ -5883,6 +6220,74 @@
"json-buffer": "3.0.1"
}
},
+ "node_modules/knip": {
+ "version": "5.82.1",
+ "resolved": "https://registry.npmjs.org/knip/-/knip-5.82.1.tgz",
+ "integrity": "sha512-1nQk+5AcnkqL40kGQXfouzAEXkTR+eSrgo/8m1d0BMei4eAzFwghoXC4gOKbACgBiCof7hE8wkBVDsEvznf85w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/webpro"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/knip"
+ }
+ ],
+ "license": "ISC",
+ "dependencies": {
+ "@nodelib/fs.walk": "^1.2.3",
+ "fast-glob": "^3.3.3",
+ "formatly": "^0.3.0",
+ "jiti": "^2.6.0",
+ "js-yaml": "^4.1.1",
+ "minimist": "^1.2.8",
+ "oxc-resolver": "^11.15.0",
+ "picocolors": "^1.1.1",
+ "picomatch": "^4.0.1",
+ "smol-toml": "^1.5.2",
+ "strip-json-comments": "5.0.3",
+ "zod": "^4.1.11"
+ },
+ "bin": {
+ "knip": "bin/knip.js",
+ "knip-bun": "bin/knip-bun.js"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18",
+ "typescript": ">=5.0.4 <7"
+ }
+ },
+ "node_modules/knip/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/knip/node_modules/strip-json-comments": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
+ "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/leven": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
@@ -6054,6 +6459,16 @@
"node": "*"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@@ -6190,6 +6605,38 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/oxc-resolver": {
+ "version": "11.16.4",
+ "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.16.4.tgz",
+ "integrity": "sha512-nvJr3orFz1wNaBA4neRw7CAn0SsjgVaEw1UHpgO/lzVW12w+nsFnvU/S6vVX3kYyFaZdxZheTExi/fa8R8PrZA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ },
+ "optionalDependencies": {
+ "@oxc-resolver/binding-android-arm-eabi": "11.16.4",
+ "@oxc-resolver/binding-android-arm64": "11.16.4",
+ "@oxc-resolver/binding-darwin-arm64": "11.16.4",
+ "@oxc-resolver/binding-darwin-x64": "11.16.4",
+ "@oxc-resolver/binding-freebsd-x64": "11.16.4",
+ "@oxc-resolver/binding-linux-arm-gnueabihf": "11.16.4",
+ "@oxc-resolver/binding-linux-arm-musleabihf": "11.16.4",
+ "@oxc-resolver/binding-linux-arm64-gnu": "11.16.4",
+ "@oxc-resolver/binding-linux-arm64-musl": "11.16.4",
+ "@oxc-resolver/binding-linux-ppc64-gnu": "11.16.4",
+ "@oxc-resolver/binding-linux-riscv64-gnu": "11.16.4",
+ "@oxc-resolver/binding-linux-riscv64-musl": "11.16.4",
+ "@oxc-resolver/binding-linux-s390x-gnu": "11.16.4",
+ "@oxc-resolver/binding-linux-x64-gnu": "11.16.4",
+ "@oxc-resolver/binding-linux-x64-musl": "11.16.4",
+ "@oxc-resolver/binding-openharmony-arm64": "11.16.4",
+ "@oxc-resolver/binding-wasm32-wasi": "11.16.4",
+ "@oxc-resolver/binding-win32-arm64-msvc": "11.16.4",
+ "@oxc-resolver/binding-win32-ia32-msvc": "11.16.4",
+ "@oxc-resolver/binding-win32-x64-msvc": "11.16.4"
+ }
+ },
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -7218,6 +7665,19 @@
"node": ">=8"
}
},
+ "node_modules/smol-toml": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz",
+ "integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/cyyynthia"
+ }
+ },
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -7976,6 +8436,16 @@
"node": ">=18"
}
},
+ "node_modules/walk-up-path": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz",
+ "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
@@ -8329,6 +8799,16 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/package.json b/package.json
index a40aee6..f4d5b79 100644
--- a/package.json
+++ b/package.json
@@ -7,24 +7,26 @@
"devDependencies": {
"@eslint/js": "9.31.0",
"@jest/globals": "30.0.4",
+ "esbuild": "0.25.1",
+ "esbuild-sass-plugin": "3.3.1",
"eslint": "9.31.0",
- "eslint-plugin-jest": "28.8.3",
"eslint-config-prettier": "10.1.5",
+ "eslint-plugin-jest": "28.8.3",
"eslint-plugin-prettier": "5.5.1",
"jest": "30.0.4",
"jest-environment-jsdom": "30.0.4",
+ "knip": "5.82.1",
"prettier": "3.6.2",
"sass": "1.25.0",
"ts-jest": "29.4.0",
"typescript": "5.8.3",
- "typescript-eslint": "8.36.0",
- "esbuild": "0.25.1",
- "esbuild-sass-plugin": "3.3.1"
+ "typescript-eslint": "8.36.0"
},
"scripts": {
"build": "node build.js",
"build:check": "npm run build && npm run lint && npm run format:check && npm run test",
"lint": "eslint .",
+ "lint:knip": "knip",
"format": "prettier --write {*,src/*,demo/*}",
"format:check": "prettier --check {*,src/*,demo/*}",
"test": "jest",
diff --git a/src/testing/cache-mock.ts b/src/testing/cache-mock.ts
index f1507bf..7cd86bd 100644
--- a/src/testing/cache-mock.ts
+++ b/src/testing/cache-mock.ts
@@ -20,32 +20,3 @@ export function createCacheMock(): jest.Mocked {
export function setupEmptyCache(mockCache: jest.Mocked): void {
mockCache.get.mockReturnValue(undefined);
}
-
-/**
- * Sets up cache mock with cached data
- */
-export function setupCacheWithData(
- mockCache: jest.Mocked,
- url: string,
- data: any,
- lastModified: string = 'Mon, 18 Mar 2019 20:40:35 GMT',
-): void {
- mockCache.get.mockImplementation((requestedUrl: string) => {
- if (requestedUrl === url) {
- return {
- lastModified,
- data,
- };
- }
- return undefined;
- });
-}
-
-/**
- * Resets all cache mock calls
- */
-export function resetCacheMock(mockCache: jest.Mocked): void {
- mockCache.get.mockClear();
- mockCache.add.mockClear();
- mockCache.clearExpiredEntries.mockClear();
-}
diff --git a/src/testing/fetch-mock.ts b/src/testing/fetch-mock.ts
index 7e57cd1..37931c7 100644
--- a/src/testing/fetch-mock.ts
+++ b/src/testing/fetch-mock.ts
@@ -54,15 +54,6 @@ export function createErrorResponse(
};
}
-/**
- * Creates a 304 Not Modified response mock
- */
-export function createNotModifiedResponse(): MockResponse {
- return {
- status: 304,
- };
-}
-
/**
* Creates a network error for fetch
*/
diff --git a/src/testing/mock-github-data.ts b/src/testing/mock-github-data.ts
index b27e2ff..4c94e8f 100644
--- a/src/testing/mock-github-data.ts
+++ b/src/testing/mock-github-data.ts
@@ -180,27 +180,3 @@ export const mockLanguageStats = [
{ TypeScript: 1000, JavaScript: 500 },
{ Python: 800, TypeScript: 200 },
];
-
-/**
- * Creates a mock repository with custom properties
- */
-export function createMockRepository(
- overrides: Partial = {},
-): ApiRepository {
- return {
- ...mockRepositories[0],
- ...overrides,
- };
-}
-
-/**
- * Creates a mock profile with custom properties
- */
-export function createMockProfile(
- overrides: Partial = {},
-): ApiProfile {
- return {
- ...mockProfile,
- ...overrides,
- };
-}
diff --git a/src/testing/test-utils.ts b/src/testing/test-utils.ts
index fc8886c..1e5324f 100644
--- a/src/testing/test-utils.ts
+++ b/src/testing/test-utils.ts
@@ -25,30 +25,3 @@ export function cleanupTestEnvironment(): void {
jest.clearAllMocks();
resetFetchMock();
}
-
-export function setupTestDOM(html: string): void {
- document.body.innerHTML = html;
-}
-
-export function setupCommonTestHooks(): {
- getLoader: () => GitHubApiLoader;
- getMockCache: () => jest.Mocked;
-} {
- let loader: GitHubApiLoader;
- let mockCache: jest.Mocked;
-
- beforeEach(() => {
- const env = setupTestEnvironment();
- loader = env.loader;
- mockCache = env.mockCache;
- });
-
- afterEach(() => {
- cleanupTestEnvironment();
- });
-
- return {
- getLoader: () => loader,
- getMockCache: () => mockCache,
- };
-}